From c11bd1d9d9b5c211d36b735e6d441c4b40adb4ca Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Thu, 4 Apr 2024 13:28:55 +0500 Subject: [PATCH 01/19] MSEARCH-707: add get consortium item api --- .../SearchConsortiumController.java | 34 +++++++++++++++++-- .../resources/swagger.api/mod-search.yaml | 33 ++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 69f77425e..72c6b5095 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -1,13 +1,18 @@ package org.folio.search.controller; import lombok.RequiredArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; import org.folio.search.domain.dto.ConsortiumHoldingCollection; +import org.folio.search.domain.dto.ConsortiumItem; import org.folio.search.domain.dto.ConsortiumItemCollection; +import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.SortOrder; import org.folio.search.exception.RequestValidationException; import org.folio.search.model.service.ConsortiumSearchContext; +import org.folio.search.model.service.CqlSearchRequest; import org.folio.search.model.types.ResourceType; import org.folio.search.rest.resource.SearchConsortiumApi; +import org.folio.search.service.SearchService; import org.folio.search.service.consortium.ConsortiumInstanceService; import org.folio.search.service.consortium.ConsortiumTenantService; import org.folio.spring.integration.XOkapiHeaders; @@ -27,13 +32,14 @@ public class SearchConsortiumController implements SearchConsortiumApi { private final ConsortiumTenantService consortiumTenantService; private final ConsortiumInstanceService instanceService; + private final SearchService searchService; @Override public ResponseEntity getConsortiumHoldings(String tenantHeader, String instanceId, String tenantId, Integer limit, Integer offset, String sortBy, SortOrder sortOrder) { - checkAllowance(tenantHeader); + verifyAndGetTenant(tenantHeader); var context = ConsortiumSearchContext.builderFor(ResourceType.HOLDINGS) .filter("instanceId", instanceId) .filter("tenantId", tenantId) @@ -50,7 +56,7 @@ public ResponseEntity getConsortiumItems(String tenant String holdingsRecordId, String tenantId, Integer limit, Integer offset, String sortBy, SortOrder sortOrder) { - checkAllowance(tenantHeader); + verifyAndGetTenant(tenantHeader); var context = ConsortiumSearchContext.builderFor(ResourceType.ITEM) .filter("instanceId", instanceId) .filter("tenantId", tenantId) @@ -63,11 +69,33 @@ public ResponseEntity getConsortiumItems(String tenant return ResponseEntity.ok(instanceService.fetchItems(context)); } - private void checkAllowance(String tenantHeader) { + @Override + public ResponseEntity getConsortiumItem(String itemId, String tenantHeader, String tenantId) { + var tenant = verifyAndGetTenant(tenantHeader); + var query = "items.id=" + itemId; + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, false); + var result = searchService.search(searchRequest); + + if (CollectionUtils.isEmpty(result.getRecords()) + || CollectionUtils.isEmpty(result.getRecords().iterator().next().getItems())) { + return ResponseEntity.ok(new ConsortiumItem()); + } + var instanceId = result.getRecords().iterator().next().getId(); + var item = result.getRecords().iterator().next().getItems().iterator().next(); + return ResponseEntity.ok(new ConsortiumItem() + .id(itemId) + .tenantId(item.getTenantId()) + .instanceId(instanceId) + .holdingsRecordId(item.getHoldingsRecordId()) + ); + } + + private String verifyAndGetTenant(String tenantHeader) { var centralTenant = consortiumTenantService.getCentralTenant(tenantHeader); if (centralTenant.isEmpty() || !centralTenant.get().equals(tenantHeader)) { throw new RequestValidationException(REQUEST_NOT_ALLOWED_MSG, XOkapiHeaders.TENANT, tenantHeader); } + return centralTenant.get(); } } diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index a4841b058..1449f62b9 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -255,6 +255,28 @@ paths: '500': $ref: '#/components/responses/internalServerErrorResponse' + /search/consortium/item: + get: + operationId: getConsortiumItem + description: Get an item (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '#/components/parameters/item-id-query-param' + - $ref: '#/components/parameters/tenant-id-query-param' + - $ref: '#/components/parameters/x-okapi-tenant-header' + responses: + '200': + description: an instance item + content: + application/json: + schema: + $ref: '#/components/schemas/consortiumItem' + '400': + $ref: '#/components/responses/badRequestResponse' + '500': + $ref: '#/components/responses/internalServerErrorResponse' + /browse/call-numbers/instances: get: operationId: browseInstancesByCallNumber @@ -1001,6 +1023,13 @@ components: required: false schema: type: string + item-id-query-param: + in: query + name: itemId + description: UUID of the item + required: true + schema: + type: string holdings-id-query-param: in: query name: holdingsRecordId @@ -1019,7 +1048,7 @@ components: in: query name: sortBy description: | - Defines a field to sort by. + Defines a field to sort by. Possible values: - id - hrid @@ -1037,7 +1066,7 @@ components: in: query name: sortBy description: | - Defines a field to sort by. + Defines a field to sort by. Possible values: - id - hrid From 50e3d95918da6ed83b760c013682d02155f5e6b2 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Thu, 4 Apr 2024 14:07:07 +0500 Subject: [PATCH 02/19] MSEARCH-707: add module description for api --- descriptors/ModuleDescriptor-template.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 94fe9a47c..92e384e60 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -141,7 +141,7 @@ }, { "id": "consortium-search", - "version": "1.0", + "version": "1.1", "handlers": [ { "methods": [ @@ -166,6 +166,18 @@ "modulePermissions": [ "user-tenants.collection.get" ] + }, + { + "methods": [ + "GET" + ], + "pathPattern": "/search/consortium/item", + "permissionsRequired": [ + "consortium-search.items.collection.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] } ] }, From d37fdbd576c32b926fc83c71f439d40942fe1c42 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Thu, 4 Apr 2024 14:57:35 +0500 Subject: [PATCH 03/19] MSEARCH-707: add module description for api --- .../folio/search/controller/SearchConsortiumController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 72c6b5095..090c22c66 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -1,6 +1,7 @@ package org.folio.search.controller; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.domain.dto.ConsortiumItem; @@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Log4j2 @Validated @RestController @RequiredArgsConstructor @@ -82,6 +84,8 @@ public ResponseEntity getConsortiumItem(String itemId, String te } var instanceId = result.getRecords().iterator().next().getId(); var item = result.getRecords().iterator().next().getItems().iterator().next(); + log.info("Instance: {}", result.getRecords().iterator().next()); + log.info("Item: {}", item); return ResponseEntity.ok(new ConsortiumItem() .id(itemId) .tenantId(item.getTenantId()) From a3e7a1b2c3ef2c696b0cd2158d45ddc978608775 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Thu, 4 Apr 2024 15:12:03 +0500 Subject: [PATCH 04/19] MSEARCH-707: add module description for api --- .../org/folio/search/controller/SearchConsortiumController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 090c22c66..5f260ead7 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -75,7 +75,7 @@ public ResponseEntity getConsortiumItems(String tenant public ResponseEntity getConsortiumItem(String itemId, String tenantHeader, String tenantId) { var tenant = verifyAndGetTenant(tenantHeader); var query = "items.id=" + itemId; - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, false); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); var result = searchService.search(searchRequest); if (CollectionUtils.isEmpty(result.getRecords()) From ecc3b705138e794e1e4859e1529285b46d407282 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Thu, 4 Apr 2024 15:13:13 +0500 Subject: [PATCH 05/19] MSEARCH-707: add module description for api --- .../org/folio/search/controller/SearchConsortiumController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 5f260ead7..7d72be869 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -84,7 +84,6 @@ public ResponseEntity getConsortiumItem(String itemId, String te } var instanceId = result.getRecords().iterator().next().getId(); var item = result.getRecords().iterator().next().getItems().iterator().next(); - log.info("Instance: {}", result.getRecords().iterator().next()); log.info("Item: {}", item); return ResponseEntity.ok(new ConsortiumItem() .id(itemId) From cdf01f09589c1213f820ebb452e651f5b6942d02 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Thu, 4 Apr 2024 15:43:41 +0500 Subject: [PATCH 06/19] MSEARCH-707: add module description for api --- src/main/resources/model/instance.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/model/instance.json b/src/main/resources/model/instance.json index 6aa20081f..619385864 100644 --- a/src/main/resources/model/instance.json +++ b/src/main/resources/model/instance.json @@ -273,6 +273,9 @@ "index": "keyword", "showInResponse": [ "call-number-browse" ] }, + "holdingsRecordId": { + "index": "keyword" + }, "hrid": { "index": "keyword" }, From 5126d0281571e45cbd9f56bd5b6de10a8e49f6e5 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Fri, 5 Apr 2024 16:40:33 +0500 Subject: [PATCH 07/19] MSEARCH-707: add module description for api --- .../SearchConsortiumController.java | 36 ++++++++++++--- .../resources/swagger.api/mod-search.yaml | 44 ++++++++++++++----- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 7d72be869..a0f875a5c 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -1,8 +1,10 @@ package org.folio.search.controller; +import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; +import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.domain.dto.ConsortiumItem; import org.folio.search.domain.dto.ConsortiumItemCollection; @@ -53,6 +55,27 @@ public ResponseEntity getConsortiumHoldings(String return ResponseEntity.ok(instanceService.fetchHoldings(context)); } + @Override + public ResponseEntity getConsortiumHolding(UUID id, String tenantHeader) { + var tenant = verifyAndGetTenant(tenantHeader); + var query = "holdings.id=" + id.toString(); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); + var result = searchService.search(searchRequest); + + if (CollectionUtils.isEmpty(result.getRecords()) + || CollectionUtils.isEmpty(result.getRecords().iterator().next().getHoldings())) { + return ResponseEntity.ok(new ConsortiumHolding()); + } + + var instance = result.getRecords().iterator().next(); + var holding = instance.getHoldings().iterator().next(); + return ResponseEntity.ok(new ConsortiumHolding() + .id(id.toString()) + .tenantId(holding.getTenantId()) + .instanceId(instance.getId()) + ); + } + @Override public ResponseEntity getConsortiumItems(String tenantHeader, String instanceId, String holdingsRecordId, String tenantId, @@ -72,9 +95,9 @@ public ResponseEntity getConsortiumItems(String tenant } @Override - public ResponseEntity getConsortiumItem(String itemId, String tenantHeader, String tenantId) { + public ResponseEntity getConsortiumItem(UUID itemId, String tenantHeader) { var tenant = verifyAndGetTenant(tenantHeader); - var query = "items.id=" + itemId; + var query = "items.id=" + itemId.toString(); var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); var result = searchService.search(searchRequest); @@ -82,13 +105,12 @@ public ResponseEntity getConsortiumItem(String itemId, String te || CollectionUtils.isEmpty(result.getRecords().iterator().next().getItems())) { return ResponseEntity.ok(new ConsortiumItem()); } - var instanceId = result.getRecords().iterator().next().getId(); - var item = result.getRecords().iterator().next().getItems().iterator().next(); - log.info("Item: {}", item); + var instance = result.getRecords().iterator().next(); + var item = instance.getItems().iterator().next(); return ResponseEntity.ok(new ConsortiumItem() - .id(itemId) + .id(itemId.toString()) .tenantId(item.getTenantId()) - .instanceId(instanceId) + .instanceId(instance.getId()) .holdingsRecordId(item.getHoldingsRecordId()) ); } diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index 1449f62b9..77446128e 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -255,15 +255,43 @@ paths: '500': $ref: '#/components/responses/internalServerErrorResponse' - /search/consortium/item: + /search/consortium/holding/{id}: + get: + operationId: getConsortiumHolding + description: Get holding by id (only for consortium environment) + tags: + - search-consortium + parameters: + - name: id + required: true + in: path + schema: + $ref: "#/components/schemas/uuid" + - $ref: '#/components/parameters/x-okapi-tenant-header' + responses: + '200': + description: an instance holding + content: + application/json: + schema: + $ref: '#/components/schemas/consortiumHolding' + '400': + $ref: '#/components/responses/badRequestResponse' + '500': + $ref: '#/components/responses/internalServerErrorResponse' + + /search/consortium/item/{id}: get: operationId: getConsortiumItem description: Get an item (only for consortium environment) tags: - search-consortium parameters: - - $ref: '#/components/parameters/item-id-query-param' - - $ref: '#/components/parameters/tenant-id-query-param' + - name: id + required: true + in: path + schema: + $ref: "#/components/schemas/uuid" - $ref: '#/components/parameters/x-okapi-tenant-header' responses: '200': @@ -877,6 +905,9 @@ components: enum: - asc - desc + uuid: + type: string + format: uuid responses: unprocessableEntityResponse: @@ -1023,13 +1054,6 @@ components: required: false schema: type: string - item-id-query-param: - in: query - name: itemId - description: UUID of the item - required: true - schema: - type: string holdings-id-query-param: in: query name: holdingsRecordId From 6255b2ecde68fd3a62f99643f5c127185e6e756e Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Fri, 5 Apr 2024 17:09:21 +0500 Subject: [PATCH 08/19] MSEARCH-707: add module description for api --- descriptors/ModuleDescriptor-template.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 92e384e60..767ffcca3 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -167,6 +167,18 @@ "user-tenants.collection.get" ] }, + { + "methods": [ + "GET" + ], + "pathPattern": "/search/consortium/holding/{id}", + "permissionsRequired": [ + "consortium-search.items.collection.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] + }, { "methods": [ "GET" From 48ce00c5ddef58b49366b5041343c128a2a8d3ec Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Mon, 29 Apr 2024 14:33:13 +0500 Subject: [PATCH 09/19] msearch-707: adjust poc after marge with master --- descriptors/ModuleDescriptor-template.json | 2 +- .../controller/SearchConsortiumController.java | 12 ++++++------ src/main/resources/swagger.api/mod-search.yaml | 18 +++++++++++------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 3ee07d747..c86cabff3 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -196,7 +196,7 @@ "methods": [ "GET" ], - "pathPattern": "/search/consortium/item", + "pathPattern": "/search/consortium/item/{id}", "permissionsRequired": [ "consortium-search.items.collection.get" ], diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 76f2bbdcb..ef446c4ce 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -1,15 +1,16 @@ package org.folio.search.controller; +import static org.apache.commons.collections4.CollectionUtils.isEmpty; + import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.apache.commons.collections4.CollectionUtils; import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.domain.dto.ConsortiumItem; import org.folio.search.domain.dto.ConsortiumItemCollection; -import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.ConsortiumLocationCollection; +import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.SortOrder; import org.folio.search.exception.RequestValidationException; import org.folio.search.model.service.ConsortiumSearchContext; @@ -65,8 +66,7 @@ public ResponseEntity getConsortiumHolding(UUID id, String te var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); var result = searchService.search(searchRequest); - if (CollectionUtils.isEmpty(result.getRecords()) - || CollectionUtils.isEmpty(result.getRecords().iterator().next().getHoldings())) { + if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getHoldings())) { return ResponseEntity.ok(new ConsortiumHolding()); } @@ -120,10 +120,10 @@ public ResponseEntity getConsortiumItem(UUID itemId, String tena var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); var result = searchService.search(searchRequest); - if (CollectionUtils.isEmpty(result.getRecords()) - || CollectionUtils.isEmpty(result.getRecords().iterator().next().getItems())) { + if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getItems())) { return ResponseEntity.ok(new ConsortiumItem()); } + var instance = result.getRecords().iterator().next(); var item = instance.getItems().iterator().next(); return ResponseEntity.ok(new ConsortiumItem() diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index 127032aa8..c06929dd9 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -95,11 +95,11 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/consortiumHolding' + $ref: 'schemas/entity/consortiumHolding.yaml' '400': - $ref: '#/components/responses/badRequestResponse' + $ref: 'responses/badRequestResponse.yaml' '500': - $ref: '#/components/responses/internalServerErrorResponse' + $ref: 'responses/internalServerErrorResponse.yaml' /search/consortium/item/{id}: get: @@ -120,11 +120,11 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/consortiumItem' + $ref: 'schemas/entity/consortiumItem.yaml' '400': - $ref: '#/components/responses/badRequestResponse' + $ref: 'responses/badRequestResponse.yaml' '500': - $ref: '#/components/responses/internalServerErrorResponse' + $ref: 'responses/internalServerErrorResponse.yaml' /search/index/indices: $ref: 'paths/search-index/search-index-indices.yaml' @@ -174,6 +174,10 @@ paths: /browse/config/{browseType}/{browseOptionId}: $ref: 'paths/browse-config/browse-type-browse-option-id.yaml' - +components: + schemas: + uuid: + type: string + format: uuid From 4a2f4c9a3d2e2fcdff6a3fb8dd0badfe1528bcf4 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Mon, 29 Apr 2024 14:46:34 +0500 Subject: [PATCH 10/19] msearch-707: refactor --- .../resources/swagger.api/mod-search.yaml | 22 ++++--------------- .../parameters/record-id-param.yaml | 7 ++++++ 2 files changed, 11 insertions(+), 18 deletions(-) create mode 100644 src/main/resources/swagger.api/parameters/record-id-param.yaml diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index c06929dd9..4dfa51e40 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -83,12 +83,8 @@ paths: tags: - search-consortium parameters: - - name: id - required: true - in: path - schema: - $ref: "#/components/schemas/uuid" - - $ref: '#/components/parameters/x-okapi-tenant-header' + - $ref: 'parameters/record-id-param.yaml' + - $ref: 'parameters/x-okapi-tenant-header.yaml' responses: '200': description: an instance holding @@ -108,12 +104,8 @@ paths: tags: - search-consortium parameters: - - name: id - required: true - in: path - schema: - $ref: "#/components/schemas/uuid" - - $ref: '#/components/parameters/x-okapi-tenant-header' + - $ref: 'parameters/record-id-param.yaml' + - $ref: 'parameters/x-okapi-tenant-header.yaml' responses: '200': description: an instance item @@ -174,10 +166,4 @@ paths: /browse/config/{browseType}/{browseOptionId}: $ref: 'paths/browse-config/browse-type-browse-option-id.yaml' -components: - schemas: - uuid: - type: string - format: uuid - diff --git a/src/main/resources/swagger.api/parameters/record-id-param.yaml b/src/main/resources/swagger.api/parameters/record-id-param.yaml new file mode 100644 index 000000000..cc6f20222 --- /dev/null +++ b/src/main/resources/swagger.api/parameters/record-id-param.yaml @@ -0,0 +1,7 @@ +name: id +description: The UUID of a record +required: true +in: path +schema: + type: string + format: uuid From bb989b962a86479b7b212a6cfa94b8a6afbd7563 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Mon, 29 Apr 2024 17:41:54 +0500 Subject: [PATCH 11/19] msearch-707: add batch item and holding endpoints --- descriptors/ModuleDescriptor-template.json | 24 +++++++++++++++++ .../SearchConsortiumController.java | 14 ++++++++++ .../resources/swagger.api/mod-search.yaml | 6 +++++ .../parameters/batchHoldingIdsDto.yaml | 9 +++++++ .../parameters/batchItemIdsDto.yaml | 9 +++++++ .../search-consortium-batch-holdings.yaml | 27 +++++++++++++++++++ .../search-consortium-batch-items.yaml | 27 +++++++++++++++++++ 7 files changed, 116 insertions(+) create mode 100644 src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml create mode 100644 src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml create mode 100644 src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml create mode 100644 src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index c86cabff3..2e26b387e 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -203,6 +203,30 @@ "modulePermissions": [ "user-tenants.collection.get" ] + }, + { + "methods": [ + "POST" + ], + "pathPattern": "/search/consortium/batch/items", + "permissionsRequired": [ + "consortium-search.items.collection.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] + }, + { + "methods": [ + "POST" + ], + "pathPattern": "/search/consortium/batch/holdings", + "permissionsRequired": [ + "consortium-search.holdings.collection.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] } ] }, diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index ef446c4ce..8d835203b 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -5,6 +5,8 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.folio.search.domain.dto.BatchHoldingIdsDto; +import org.folio.search.domain.dto.BatchItemIdsDto; import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.domain.dto.ConsortiumItem; @@ -134,6 +136,18 @@ public ResponseEntity getConsortiumItem(UUID itemId, String tena ); } + @Override + public ResponseEntity fetchConsortiumBatchItems(String tenantHeader, + BatchItemIdsDto batchItemIdsDto) { + return SearchConsortiumApi.super.fetchConsortiumBatchItems(tenantHeader, batchItemIdsDto); + } + + @Override + public ResponseEntity fetchConsortiumBatchHoldings(String tenantHeader, + BatchHoldingIdsDto holdingIdsDto) { + return SearchConsortiumApi.super.fetchConsortiumBatchHoldings(tenantHeader, holdingIdsDto); + } + private String verifyAndGetTenant(String tenantHeader) { var centralTenant = consortiumTenantService.getCentralTenant(tenantHeader); if (centralTenant.isEmpty() || !centralTenant.get().equals(tenantHeader)) { diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index 4dfa51e40..543de7a49 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -76,6 +76,12 @@ paths: /search/consortium/locations: $ref: 'paths/search-consortium/search-consortium-locations.yaml' + /search/consortium/batch/items: + $ref: 'paths/search-consortium/search-consortium-batch-items.yaml' + + /search/consortium/batch/holdings: + $ref: 'paths/search-consortium/search-consortium-batch-holdings.yaml' + /search/consortium/holding/{id}: get: operationId: getConsortiumHolding diff --git a/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml b/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml new file mode 100644 index 000000000..41c6414cd --- /dev/null +++ b/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml @@ -0,0 +1,9 @@ +description: Batch Holding IDs DTO +type: object +properties: + id: + description: Holding ID + type: string + format: uuid +required: + - id diff --git a/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml b/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml new file mode 100644 index 000000000..9abdeaa0b --- /dev/null +++ b/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml @@ -0,0 +1,9 @@ +description: Batch Item IDs DTO +type: object +properties: + id: + description: Item ID + type: string + format: uuid +required: + - id diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml new file mode 100644 index 000000000..da8c5aaf5 --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml @@ -0,0 +1,27 @@ +post: + operationId: fetchConsortiumBatchHoldings + summary: Fetch Consolidated Holdings + description: Fetch a list of holdings (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + requestBody: + content: + application/json: + schema: + $ref: '../../parameters/batchHoldingIdsDto.yaml' + responses: + '200': + description: List of holdings + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumHoldingCollection.yaml' + examples: + consortiumItemCollection: + $ref: '../../examples/response/consortiumHoldingsCollectionResponse.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml new file mode 100644 index 000000000..cc2a11d2c --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml @@ -0,0 +1,27 @@ +post: + operationId: fetchConsortiumBatchItems + summary: Fetch Consolidated Items + description: Fetch a list of items (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + requestBody: + content: + application/json: + schema: + $ref: '../../parameters/batchItemIdsDto.yaml' + responses: + '200': + description: List of items + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumItemCollection.yaml' + examples: + consortiumItemCollection: + $ref: '../../examples/response/consortiumItemCollectionResponse.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' From 7c6d97fb1b22134ea1f9b19cd60b81b7fb430af1 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Tue, 30 Apr 2024 12:25:49 +0500 Subject: [PATCH 12/19] msearch-707: add batch item and holding endpoints --- .../SearchConsortiumController.java | 40 ++++++++++++++++++- .../parameters/batchHoldingIdsDto.yaml | 12 +++--- .../parameters/batchItemIdsDto.yaml | 12 +++--- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 8d835203b..1020357eb 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -3,6 +3,7 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.search.domain.dto.BatchHoldingIdsDto; @@ -139,13 +140,48 @@ public ResponseEntity getConsortiumItem(UUID itemId, String tena @Override public ResponseEntity fetchConsortiumBatchItems(String tenantHeader, BatchItemIdsDto batchItemIdsDto) { - return SearchConsortiumApi.super.fetchConsortiumBatchItems(tenantHeader, batchItemIdsDto); + var tenant = verifyAndGetTenant(tenantHeader); + var query = batchItemIdsDto.getIds().stream() + .map(UUID::toString) + .map("items.id = %s"::formatted) + .collect(Collectors.joining(" or ")); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); + var result = searchService.search(searchRequest); + var consortiumItems = result.getRecords().stream() + .map(instance -> { + var item = instance.getItems().iterator().next(); + return new ConsortiumItem() + .id(item.getId()) + .tenantId(item.getTenantId()) + .instanceId(instance.getId()) + .holdingsRecordId(item.getHoldingsRecordId()); + }) + .toList(); + return ResponseEntity + .ok(new ConsortiumItemCollection().items(consortiumItems).totalRecords(result.getTotalRecords())); } @Override public ResponseEntity fetchConsortiumBatchHoldings(String tenantHeader, BatchHoldingIdsDto holdingIdsDto) { - return SearchConsortiumApi.super.fetchConsortiumBatchHoldings(tenantHeader, holdingIdsDto); + var tenant = verifyAndGetTenant(tenantHeader); + var query = holdingIdsDto.getIds().stream() + .map(UUID::toString) + .map("holdings.id = %s"::formatted) + .collect(Collectors.joining(" or ")); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); + var result = searchService.search(searchRequest); + var consortiumHoldings = result.getRecords().stream() + .map(instance -> { + var holding = instance.getHoldings().iterator().next(); + return new ConsortiumHolding() + .id(holding.getId()) + .tenantId(holding.getTenantId()) + .instanceId(instance.getId()); + }) + .toList(); + return ResponseEntity + .ok(new ConsortiumHoldingCollection().holdings(consortiumHoldings).totalRecords(result.getTotalRecords())); } private String verifyAndGetTenant(String tenantHeader) { diff --git a/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml b/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml index 41c6414cd..b98996216 100644 --- a/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml +++ b/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml @@ -1,9 +1,11 @@ description: Batch Holding IDs DTO type: object properties: - id: - description: Holding ID - type: string - format: uuid + ids: + description: Holding IDs + type: array + items: + type: string + format: uuid required: - - id + - ids diff --git a/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml b/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml index 9abdeaa0b..489ad4351 100644 --- a/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml +++ b/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml @@ -1,9 +1,11 @@ description: Batch Item IDs DTO type: object properties: - id: - description: Item ID - type: string - format: uuid + ids: + description: Item IDs + type: array + items: + type: string + format: uuid required: - - id + - ids From e4c402e5f2b36aa36ddda7242d18138d8b6c5178 Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Tue, 30 Apr 2024 15:59:18 +0500 Subject: [PATCH 13/19] msearch-707: add batch item and holding endpoints --- .../folio/search/controller/SearchConsortiumController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 1020357eb..a2da7b775 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -145,8 +145,9 @@ public ResponseEntity fetchConsortiumBatchItems(String .map(UUID::toString) .map("items.id = %s"::formatted) .collect(Collectors.joining(" or ")); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1000, 0, true); var result = searchService.search(searchRequest); + log.info("QUERY for Batch ITEMS: {}, number of found instances: {}", query, result.getRecords()); var consortiumItems = result.getRecords().stream() .map(instance -> { var item = instance.getItems().iterator().next(); @@ -157,6 +158,7 @@ public ResponseEntity fetchConsortiumBatchItems(String .holdingsRecordId(item.getHoldingsRecordId()); }) .toList(); + log.info("ITEMS: {}", consortiumItems); return ResponseEntity .ok(new ConsortiumItemCollection().items(consortiumItems).totalRecords(result.getTotalRecords())); } @@ -169,7 +171,7 @@ public ResponseEntity fetchConsortiumBatchHoldings( .map(UUID::toString) .map("holdings.id = %s"::formatted) .collect(Collectors.joining(" or ")); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1000, 0, true); var result = searchService.search(searchRequest); var consortiumHoldings = result.getRecords().stream() .map(instance -> { From 2a9e79c799eb71447591beab8a537ba390b1e23a Mon Sep 17 00:00:00 2001 From: Mukhiddin Yusupov Date: Wed, 1 May 2024 17:04:43 +0500 Subject: [PATCH 14/19] msearch-707: modify batch endpoints to filter out the desired item/holding from instance --- .../SearchConsortiumController.java | 73 +++++++++++++------ 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index a2da7b775..714b548e4 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -2,6 +2,8 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -65,7 +67,8 @@ public ResponseEntity getConsortiumHoldings(String @Override public ResponseEntity getConsortiumHolding(UUID id, String tenantHeader) { var tenant = verifyAndGetTenant(tenantHeader); - var query = "holdings.id=" + id.toString(); + var holdingId = id.toString(); + var query = "holdings.id=" + holdingId; var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); var result = searchService.search(searchRequest); @@ -74,9 +77,16 @@ public ResponseEntity getConsortiumHolding(UUID id, String te } var instance = result.getRecords().iterator().next(); - var holding = instance.getHoldings().iterator().next(); + var holding = instance.getHoldings().stream() + .filter(hol -> Objects.equals(holdingId, hol.getId())) + .findFirst().orElse(null); + + if (holding == null) { + return ResponseEntity.ok(new ConsortiumHolding()); + } + return ResponseEntity.ok(new ConsortiumHolding() - .id(id.toString()) + .id(holdingId) .tenantId(holding.getTenantId()) .instanceId(instance.getId()) ); @@ -128,7 +138,14 @@ public ResponseEntity getConsortiumItem(UUID itemId, String tena } var instance = result.getRecords().iterator().next(); - var item = instance.getItems().iterator().next(); + var item = instance.getItems().stream() + .filter(it -> Objects.equals(itemId.toString(), it.getId())) + .findFirst().orElse(null); + + if (item == null) { + return ResponseEntity.ok(new ConsortiumItem()); + } + return ResponseEntity.ok(new ConsortiumItem() .id(itemId.toString()) .tenantId(item.getTenantId()) @@ -140,25 +157,34 @@ public ResponseEntity getConsortiumItem(UUID itemId, String tena @Override public ResponseEntity fetchConsortiumBatchItems(String tenantHeader, BatchItemIdsDto batchItemIdsDto) { + if (batchItemIdsDto.getIds().isEmpty()) { + return ResponseEntity + .ok(new ConsortiumItemCollection()); + } + var tenant = verifyAndGetTenant(tenantHeader); + var itemIds = batchItemIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); var query = batchItemIdsDto.getIds().stream() .map(UUID::toString) .map("items.id = %s"::formatted) .collect(Collectors.joining(" or ")); var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1000, 0, true); var result = searchService.search(searchRequest); - log.info("QUERY for Batch ITEMS: {}, number of found instances: {}", query, result.getRecords()); var consortiumItems = result.getRecords().stream() - .map(instance -> { - var item = instance.getItems().iterator().next(); - return new ConsortiumItem() - .id(item.getId()) - .tenantId(item.getTenantId()) - .instanceId(instance.getId()) - .holdingsRecordId(item.getHoldingsRecordId()); - }) + .map(instance -> + instance.getItems().stream() + .filter(item -> itemIds.contains(item.getId())) + .findFirst() + .map(item -> new ConsortiumItem() + .id(item.getId()) + .tenantId(item.getTenantId()) + .instanceId(instance.getId()) + .holdingsRecordId(item.getHoldingsRecordId())) + ) + .filter(Optional::isPresent) + .map(Optional::get) .toList(); - log.info("ITEMS: {}", consortiumItems); + return ResponseEntity .ok(new ConsortiumItemCollection().items(consortiumItems).totalRecords(result.getTotalRecords())); } @@ -167,6 +193,7 @@ public ResponseEntity fetchConsortiumBatchItems(String public ResponseEntity fetchConsortiumBatchHoldings(String tenantHeader, BatchHoldingIdsDto holdingIdsDto) { var tenant = verifyAndGetTenant(tenantHeader); + var holdingIds = holdingIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); var query = holdingIdsDto.getIds().stream() .map(UUID::toString) .map("holdings.id = %s"::formatted) @@ -174,13 +201,17 @@ public ResponseEntity fetchConsortiumBatchHoldings( var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1000, 0, true); var result = searchService.search(searchRequest); var consortiumHoldings = result.getRecords().stream() - .map(instance -> { - var holding = instance.getHoldings().iterator().next(); - return new ConsortiumHolding() - .id(holding.getId()) - .tenantId(holding.getTenantId()) - .instanceId(instance.getId()); - }) + .map(instance -> + instance.getHoldings().stream() + .filter(holding -> holdingIds.contains(holding.getId())) + .findFirst() + .map(holding -> new ConsortiumHolding() + .id(holding.getId()) + .tenantId(holding.getTenantId()) + .instanceId(instance.getId())) + ) + .filter(Optional::isPresent) + .map(Optional::get) .toList(); return ResponseEntity .ok(new ConsortiumHoldingCollection().holdings(consortiumHoldings).totalRecords(result.getTotalRecords())); From c5f5ca5600f0d07fa4db4329b80441b248c4e8b4 Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Wed, 22 May 2024 17:51:56 +0200 Subject: [PATCH 15/19] feat(consortium-search): Implement consolidated items/holdings search - Implement endpoints by id - Implement batch endpoints by ids Implements: MSEARCH-759 --- NEWS.md | 1 + .../SearchConsortiumController.java | 142 +++------- .../converter/ConsortiumHoldingMapper.java | 23 ++ .../converter/ConsortiumItemMapper.java | 19 ++ .../search/cql/CqlSearchQueryConverter.java | 9 +- .../model/service/CqlSearchRequest.java | 17 +- .../folio/search/service/SearchService.java | 8 +- .../ConsortiumInstanceSearchService.java | 102 +++++++ .../org/folio/search/utils/SearchUtils.java | 7 + .../resources/swagger.api/mod-search.yaml | 40 +-- .../parameters/batchHoldingIdsDto.yaml | 11 - ...{batchItemIdsDto.yaml => batchIdsDto.yaml} | 4 +- .../search-consortium-batch-holdings.yaml | 2 +- .../search-consortium-batch-items.yaml | 2 +- .../search-consortium-holding.yaml | 19 ++ .../search-consortium-item.yaml | 19 ++ .../ConsortiumSearchHoldingsIT.java | 63 +++++ .../controller/ConsortiumSearchItemsIT.java | 50 ++++ .../search/service/SearchServiceTest.java | 6 +- .../ConsortiumInstanceSearchServiceTest.java | 266 ++++++++++++++++++ .../search/support/base/ApiEndpoints.java | 16 ++ .../base/BaseConsortiumIntegrationTest.java | 19 ++ .../instance-full-response.json | 3 + 23 files changed, 680 insertions(+), 168 deletions(-) create mode 100644 src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java create mode 100644 src/main/java/org/folio/search/converter/ConsortiumItemMapper.java create mode 100644 src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java delete mode 100644 src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml rename src/main/resources/swagger.api/parameters/{batchItemIdsDto.yaml => batchIdsDto.yaml} (66%) create mode 100644 src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml create mode 100644 src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml create mode 100644 src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java diff --git a/NEWS.md b/NEWS.md index a8886a20c..b226b16ab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,7 @@ * Facets: add support for instance classification facets ([MSEARCH-606](https://issues.folio.org/browse/MSEARCH-606)) * Return Unified List of Inventory Locations in a Consortium ([MSEARCH-681](https://folio-org.atlassian.net/browse/MSEARCH-681)) * Remove ability to match on LCCN searches without a prefix ([MSEARCH-752](https://folio-org.atlassian.net/browse/MSEARCH-752)) +* Search consolidated items/holdings data in consortium ([MSEARCH-759](https://folio-org.atlassian.net/browse/MSEARCH-759)) ### Bug fixes * Do not delete kafka topics if collection topic is enabled ([MSEARCH-725](https://folio-org.atlassian.net/browse/MSEARCH-725)) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 714b548e4..6b35429a9 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -1,15 +1,10 @@ package org.folio.search.controller; -import static org.apache.commons.collections4.CollectionUtils.isEmpty; - -import java.util.Objects; -import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.folio.search.domain.dto.BatchHoldingIdsDto; -import org.folio.search.domain.dto.BatchItemIdsDto; +import org.folio.search.domain.dto.BatchIdsDto; import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.domain.dto.ConsortiumItem; @@ -22,7 +17,7 @@ import org.folio.search.model.service.CqlSearchRequest; import org.folio.search.model.types.ResourceType; import org.folio.search.rest.resource.SearchConsortiumApi; -import org.folio.search.service.SearchService; +import org.folio.search.service.consortium.ConsortiumInstanceSearchService; import org.folio.search.service.consortium.ConsortiumInstanceService; import org.folio.search.service.consortium.ConsortiumLocationService; import org.folio.search.service.consortium.ConsortiumTenantService; @@ -45,7 +40,7 @@ public class SearchConsortiumController implements SearchConsortiumApi { private final ConsortiumTenantService consortiumTenantService; private final ConsortiumInstanceService instanceService; private final ConsortiumLocationService locationService; - private final SearchService searchService; + private final ConsortiumInstanceSearchService searchService; @Override public ResponseEntity getConsortiumHoldings(String tenantHeader, String instanceId, @@ -64,34 +59,6 @@ public ResponseEntity getConsortiumHoldings(String return ResponseEntity.ok(instanceService.fetchHoldings(context)); } - @Override - public ResponseEntity getConsortiumHolding(UUID id, String tenantHeader) { - var tenant = verifyAndGetTenant(tenantHeader); - var holdingId = id.toString(); - var query = "holdings.id=" + holdingId; - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); - var result = searchService.search(searchRequest); - - if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getHoldings())) { - return ResponseEntity.ok(new ConsortiumHolding()); - } - - var instance = result.getRecords().iterator().next(); - var holding = instance.getHoldings().stream() - .filter(hol -> Objects.equals(holdingId, hol.getId())) - .findFirst().orElse(null); - - if (holding == null) { - return ResponseEntity.ok(new ConsortiumHolding()); - } - - return ResponseEntity.ok(new ConsortiumHolding() - .id(holdingId) - .tenantId(holding.getTenantId()) - .instanceId(instance.getId()) - ); - } - @Override public ResponseEntity getConsortiumItems(String tenantHeader, String instanceId, String holdingsRecordId, String tenantId, @@ -126,95 +93,60 @@ public ResponseEntity getConsortiumLocations(Strin .totalRecords(result.getTotalRecords())); } + @Override + public ResponseEntity getConsortiumHolding(UUID id, String tenantHeader) { + var tenant = verifyAndGetTenant(tenantHeader); + var holdingId = id.toString(); + var query = "holdings.id=" + holdingId; + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true, false, true); + + var result = searchService.getConsortiumHolding(id.toString(), searchRequest); + return ResponseEntity.ok(result); + } + @Override public ResponseEntity getConsortiumItem(UUID itemId, String tenantHeader) { var tenant = verifyAndGetTenant(tenantHeader); var query = "items.id=" + itemId.toString(); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true); - var result = searchService.search(searchRequest); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true, false, true); - if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getItems())) { - return ResponseEntity.ok(new ConsortiumItem()); - } - - var instance = result.getRecords().iterator().next(); - var item = instance.getItems().stream() - .filter(it -> Objects.equals(itemId.toString(), it.getId())) - .findFirst().orElse(null); + var result = searchService.getConsortiumItem(itemId.toString(), searchRequest); + return ResponseEntity.ok(result); + } - if (item == null) { - return ResponseEntity.ok(new ConsortiumItem()); - } + @Override + public ResponseEntity fetchConsortiumBatchHoldings(String tenantHeader, + BatchIdsDto batchIdsDto) { + var tenant = verifyAndGetTenant(tenantHeader); + var holdingIds = batchIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); + var query = batchIdsDto.getIds().stream() + .map(UUID::toString) + .map("holdings.id = %s"::formatted) + .collect(Collectors.joining(" or ")); + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, holdingIds.size(), 0, true, false, true); - return ResponseEntity.ok(new ConsortiumItem() - .id(itemId.toString()) - .tenantId(item.getTenantId()) - .instanceId(instance.getId()) - .holdingsRecordId(item.getHoldingsRecordId()) - ); + var result = searchService.fetchConsortiumBatchHoldings(searchRequest, holdingIds); + return ResponseEntity.ok(result); } @Override public ResponseEntity fetchConsortiumBatchItems(String tenantHeader, - BatchItemIdsDto batchItemIdsDto) { - if (batchItemIdsDto.getIds().isEmpty()) { + BatchIdsDto batchIdsDto) { + if (batchIdsDto.getIds().isEmpty()) { return ResponseEntity .ok(new ConsortiumItemCollection()); } var tenant = verifyAndGetTenant(tenantHeader); - var itemIds = batchItemIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); - var query = batchItemIdsDto.getIds().stream() + var itemIds = batchIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); + var query = batchIdsDto.getIds().stream() .map(UUID::toString) .map("items.id = %s"::formatted) .collect(Collectors.joining(" or ")); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1000, 0, true); - var result = searchService.search(searchRequest); - var consortiumItems = result.getRecords().stream() - .map(instance -> - instance.getItems().stream() - .filter(item -> itemIds.contains(item.getId())) - .findFirst() - .map(item -> new ConsortiumItem() - .id(item.getId()) - .tenantId(item.getTenantId()) - .instanceId(instance.getId()) - .holdingsRecordId(item.getHoldingsRecordId())) - ) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(); - - return ResponseEntity - .ok(new ConsortiumItemCollection().items(consortiumItems).totalRecords(result.getTotalRecords())); - } + var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, itemIds.size(), 0, true, false, true); - @Override - public ResponseEntity fetchConsortiumBatchHoldings(String tenantHeader, - BatchHoldingIdsDto holdingIdsDto) { - var tenant = verifyAndGetTenant(tenantHeader); - var holdingIds = holdingIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); - var query = holdingIdsDto.getIds().stream() - .map(UUID::toString) - .map("holdings.id = %s"::formatted) - .collect(Collectors.joining(" or ")); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1000, 0, true); - var result = searchService.search(searchRequest); - var consortiumHoldings = result.getRecords().stream() - .map(instance -> - instance.getHoldings().stream() - .filter(holding -> holdingIds.contains(holding.getId())) - .findFirst() - .map(holding -> new ConsortiumHolding() - .id(holding.getId()) - .tenantId(holding.getTenantId()) - .instanceId(instance.getId())) - ) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(); - return ResponseEntity - .ok(new ConsortiumHoldingCollection().holdings(consortiumHoldings).totalRecords(result.getTotalRecords())); + var result = searchService.fetchConsortiumBatchItems(searchRequest, itemIds); + return ResponseEntity.ok(result); } private String verifyAndGetTenant(String tenantHeader) { diff --git a/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java b/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java new file mode 100644 index 000000000..e8ea62afc --- /dev/null +++ b/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java @@ -0,0 +1,23 @@ +package org.folio.search.converter; + +import org.folio.search.domain.dto.ConsortiumHolding; +import org.folio.search.domain.dto.Holding; +import org.springframework.stereotype.Component; + +@Component +public class ConsortiumHoldingMapper { + + public ConsortiumHolding map(String instanceId, Holding holding) { + return new ConsortiumHolding() + .id(holding.getId()) + .hrid(holding.getHrid()) + .tenantId(holding.getTenantId()) + .instanceId(instanceId) + .callNumberPrefix(holding.getCallNumberPrefix()) + .callNumber(holding.getCallNumber()) + .callNumberSuffix(holding.getCallNumberSuffix()) + .copyNumber(holding.getCopyNumber()) + .permanentLocationId(holding.getPermanentLocationId()) + .discoverySuppress(holding.getDiscoverySuppress() != null && holding.getDiscoverySuppress()); + } +} diff --git a/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java b/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java new file mode 100644 index 000000000..8603afee6 --- /dev/null +++ b/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java @@ -0,0 +1,19 @@ +package org.folio.search.converter; + +import org.folio.search.domain.dto.ConsortiumItem; +import org.folio.search.domain.dto.Item; +import org.springframework.stereotype.Component; + +@Component +public class ConsortiumItemMapper { + + public ConsortiumItem map(String instanceId, Item item) { + return new ConsortiumItem() + .id(item.getId()) + .hrid(item.getHrid()) + .tenantId(item.getTenantId()) + .instanceId(instanceId) + .holdingsRecordId(item.getHoldingsRecordId()) + .barcode(item.getBarcode()); + } +} diff --git a/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java b/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java index cf573564f..13f90c9e6 100644 --- a/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java +++ b/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java @@ -68,9 +68,16 @@ public SearchSourceBuilder convert(String query, String resource) { * @return search source as {@link SearchSourceBuilder} object with query and sorting conditions */ public SearchSourceBuilder convertForConsortia(String query, String resource) { + return convertForConsortia(query, resource, false); + } + + public SearchSourceBuilder convertForConsortia(String query, String resource, boolean consortiumConsolidated) { var sourceBuilder = convert(query, resource); - var queryBuilder = consortiumSearchHelper.filterQueryForActiveAffiliation(sourceBuilder.query(), resource); + if (consortiumConsolidated) { + return sourceBuilder; + } + var queryBuilder = consortiumSearchHelper.filterQueryForActiveAffiliation(sourceBuilder.query(), resource); return sourceBuilder.query(queryBuilder); } diff --git a/src/main/java/org/folio/search/model/service/CqlSearchRequest.java b/src/main/java/org/folio/search/model/service/CqlSearchRequest.java index b89258e0d..a5856e87d 100644 --- a/src/main/java/org/folio/search/model/service/CqlSearchRequest.java +++ b/src/main/java/org/folio/search/model/service/CqlSearchRequest.java @@ -53,6 +53,11 @@ public class CqlSearchRequest implements ResourceRequest { */ private final Boolean includeNumberOfTitles; + /** + * Doesn't affect non-consortium. true means include all records, false means filter for active affiliation. + */ + private final Boolean consortiumConsolidated; + /** * Creates {@link CqlSearchRequest} object for given variables. * @@ -64,14 +69,22 @@ public class CqlSearchRequest implements ResourceRequest { * @param expandAll - whether to return only response properties or entire record * @param - generic type for {@link CqlSearchRequest} object. * @param includeNumberOfTitles - indicates whether the number of titles should be counted. + * @param consortiumConsolidated - indicates whether to return consortium consolidated records. * @return created {@link CqlSearchRequest} object */ public static CqlSearchRequest of(Class resourceClass, String tenantId, String query, Integer limit, Integer offset, Boolean expandAll, - Boolean includeNumberOfTitles) { + Boolean includeNumberOfTitles, Boolean consortiumConsolidated) { var resource = SearchUtils.getResourceName(resourceClass); return new CqlSearchRequest<>(resource, resourceClass, tenantId, query, limit, offset, expandAll, - includeNumberOfTitles); + includeNumberOfTitles, consortiumConsolidated); + } + + public static CqlSearchRequest of(Class resourceClass, String tenantId, String query, + Integer limit, Integer offset, Boolean expandAll, + Boolean includeNumberOfTitles) { + return CqlSearchRequest.of(resourceClass, tenantId, query, limit, offset, expandAll, + includeNumberOfTitles, false); } public static CqlSearchRequest of(Class resourceClass, String tenantId, String query, diff --git a/src/main/java/org/folio/search/service/SearchService.java b/src/main/java/org/folio/search/service/SearchService.java index 5bf28e1a7..962c258b0 100644 --- a/src/main/java/org/folio/search/service/SearchService.java +++ b/src/main/java/org/folio/search/service/SearchService.java @@ -3,6 +3,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.commons.lang3.BooleanUtils.isFalse; import static org.folio.search.model.types.ResponseGroupType.SEARCH; +import static org.folio.search.utils.SearchUtils.buildPreferenceKey; import java.util.List; import java.util.Map; @@ -54,7 +55,8 @@ public SearchResult search(CqlSearchRequest request) { } var resource = request.getResource(); var requestTimeout = searchQueryConfiguration.getRequestTimeout(); - var queryBuilder = cqlSearchQueryConverter.convertForConsortia(request.getQuery(), resource) + var queryBuilder = cqlSearchQueryConverter.convertForConsortia(request.getQuery(), resource, + request.getConsortiumConsolidated()) .from(request.getOffset()) .size(request.getLimit()) .trackTotalHits(true) @@ -76,10 +78,6 @@ public SearchResult search(CqlSearchRequest request) { return searchResult; } - private String buildPreferenceKey(String tenantId, String resource, String query) { - return tenantId + "-" + resource + "-" + query; - } - private void searchResultPostProcessing(Class resourceClass, boolean includeNumberOfTitles, SearchResult searchResult) { if (Objects.isNull(resourceClass)) { diff --git a/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java new file mode 100644 index 000000000..5eae42e54 --- /dev/null +++ b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java @@ -0,0 +1,102 @@ +package org.folio.search.service.consortium; + +import static org.apache.commons.collections4.CollectionUtils.isEmpty; + +import java.util.Objects; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.search.converter.ConsortiumHoldingMapper; +import org.folio.search.converter.ConsortiumItemMapper; +import org.folio.search.domain.dto.ConsortiumHolding; +import org.folio.search.domain.dto.ConsortiumHoldingCollection; +import org.folio.search.domain.dto.ConsortiumItem; +import org.folio.search.domain.dto.ConsortiumItemCollection; +import org.folio.search.domain.dto.Instance; +import org.folio.search.model.service.CqlSearchRequest; +import org.folio.search.service.SearchService; +import org.folio.spring.FolioExecutionContext; +import org.springframework.stereotype.Service; + +/** + * Class designed to be executed only in scope of consortium central tenant id. + * So, it can be expected to always have central tenant id in {@link FolioExecutionContext}. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class ConsortiumInstanceSearchService { + + private final ConsortiumHoldingMapper holdingMapper; + private final ConsortiumItemMapper itemMapper; + private final SearchService searchService; + + public ConsortiumHolding getConsortiumHolding(String id, CqlSearchRequest searchRequest) { + var result = searchService.search(searchRequest); + + if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getHoldings())) { + return new ConsortiumHolding(); + } + + var instance = result.getRecords().iterator().next(); + var holding = instance.getHoldings().stream() + .filter(hol -> Objects.equals(id, hol.getId())) + .findFirst().orElse(null); + + if (holding == null) { + return new ConsortiumHolding(); + } + + return holdingMapper.map(instance.getId(), holding); + } + + public ConsortiumItem getConsortiumItem(String id, CqlSearchRequest searchRequest) { + var result = searchService.search(searchRequest); + + if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getItems())) { + return new ConsortiumItem(); + } + + var instance = result.getRecords().iterator().next(); + var item = instance.getItems().stream() + .filter(it -> Objects.equals(id, it.getId())) + .findFirst().orElse(null); + + if (item == null) { + return new ConsortiumItem(); + } + + return itemMapper.map(instance.getId(), item); + } + + public ConsortiumHoldingCollection fetchConsortiumBatchHoldings(CqlSearchRequest searchRequest, + Set ids) { + var result = searchService.search(searchRequest); + var consortiumHoldings = result.getRecords().stream() + .flatMap(instance -> + instance.getHoldings().stream() + .filter(holding -> ids.contains(holding.getId())) + .map(holding -> holdingMapper.map(instance.getId(), holding)) + ) + .toList(); + return new ConsortiumHoldingCollection() + .holdings(consortiumHoldings) + .totalRecords(consortiumHoldings.size()); + } + + public ConsortiumItemCollection fetchConsortiumBatchItems(CqlSearchRequest searchRequest, + Set ids) { + var result = searchService.search(searchRequest); + var consortiumItems = result.getRecords().stream() + .flatMap(instance -> + instance.getItems().stream() + .filter(item -> ids.contains(item.getId())) + .map(item -> itemMapper.map(instance.getId(), item)) + ) + .toList(); + + return new ConsortiumItemCollection() + .items(consortiumItems) + .totalRecords(consortiumItems.size()); + } +} diff --git a/src/main/java/org/folio/search/utils/SearchUtils.java b/src/main/java/org/folio/search/utils/SearchUtils.java index ebfbcaf48..2435e17e8 100644 --- a/src/main/java/org/folio/search/utils/SearchUtils.java +++ b/src/main/java/org/folio/search/utils/SearchUtils.java @@ -360,6 +360,13 @@ public static String normalizeToAlphaNumeric(String value) { .orElse(null); } + /** + * Build preference string to address similar requests to the same shard. + * */ + public static String buildPreferenceKey(String tenantId, String resource, String query) { + return tenantId + "-" + resource + "-" + query; + } + private static Object getMultilangValueObject(Object value) { return value instanceof MultilangValue v ? v.getMultilangValues() : value; } diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index 543de7a49..5a158fef0 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -83,46 +83,10 @@ paths: $ref: 'paths/search-consortium/search-consortium-batch-holdings.yaml' /search/consortium/holding/{id}: - get: - operationId: getConsortiumHolding - description: Get holding by id (only for consortium environment) - tags: - - search-consortium - parameters: - - $ref: 'parameters/record-id-param.yaml' - - $ref: 'parameters/x-okapi-tenant-header.yaml' - responses: - '200': - description: an instance holding - content: - application/json: - schema: - $ref: 'schemas/entity/consortiumHolding.yaml' - '400': - $ref: 'responses/badRequestResponse.yaml' - '500': - $ref: 'responses/internalServerErrorResponse.yaml' + $ref: 'paths/search-consortium/search-consortium-holding.yaml' /search/consortium/item/{id}: - get: - operationId: getConsortiumItem - description: Get an item (only for consortium environment) - tags: - - search-consortium - parameters: - - $ref: 'parameters/record-id-param.yaml' - - $ref: 'parameters/x-okapi-tenant-header.yaml' - responses: - '200': - description: an instance item - content: - application/json: - schema: - $ref: 'schemas/entity/consortiumItem.yaml' - '400': - $ref: 'responses/badRequestResponse.yaml' - '500': - $ref: 'responses/internalServerErrorResponse.yaml' + $ref: 'paths/search-consortium/search-consortium-item.yaml' /search/index/indices: $ref: 'paths/search-index/search-index-indices.yaml' diff --git a/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml b/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml deleted file mode 100644 index b98996216..000000000 --- a/src/main/resources/swagger.api/parameters/batchHoldingIdsDto.yaml +++ /dev/null @@ -1,11 +0,0 @@ -description: Batch Holding IDs DTO -type: object -properties: - ids: - description: Holding IDs - type: array - items: - type: string - format: uuid -required: - - ids diff --git a/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml b/src/main/resources/swagger.api/parameters/batchIdsDto.yaml similarity index 66% rename from src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml rename to src/main/resources/swagger.api/parameters/batchIdsDto.yaml index 489ad4351..367ec45b8 100644 --- a/src/main/resources/swagger.api/parameters/batchItemIdsDto.yaml +++ b/src/main/resources/swagger.api/parameters/batchIdsDto.yaml @@ -1,8 +1,8 @@ -description: Batch Item IDs DTO +description: Batch IDs DTO type: object properties: ids: - description: Item IDs + description: Entity IDs type: array items: type: string diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml index da8c5aaf5..c60807ebf 100644 --- a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml @@ -10,7 +10,7 @@ post: content: application/json: schema: - $ref: '../../parameters/batchHoldingIdsDto.yaml' + $ref: '../../parameters/batchIdsDto.yaml' responses: '200': description: List of holdings diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml index cc2a11d2c..0f2dff213 100644 --- a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml @@ -10,7 +10,7 @@ post: content: application/json: schema: - $ref: '../../parameters/batchItemIdsDto.yaml' + $ref: '../../parameters/batchIdsDto.yaml' responses: '200': description: List of items diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml new file mode 100644 index 000000000..00af95cc3 --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml @@ -0,0 +1,19 @@ +get: + operationId: getConsortiumHolding + description: Get holding by id (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/record-id-param.yaml' + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + responses: + '200': + description: an instance holding + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumHolding.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' \ No newline at end of file diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml new file mode 100644 index 000000000..5d1b83b42 --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml @@ -0,0 +1,19 @@ +get: + operationId: getConsortiumItem + description: Get an item (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/record-id-param.yaml' + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + responses: + '200': + description: an instance item + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumItem.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' \ No newline at end of file diff --git a/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java b/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java index ef50d933e..288143b14 100644 --- a/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java +++ b/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java @@ -5,6 +5,8 @@ import static org.folio.search.model.Pair.pair; import static org.folio.search.sample.SampleInstances.getSemanticWeb; import static org.folio.search.sample.SampleInstances.getSemanticWebId; +import static org.folio.search.support.base.ApiEndpoints.consortiumBatchHoldingsSearchPath; +import static org.folio.search.support.base.ApiEndpoints.consortiumHoldingSearchPath; import static org.folio.search.support.base.ApiEndpoints.consortiumHoldingsSearchPath; import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; @@ -14,7 +16,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Arrays; import java.util.List; +import java.util.UUID; +import org.folio.search.domain.dto.BatchIdsDto; import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.model.Pair; @@ -91,6 +96,51 @@ void tryGetConsortiumHoldings_returns400_whenOrderBySpecifiedWithoutAnyFilters() .andExpect(jsonPath("$.errors[0].code", is("validation_error"))); } + @Test + void doGetConsortiumHolding_returns200AndRecord() { + var holdings = getExpectedConsolidatedHoldings(); + var expectedHolding = holdings[0]; + var result = doGet(consortiumHoldingSearchPath(expectedHolding.getId()), CENTRAL_TENANT_ID); + var actual = parseResponse(result, ConsortiumHolding.class); + + assertThat(actual) + .isEqualTo(expectedHolding); + } + + @Test + void tryGetConsortiumHolding_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryGet(consortiumHoldingSearchPath(UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + + @Test + void doGetConsortiumBatchHoldings_returns200AndRecords() { + var holdings = getExpectedConsolidatedHoldings(); + var request = new BatchIdsDto() + .ids(Arrays.stream(holdings).map(ConsortiumHolding::getId).map(UUID::fromString).toList()); + var result = doPost(consortiumBatchHoldingsSearchPath(), CENTRAL_TENANT_ID, request); + var actual = parseResponse(result, ConsortiumHoldingCollection.class); + + assertThat(actual.getTotalRecords()).isEqualTo(3); + assertThat(actual.getHoldings()).containsExactlyInAnyOrder(holdings); + } + + @Test + void tryGetConsortiumBatchHoldings_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryPost(consortiumBatchHoldingsSearchPath(), new BatchIdsDto().ids(List.of(UUID.randomUUID()))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + private ConsortiumHolding[] getExpectedHoldings() { var instance = getSemanticWeb(); return instance.getHoldings().stream() @@ -107,4 +157,17 @@ private ConsortiumHolding[] getExpectedHoldings() { .discoverySuppress(holding.getDiscoverySuppress() != null && holding.getDiscoverySuppress())) .toArray(ConsortiumHolding[]::new); } + + private ConsortiumHolding[] getExpectedConsolidatedHoldings() { + var instance = getSemanticWeb(); + return instance.getHoldings().stream() + .map(holding -> new ConsortiumHolding() + .id(holding.getId()) + .hrid(holding.getHrid()) + .tenantId(MEMBER_TENANT_ID) + .instanceId(instance.getId()) + .permanentLocationId(holding.getPermanentLocationId()) + .discoverySuppress(holding.getDiscoverySuppress() != null && holding.getDiscoverySuppress())) + .toArray(ConsortiumHolding[]::new); + } } diff --git a/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java b/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java index ac20c4690..40668512d 100644 --- a/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java +++ b/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java @@ -5,6 +5,8 @@ import static org.folio.search.model.Pair.pair; import static org.folio.search.sample.SampleInstances.getSemanticWeb; import static org.folio.search.sample.SampleInstances.getSemanticWebId; +import static org.folio.search.support.base.ApiEndpoints.consortiumBatchItemsSearchPath; +import static org.folio.search.support.base.ApiEndpoints.consortiumItemSearchPath; import static org.folio.search.support.base.ApiEndpoints.consortiumItemsSearchPath; import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; @@ -14,7 +16,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Arrays; import java.util.List; +import java.util.UUID; +import org.folio.search.domain.dto.BatchIdsDto; import org.folio.search.domain.dto.ConsortiumItem; import org.folio.search.domain.dto.ConsortiumItemCollection; import org.folio.search.model.Pair; @@ -95,6 +100,51 @@ void tryGetConsortiumItems_returns400_whenInstanceIdIsNotSpecified() throws Exce .andExpect(jsonPath("$.errors[0].code", is("validation_error"))); } + @Test + void doGetConsortiumItem_returns200AndRecords() { + var items = getExpectedItems(); + var expectedItem = items[0]; + var result = doGet(consortiumItemSearchPath(expectedItem.getId()), CENTRAL_TENANT_ID); + var actual = parseResponse(result, ConsortiumItem.class); + + assertThat(actual) + .isEqualTo(expectedItem); + } + + @Test + void tryGetConsortiumItem_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryGet(consortiumItemSearchPath(UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + + @Test + void doGetConsortiumBatchItems_returns200AndRecords() { + var items = getExpectedItems(); + var request = new BatchIdsDto() + .ids(Arrays.stream(items).map(ConsortiumItem::getId).map(UUID::fromString).toList()); + var result = doPost(consortiumBatchItemsSearchPath(), CENTRAL_TENANT_ID, request); + var actual = parseResponse(result, ConsortiumItemCollection.class); + + assertThat(actual.getTotalRecords()).isEqualTo(3); + assertThat(actual.getItems()).containsExactlyInAnyOrder(items); + } + + @Test + void tryGetConsortiumBatchItems_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryPost(consortiumBatchItemsSearchPath(), new BatchIdsDto().ids(List.of(UUID.randomUUID()))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + private ConsortiumItem[] getExpectedItems() { var instance = getSemanticWeb(); return instance.getItems().stream() diff --git a/src/test/java/org/folio/search/service/SearchServiceTest.java b/src/test/java/org/folio/search/service/SearchServiceTest.java index eef05a6fc..badc7c741 100644 --- a/src/test/java/org/folio/search/service/SearchServiceTest.java +++ b/src/test/java/org/folio/search/service/SearchServiceTest.java @@ -73,7 +73,8 @@ void search_positive() { var expectedSearchResult = searchResult(TestResource.of(RESOURCE_ID)); when(searchFieldProvider.getSourceFields(RESOURCE_NAME, SEARCH)).thenReturn(new String[] {"field1", "field2"}); - when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME)).thenReturn(searchSourceBuilder); + when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME, false)) + .thenReturn(searchSourceBuilder); when(searchRepository.search(eq(searchRequest), eq(expectedSourceBuilder), anyString())).thenReturn(searchResponse); when(documentConverter.convertToSearchResult(searchResponse, TestResource.class)) .thenReturn(expectedSearchResult); @@ -100,7 +101,8 @@ void search_positive_withExpandAll() { .trackTotalHits(true).timeout(new TimeValue(1000, MILLISECONDS)); var expectedSearchResult = searchResult(TestResource.of(RESOURCE_ID)); - when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME)).thenReturn(searchSourceBuilder); + when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME, false)) + .thenReturn(searchSourceBuilder); when(searchRepository.search(eq(searchRequest), eq(expectedSourceBuilder), anyString())).thenReturn(searchResponse); when(documentConverter.convertToSearchResult(searchResponse, TestResource.class)) .thenReturn(expectedSearchResult); diff --git a/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java b/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java new file mode 100644 index 000000000..06d966ff9 --- /dev/null +++ b/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java @@ -0,0 +1,266 @@ +package org.folio.search.service.consortium; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; +import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import org.folio.search.converter.ConsortiumHoldingMapper; +import org.folio.search.converter.ConsortiumItemMapper; +import org.folio.search.domain.dto.ConsortiumHolding; +import org.folio.search.domain.dto.ConsortiumHoldingCollection; +import org.folio.search.domain.dto.ConsortiumItem; +import org.folio.search.domain.dto.ConsortiumItemCollection; +import org.folio.search.domain.dto.Holding; +import org.folio.search.domain.dto.Instance; +import org.folio.search.domain.dto.Item; +import org.folio.search.model.SearchResult; +import org.folio.search.model.service.CqlSearchRequest; +import org.folio.search.service.SearchService; +import org.folio.spring.testing.type.UnitTest; +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@UnitTest +@ExtendWith(MockitoExtension.class) +class ConsortiumInstanceSearchServiceTest { + + private @Mock ConsortiumHoldingMapper holdingMapper; + private @Mock ConsortiumItemMapper itemMapper; + private @Mock SearchService searchService; + private @InjectMocks ConsortiumInstanceSearchService service; + + @BeforeEach + void setUpMocks() { + lenient().doAnswer(invocationOnMock -> { + var instanceId = (String) invocationOnMock.getArgument(0); + var holding = (Holding) invocationOnMock.getArgument(1); + return new ConsortiumHolding() + .id(holding.getId()) + .instanceId(instanceId) + .tenantId(holding.getTenantId()); + }).when(holdingMapper).map(anyString(), any()); + lenient().doAnswer(invocationOnMock -> { + var instanceId = (String) invocationOnMock.getArgument(0); + var item = (Item) invocationOnMock.getArgument(1); + return new ConsortiumItem() + .id(item.getId()) + .instanceId(instanceId) + .tenantId(item.getTenantId()) + .holdingsRecordId(item.getHoldingsRecordId()); + }).when(itemMapper).map(anyString(), any()); + } + + @Test + void getConsortiumHolding_positive() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var holdings = List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), holding(id, MEMBER_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).holdings(holdings))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result.getId()).isEqualTo(id); + assertThat(result.getTenantId()).isEqualTo(MEMBER_TENANT_ID); + assertThat(result.getInstanceId()).isEqualTo(instanceId); + } + + @Test + void getConsortiumHolding_positive_noRecords() { + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var searchResult = SearchResult.of(0, Collections.emptyList()); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result).isEqualTo(new ConsortiumHolding()); + } + + @Test + void getConsortiumHolding_positive_noHoldings() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var holdings = Collections.emptyList(); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).holdings(holdings))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result).isEqualTo(new ConsortiumHolding()); + } + + @Test + void getConsortiumHolding_positive_noDesiredHolding() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var holdings = List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).holdings(holdings))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result).isEqualTo(new ConsortiumHolding()); + } + + @Test + void getConsortiumItem_positive() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var items = List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), item(id, MEMBER_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).items(items))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result.getId()).isEqualTo(id); + assertThat(result.getTenantId()).isEqualTo(MEMBER_TENANT_ID); + assertThat(result.getInstanceId()).isEqualTo(instanceId); + assertThat(result.getHoldingsRecordId()).isNotBlank(); + } + + @Test + void getConsortiumItem_positive_noRecords() { + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var searchResult = SearchResult.of(0, Collections.emptyList()); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result).isEqualTo(new ConsortiumItem()); + } + + @Test + void getConsortiumItem_positive_noItems() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var items = Collections.emptyList(); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).items(items))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result).isEqualTo(new ConsortiumItem()); + } + + @Test + void getConsortiumItem_positive_noDesiredItem() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var items = List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).items(items))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result).isEqualTo(new ConsortiumItem()); + } + + @Test + void fetchConsortiumBatchHoldings_positive() { + var ids = List.of(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + var instances = List.of( + instanceForHoldings(List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), + holding(ids.get(0), MEMBER_TENANT_ID))), + instanceForHoldings(List.of(holding(ids.get(1), CENTRAL_TENANT_ID))), + instanceForHoldings(List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)))); + var searchResult = SearchResult.of(instances.size(), instances); + var request = Mockito.>mock(); + var expected = new ConsortiumHoldingCollection() + .holdings(instances.subList(0, instances.size() - 1).stream().map(instance -> { + var holding = instance.getHoldings().size() > 1 ? instance.getHoldings().get(1) : instance.getHoldings().get(0); + return new ConsortiumHolding() + .id(holding.getId()) + .instanceId(instance.getId()) + .tenantId(holding.getTenantId()); + }).toList()) + .totalRecords(2); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.fetchConsortiumBatchHoldings(request, Sets.newHashSet(ids)); + + assertThat(result).isEqualTo(expected); + } + + @Test + void fetchConsortiumBatchItems_positive() { + var ids = List.of(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + var instances = List.of( + instanceForItems(List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), + item(ids.get(0), MEMBER_TENANT_ID))), + instanceForItems(List.of(item(ids.get(1), CENTRAL_TENANT_ID))), + instanceForItems(List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)))); + var searchResult = SearchResult.of(instances.size(), instances); + var request = Mockito.>mock(); + var expected = new ConsortiumItemCollection() + .items(instances.subList(0, instances.size() - 1).stream().map(instance -> { + var item = instance.getItems().size() > 1 ? instance.getItems().get(1) : instance.getItems().get(0); + return new ConsortiumItem() + .id(item.getId()) + .instanceId(instance.getId()) + .tenantId(item.getTenantId()) + .holdingsRecordId(item.getHoldingsRecordId()); + }).toList()) + .totalRecords(2); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.fetchConsortiumBatchItems(request, Sets.newHashSet(ids)); + + assertThat(result).isEqualTo(expected); + } + + private Holding holding(String id, String tenantId) { + return new Holding() + .id(id) + .tenantId(tenantId); + } + + private Item item(String id, String tenantId) { + return new Item() + .id(id) + .tenantId(tenantId) + .holdingsRecordId(UUID.randomUUID().toString()); + } + + private Instance instanceForHoldings(List holdings) { + return new Instance() + .id(UUID.randomUUID().toString()) + .holdings(holdings); + } + + private Instance instanceForItems(List items) { + return new Instance() + .id(UUID.randomUUID().toString()) + .items(items); + } +} diff --git a/src/test/java/org/folio/search/support/base/ApiEndpoints.java b/src/test/java/org/folio/search/support/base/ApiEndpoints.java index 1309e2b69..dfc6d31c4 100644 --- a/src/test/java/org/folio/search/support/base/ApiEndpoints.java +++ b/src/test/java/org/folio/search/support/base/ApiEndpoints.java @@ -41,6 +41,22 @@ public static String consortiumItemsSearchPath(List> queryP return addQueryParams(consortiumItemsSearchPath(), queryParams); } + public static String consortiumHoldingSearchPath(String id) { + return "/search/consortium/holding/" + id; + } + + public static String consortiumItemSearchPath(String id) { + return "/search/consortium/item/" + id; + } + + public static String consortiumBatchHoldingsSearchPath() { + return "/search/consortium/batch/holdings"; + } + + public static String consortiumBatchItemsSearchPath() { + return "/search/consortium/batch/items"; + } + public static String authoritySearchPath() { return "/search/authorities"; } diff --git a/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java b/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java index 980c0ddca..b56f35b4b 100644 --- a/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java +++ b/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java @@ -103,6 +103,25 @@ public static ResultActions doGet(MockHttpServletRequestBuilder request, String .andExpect(status().isOk()); } + @SneakyThrows + public static ResultActions tryPost(String uri, Object body) { + return tryPost(uri, MEMBER_TENANT_ID, body); + } + + @SneakyThrows + public static ResultActions tryPost(String uri, String tenantHeader, Object body) { + return mockMvc.perform(post(uri) + .content(asJsonString(body)) + .headers(defaultHeaders(tenantHeader)) + .accept("application/json;charset=UTF-8")); + } + + @SneakyThrows + public static ResultActions doPost(String uri, String tenantHeader, Object body) { + return tryPost(uri, tenantHeader, body) + .andExpect(status().isOk()); + } + @SneakyThrows protected static ResultActions doSearchByInstances(String query) { return doSearch(instanceSearchPath(), MEMBER_TENANT_ID, query, null, null, null); diff --git a/src/test/resources/samples/instance-response-sample/instance-full-response.json b/src/test/resources/samples/instance-response-sample/instance-full-response.json index 7f25f4f11..03c6c3f04 100644 --- a/src/test/resources/samples/instance-response-sample/instance-full-response.json +++ b/src/test/resources/samples/instance-response-sample/instance-full-response.json @@ -220,6 +220,7 @@ "staffOnly": false } ], + "holdingsRecordId" : "e3ff6133-b9a2-4d4c-a1c9-dc1867d4df19", "metadata": { "createdDate": "2020-12-08T15:47:21.327+00:00", "updatedDate": "2021-01-18T08:17:56.752+00:00", @@ -281,6 +282,7 @@ "staffOnly": false } ], + "holdingsRecordId" : "e3ff6133-b9a2-4d4c-a1c9-dc1867d4df19", "metadata": { "createdDate": "2020-12-08T15:47:22.827+00:00", "updatedDate": "2020-12-08T15:47:22.827+00:00" @@ -312,6 +314,7 @@ "notes": [], "statisticalCodeIds": [], "circulationNotes": [], + "holdingsRecordId" : "9550c935-401a-4a85-875e-4d1fe7678870", "metadata": { "createdDate": "2024-03-14T13:05:09.406+00:00", "createdByUserId": "93fc1367-7321-4836-b2a7-cbc7ffe8c20e", From f7c6d0a1655ce68820a84f2262dc0e47690395c8 Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Wed, 22 May 2024 17:59:14 +0200 Subject: [PATCH 16/19] Fix api schema lint errors --- .../paths/search-consortium/search-consortium-holding.yaml | 3 ++- .../paths/search-consortium/search-consortium-item.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml index 00af95cc3..ed00f7981 100644 --- a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml @@ -1,5 +1,6 @@ get: operationId: getConsortiumHolding + summary: Fetch consolidated holding by id description: Get holding by id (only for consortium environment) tags: - search-consortium @@ -8,7 +9,7 @@ get: - $ref: '../../parameters/x-okapi-tenant-header.yaml' responses: '200': - description: an instance holding + description: An instance holding content: application/json: schema: diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml index 5d1b83b42..a33b183d9 100644 --- a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml @@ -1,5 +1,6 @@ get: operationId: getConsortiumItem + summary: Fetch consolidated item by id description: Get an item (only for consortium environment) tags: - search-consortium @@ -8,7 +9,7 @@ get: - $ref: '../../parameters/x-okapi-tenant-header.yaml' responses: '200': - description: an instance item + description: An instance item content: application/json: schema: From 2fc369cd19d52ef49ece2350740d0af8e49b79b9 Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Wed, 29 May 2024 13:34:55 +0200 Subject: [PATCH 17/19] - Add permissions in module descriptor - Fix permission names in module descriptor --- descriptors/ModuleDescriptor-template.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 2e26b387e..b2b5e29c8 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -186,7 +186,7 @@ ], "pathPattern": "/search/consortium/holding/{id}", "permissionsRequired": [ - "consortium-search.items.collection.get" + "consortium-search.holdings.item.get" ], "modulePermissions": [ "user-tenants.collection.get" @@ -198,7 +198,7 @@ ], "pathPattern": "/search/consortium/item/{id}", "permissionsRequired": [ - "consortium-search.items.collection.get" + "consortium-search.items.item.get" ], "modulePermissions": [ "user-tenants.collection.get" @@ -696,6 +696,16 @@ "displayName": "Consortium Search - fetch items records", "description": "Returns items records in consortium" }, + { + "permissionName": "consortium-search.holdings.item.get", + "displayName": "Consortium Search - fetch holdings record", + "description": "Returns holdings record in consortium" + }, + { + "permissionName": "consortium-search.items.item.get", + "displayName": "Consortium Search - fetch item record", + "description": "Returns item record in consortium" + }, { "permissionName": "consortium-search.locations.collection.get", "displayName": "Consortium Search - fetch locations records", From 397d76516ea8342cd15fc9d9d957b094d0e89c2d Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Thu, 30 May 2024 12:14:49 +0200 Subject: [PATCH 18/19] - Refactor --- .../SearchConsortiumController.java | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 6b35429a9..3e0f1f1b7 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -1,5 +1,9 @@ package org.folio.search.controller; +import static org.folio.search.utils.SearchUtils.INSTANCE_HOLDING_FIELD_NAME; +import static org.folio.search.utils.SearchUtils.INSTANCE_ITEM_FIELD_NAME; + +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -97,20 +101,19 @@ public ResponseEntity getConsortiumLocations(Strin public ResponseEntity getConsortiumHolding(UUID id, String tenantHeader) { var tenant = verifyAndGetTenant(tenantHeader); var holdingId = id.toString(); - var query = "holdings.id=" + holdingId; - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true, false, true); + var searchRequest = idCqlRequest(tenant, INSTANCE_HOLDING_FIELD_NAME, holdingId); - var result = searchService.getConsortiumHolding(id.toString(), searchRequest); + var result = searchService.getConsortiumHolding(holdingId, searchRequest); return ResponseEntity.ok(result); } @Override - public ResponseEntity getConsortiumItem(UUID itemId, String tenantHeader) { + public ResponseEntity getConsortiumItem(UUID id, String tenantHeader) { var tenant = verifyAndGetTenant(tenantHeader); - var query = "items.id=" + itemId.toString(); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true, false, true); + var itemId = id.toString(); + var searchRequest = idCqlRequest(tenant, INSTANCE_ITEM_FIELD_NAME, itemId); - var result = searchService.getConsortiumItem(itemId.toString(), searchRequest); + var result = searchService.getConsortiumItem(itemId, searchRequest); return ResponseEntity.ok(result); } @@ -119,11 +122,7 @@ public ResponseEntity fetchConsortiumBatchHoldings( BatchIdsDto batchIdsDto) { var tenant = verifyAndGetTenant(tenantHeader); var holdingIds = batchIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); - var query = batchIdsDto.getIds().stream() - .map(UUID::toString) - .map("holdings.id = %s"::formatted) - .collect(Collectors.joining(" or ")); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, holdingIds.size(), 0, true, false, true); + var searchRequest = idsCqlRequest(tenant, INSTANCE_HOLDING_FIELD_NAME, holdingIds); var result = searchService.fetchConsortiumBatchHoldings(searchRequest, holdingIds); return ResponseEntity.ok(result); @@ -139,11 +138,7 @@ public ResponseEntity fetchConsortiumBatchItems(String var tenant = verifyAndGetTenant(tenantHeader); var itemIds = batchIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); - var query = batchIdsDto.getIds().stream() - .map(UUID::toString) - .map("items.id = %s"::formatted) - .collect(Collectors.joining(" or ")); - var searchRequest = CqlSearchRequest.of(Instance.class, tenant, query, itemIds.size(), 0, true, false, true); + var searchRequest = idsCqlRequest(tenant, INSTANCE_ITEM_FIELD_NAME, itemIds); var result = searchService.fetchConsortiumBatchItems(searchRequest, itemIds); return ResponseEntity.ok(result); @@ -157,4 +152,17 @@ private String verifyAndGetTenant(String tenantHeader) { return centralTenant.get(); } + private CqlSearchRequest idCqlRequest(String tenant, String fieldName, String id) { + var query = fieldName + ".id=" + id; + return CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true, false, true); + } + + private CqlSearchRequest idsCqlRequest(String tenant, String fieldName, Set ids) { + var query = ids.stream() + .map((fieldName + ".id=%s")::formatted) + .collect(Collectors.joining(" or ")); + + return CqlSearchRequest.of(Instance.class, tenant, query, ids.size(), 0, true, false, true); + } + } From 570360a48823cb9495f0784e641b2bf0c8c2699c Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Thu, 30 May 2024 12:33:05 +0200 Subject: [PATCH 19/19] - Refactor --- .../converter/ConsortiumHoldingMapper.java | 7 +++-- .../converter/ConsortiumItemMapper.java | 7 +++-- .../ConsortiumInstanceSearchService.java | 10 +++---- .../ConsortiumInstanceSearchServiceTest.java | 30 +------------------ 4 files changed, 13 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java b/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java index e8ea62afc..d92c894dd 100644 --- a/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java +++ b/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java @@ -1,13 +1,14 @@ package org.folio.search.converter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.Holding; -import org.springframework.stereotype.Component; -@Component +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class ConsortiumHoldingMapper { - public ConsortiumHolding map(String instanceId, Holding holding) { + public static ConsortiumHolding toConsortiumHolding(String instanceId, Holding holding) { return new ConsortiumHolding() .id(holding.getId()) .hrid(holding.getHrid()) diff --git a/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java b/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java index 8603afee6..830fd1236 100644 --- a/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java +++ b/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java @@ -1,13 +1,14 @@ package org.folio.search.converter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.folio.search.domain.dto.ConsortiumItem; import org.folio.search.domain.dto.Item; -import org.springframework.stereotype.Component; -@Component +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class ConsortiumItemMapper { - public ConsortiumItem map(String instanceId, Item item) { + public static ConsortiumItem toConsortiumItem(String instanceId, Item item) { return new ConsortiumItem() .id(item.getId()) .hrid(item.getHrid()) diff --git a/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java index 5eae42e54..c76ddf2b0 100644 --- a/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java +++ b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java @@ -27,8 +27,6 @@ @RequiredArgsConstructor public class ConsortiumInstanceSearchService { - private final ConsortiumHoldingMapper holdingMapper; - private final ConsortiumItemMapper itemMapper; private final SearchService searchService; public ConsortiumHolding getConsortiumHolding(String id, CqlSearchRequest searchRequest) { @@ -47,7 +45,7 @@ public ConsortiumHolding getConsortiumHolding(String id, CqlSearchRequest searchRequest) { @@ -66,7 +64,7 @@ public ConsortiumItem getConsortiumItem(String id, CqlSearchRequest se return new ConsortiumItem(); } - return itemMapper.map(instance.getId(), item); + return ConsortiumItemMapper.toConsortiumItem(instance.getId(), item); } public ConsortiumHoldingCollection fetchConsortiumBatchHoldings(CqlSearchRequest searchRequest, @@ -76,7 +74,7 @@ public ConsortiumHoldingCollection fetchConsortiumBatchHoldings(CqlSearchRequest .flatMap(instance -> instance.getHoldings().stream() .filter(holding -> ids.contains(holding.getId())) - .map(holding -> holdingMapper.map(instance.getId(), holding)) + .map(holding -> ConsortiumHoldingMapper.toConsortiumHolding(instance.getId(), holding)) ) .toList(); return new ConsortiumHoldingCollection() @@ -91,7 +89,7 @@ public ConsortiumItemCollection fetchConsortiumBatchItems(CqlSearchRequest instance.getItems().stream() .filter(item -> ids.contains(item.getId())) - .map(item -> itemMapper.map(instance.getId(), item)) + .map(item -> ConsortiumItemMapper.toConsortiumItem(instance.getId(), item)) ) .toList(); diff --git a/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java b/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java index 06d966ff9..a7141b869 100644 --- a/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java +++ b/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java @@ -3,17 +3,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import com.google.common.collect.Sets; import java.util.Collections; import java.util.List; import java.util.UUID; -import org.folio.search.converter.ConsortiumHoldingMapper; -import org.folio.search.converter.ConsortiumItemMapper; import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.domain.dto.ConsortiumItem; @@ -25,7 +20,6 @@ import org.folio.search.model.service.CqlSearchRequest; import org.folio.search.service.SearchService; import org.folio.spring.testing.type.UnitTest; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -37,32 +31,9 @@ @ExtendWith(MockitoExtension.class) class ConsortiumInstanceSearchServiceTest { - private @Mock ConsortiumHoldingMapper holdingMapper; - private @Mock ConsortiumItemMapper itemMapper; private @Mock SearchService searchService; private @InjectMocks ConsortiumInstanceSearchService service; - @BeforeEach - void setUpMocks() { - lenient().doAnswer(invocationOnMock -> { - var instanceId = (String) invocationOnMock.getArgument(0); - var holding = (Holding) invocationOnMock.getArgument(1); - return new ConsortiumHolding() - .id(holding.getId()) - .instanceId(instanceId) - .tenantId(holding.getTenantId()); - }).when(holdingMapper).map(anyString(), any()); - lenient().doAnswer(invocationOnMock -> { - var instanceId = (String) invocationOnMock.getArgument(0); - var item = (Item) invocationOnMock.getArgument(1); - return new ConsortiumItem() - .id(item.getId()) - .instanceId(instanceId) - .tenantId(item.getTenantId()) - .holdingsRecordId(item.getHoldingsRecordId()); - }).when(itemMapper).map(anyString(), any()); - } - @Test void getConsortiumHolding_positive() { var instanceId = UUID.randomUUID().toString(); @@ -200,6 +171,7 @@ void fetchConsortiumBatchHoldings_positive() { return new ConsortiumHolding() .id(holding.getId()) .instanceId(instance.getId()) + .discoverySuppress(false) .tenantId(holding.getTenantId()); }).toList()) .totalRecords(2);