Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(consortium-search): Implement consolidated items/holdings search #586

Merged
merged 24 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c11bd1d
MSEARCH-707: add get consortium item api
mukhiddin-yusuf Apr 4, 2024
50e3d95
MSEARCH-707: add module description for api
mukhiddin-yusuf Apr 4, 2024
d37fdbd
MSEARCH-707: add module description for api
mukhiddin-yusuf Apr 4, 2024
a3e7a1b
MSEARCH-707: add module description for api
mukhiddin-yusuf Apr 4, 2024
ecc3b70
MSEARCH-707: add module description for api
mukhiddin-yusuf Apr 4, 2024
cdf01f0
MSEARCH-707: add module description for api
mukhiddin-yusuf Apr 4, 2024
5126d02
MSEARCH-707: add module description for api
mukhiddin-yusuf Apr 5, 2024
6255b2e
MSEARCH-707: add module description for api
mukhiddin-yusuf Apr 5, 2024
399ee83
Merge branch 'master' into msearch-707
mukhiddin-yusuf Apr 29, 2024
48ce00c
msearch-707: adjust poc after marge with master
mukhiddin-yusuf Apr 29, 2024
4a2f4c9
msearch-707: refactor
mukhiddin-yusuf Apr 29, 2024
bb989b9
msearch-707: add batch item and holding endpoints
mukhiddin-yusuf Apr 29, 2024
7c6d97f
msearch-707: add batch item and holding endpoints
mukhiddin-yusuf Apr 30, 2024
e4c402e
msearch-707: add batch item and holding endpoints
mukhiddin-yusuf Apr 30, 2024
2a9e79c
msearch-707: modify batch endpoints to filter out the desired item/ho…
mukhiddin-yusuf May 1, 2024
13e1fac
Merge branch 'msearch-707' into MSEARCH-759
viacheslavkol May 20, 2024
c5f5ca5
feat(consortium-search): Implement consolidated items/holdings search
viacheslavkol May 22, 2024
2030a88
Merge branch 'master' into MSEARCH-759
viacheslavkol May 22, 2024
f7c6d0a
Fix api schema lint errors
viacheslavkol May 22, 2024
2fc369c
- Add permissions in module descriptor
viacheslavkol May 29, 2024
98f5129
Merge branch 'master' into MSEARCH-759
psmagin May 29, 2024
397d765
- Refactor
viacheslavkol May 30, 2024
570360a
- Refactor
viacheslavkol May 30, 2024
c3d49a7
Merge branch 'master' into MSEARCH-759
mukhiddin-yusuf May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
58 changes: 58 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,54 @@
"modulePermissions": [
"user-tenants.collection.get"
]
},
{
"methods": [
"GET"
],
"pathPattern": "/search/consortium/holding/{id}",
"permissionsRequired": [
"consortium-search.holdings.item.get"
],
"modulePermissions": [
"user-tenants.collection.get"
]
},
{
"methods": [
"GET"
],
"pathPattern": "/search/consortium/item/{id}",
"permissionsRequired": [
"consortium-search.items.item.get"
],
"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"
]
}
]
},
Expand Down Expand Up @@ -648,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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
package org.folio.search.controller;

import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
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;
import org.folio.search.domain.dto.ConsortiumItemCollection;
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;
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.consortium.ConsortiumInstanceSearchService;
import org.folio.search.service.consortium.ConsortiumInstanceService;
import org.folio.search.service.consortium.ConsortiumLocationService;
import org.folio.search.service.consortium.ConsortiumTenantService;
Expand All @@ -18,6 +27,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Log4j2
@Validated
@RestController
@RequiredArgsConstructor
Expand All @@ -30,13 +40,14 @@ public class SearchConsortiumController implements SearchConsortiumApi {
private final ConsortiumTenantService consortiumTenantService;
private final ConsortiumInstanceService instanceService;
private final ConsortiumLocationService locationService;
private final ConsortiumInstanceSearchService searchService;

@Override
public ResponseEntity<ConsortiumHoldingCollection> 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)
Expand All @@ -53,7 +64,7 @@ public ResponseEntity<ConsortiumItemCollection> 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)
Expand All @@ -73,7 +84,7 @@ public ResponseEntity<ConsortiumLocationCollection> getConsortiumLocations(Strin
Integer offset,
String sortBy,
SortOrder sortOrder) {
checkAllowance(tenantHeader);
verifyAndGetTenant(tenantHeader);
var result = locationService.fetchLocations(tenantHeader, tenantId, limit, offset, sortBy, sortOrder);

return ResponseEntity.ok(new
Expand All @@ -82,11 +93,68 @@ public ResponseEntity<ConsortiumLocationCollection> getConsortiumLocations(Strin
.totalRecords(result.getTotalRecords()));
}

private void checkAllowance(String tenantHeader) {
@Override
public ResponseEntity<ConsortiumHolding> 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<ConsortiumItem> 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, false, true);

var result = searchService.getConsortiumItem(itemId.toString(), searchRequest);
return ResponseEntity.ok(result);
}

@Override
public ResponseEntity<ConsortiumHoldingCollection> 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);

var result = searchService.fetchConsortiumBatchHoldings(searchRequest, holdingIds);
return ResponseEntity.ok(result);
}

@Override
public ResponseEntity<ConsortiumItemCollection> fetchConsortiumBatchItems(String tenantHeader,
BatchIdsDto batchIdsDto) {
if (batchIdsDto.getIds().isEmpty()) {
return ResponseEntity
.ok(new ConsortiumItemCollection());
}

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);
viacheslavkol marked this conversation as resolved.
Show resolved Hide resolved

var result = searchService.fetchConsortiumBatchItems(searchRequest, itemIds);
return ResponseEntity.ok(result);
}

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();
}

}
Original file line number Diff line number Diff line change
@@ -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
viacheslavkol marked this conversation as resolved.
Show resolved Hide resolved
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());
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public class CqlSearchRequest<T> 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.
*
Expand All @@ -64,14 +69,22 @@ public class CqlSearchRequest<T> implements ResourceRequest {
* @param expandAll - whether to return only response properties or entire record
* @param <R> - 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 <R> CqlSearchRequest<R> of(Class<R> 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 <R> CqlSearchRequest<R> of(Class<R> 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 <R> CqlSearchRequest<R> of(Class<R> resourceClass, String tenantId, String query,
Expand Down
8 changes: 3 additions & 5 deletions src/main/java/org/folio/search/service/SearchService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,7 +55,8 @@ public <T> SearchResult<T> search(CqlSearchRequest<T> 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)
Expand All @@ -76,10 +78,6 @@ public <T> SearchResult<T> search(CqlSearchRequest<T> request) {
return searchResult;
}

private String buildPreferenceKey(String tenantId, String resource, String query) {
return tenantId + "-" + resource + "-" + query;
}

private <T> void searchResultPostProcessing(Class<?> resourceClass, boolean includeNumberOfTitles,
SearchResult<T> searchResult) {
if (Objects.isNull(resourceClass)) {
Expand Down
Loading
Loading