diff --git a/NEWS.md b/NEWS.md index 64c27f94b..70fb18c70 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,7 +5,7 @@ * Requires `identifier-types v1.0` * Requires `call-number-types v1.0` * Provides `indices v0.6` -* Provides `search v1.1` +* Provides `search v1.2` * Provides `browse v1.2` ### Features @@ -22,6 +22,7 @@ * Implement Active Affiliation Context for stream IDs in Consortia Mode ([MSEARCH-576](https://issues.folio.org/browse/MSEARCH-576)) * Restrict central tenant queries to only shared records ([MSEARCH-588](https://issues.folio.org/browse/MSEARCH-588)) * Implement Active Affiliation Context for browsing ([MSEARCH-580](https://issues.folio.org/browse/MSEARCH-580)) +* Add new facets for shared/local flag and tenantId ([MSEARCH-534](https://issues.folio.org/browse/MSEARCH-534)) ### Bug fixes * Fix bug when number of titles response is greater than real ([MSEARCH-526](https://issues.folio.org/browse/MSEARCH-526)) diff --git a/README.md b/README.md index b93faec77..b25feab04 100644 --- a/README.md +++ b/README.md @@ -371,13 +371,13 @@ Consortium feature on module enable is defined by 'centralTenantId' tenant param ### Search API -| METHOD | URL | DESCRIPTION | -|:-------|:------------------------------|:------------------------------------------------------------------------------| -| GET | `/search/instances` | Search by instances and to this instance items and holding-records | -| GET | `/search/authorities` | Search by authority records | -| GET | `/search/{recordType}/facets` | Get facets where recordType could be: instances, authorities, or contributors | -| GET | ~~`/search/instances/ids`~~ | (DEPRECATED) Stream instance ids as JSON or plain text | -| GET | ~~`/search/holdings/ids`~~ | (DEPRECATED) Stream holding record ids as JSON or plain text | +| METHOD | URL | DESCRIPTION | +|:-------|:------------------------------|:-------------------------------------------------------------------------------------| +| GET | `/search/instances` | Search by instances and to this instance items and holding-records | +| GET | `/search/authorities` | Search by authority records | +| GET | `/search/{recordType}/facets` | Get facets where recordType could be: instances, authorities, contributors, subjects | +| GET | ~~`/search/instances/ids`~~ | (DEPRECATED) Stream instance ids as JSON or plain text | +| GET | ~~`/search/holdings/ids`~~ | (DEPRECATED) Stream holding record ids as JSON or plain text | #### Searching and filtering @@ -636,20 +636,22 @@ GET /instances/facets?query=title all book&facet=source:5,discoverySuppress:2 ##### Instance facets -| Option | Type | Description | -|:-------------------------|:-------:|:---------------------------------------------------------------------| -| `source` | term | Requests a source facet | -| `instanceTypeId` | term | Requests a type id facet | -| `statusId` | term | Requests a status id facet | -| `instanceFormatIds` | term | Requests a format id facet | -| `modeOfIssuanceId` | term | Requests a mode of issuance id facet | -| `natureOfContentTermIds` | term | Requests a nature of content terms id facet | -| `languages` | term | Requests a language code facet | -| `instanceTags` | term | Requests a tags facet | -| `staffSuppress` | boolean | Requests a staff suppress facet | -| `discoverySuppress` | boolean | Requests a discovery suppress facet | -| `statisticalCodeIds` | term | Requests a statistical code ids facet | -| `statisticalCodes` | term | Requests a statistical code ids from instance, holdings, item facet | +| Option | Type | Description | +|:-------------------------|:-------:|:--------------------------------------------------------------------| +| `source` | term | Requests a source facet | +| `instanceTypeId` | term | Requests a type id facet | +| `statusId` | term | Requests a status id facet | +| `instanceFormatIds` | term | Requests a format id facet | +| `modeOfIssuanceId` | term | Requests a mode of issuance id facet | +| `natureOfContentTermIds` | term | Requests a nature of content terms id facet | +| `languages` | term | Requests a language code facet | +| `instanceTags` | term | Requests a tags facet | +| `staffSuppress` | boolean | Requests a staff suppress facet | +| `discoverySuppress` | boolean | Requests a discovery suppress facet | +| `statisticalCodeIds` | term | Requests a statistical code ids facet | +| `statisticalCodes` | term | Requests a statistical code ids from instance, holdings, item facet | +| `tenantId` | term | Requests a tenantId facet | +| `shared` | term | Requests a shared/local facet | ##### Holdings facets @@ -661,6 +663,7 @@ GET /instances/facets?query=title all book&facet=source:5,discoverySuppress:2 | `holdings.sourceId` | term | Requests a holdings sourceId facet | | `holdingsTypeId` | term | Requests a holdings typeId facet | | `holdingsTags` | term | Requests a holdings tag facet | +| `holdings.tenantId` | term | Requests a holdings tenantId facet | ##### Item facets @@ -680,12 +683,23 @@ GET /instances/facets?query=title all book&facet=source:5,discoverySuppress:2 | `headingType` | term | Requests a heading type facet | | `subjectHeadings` | term | Requests a subject headings facet | | `sourceFileId` | term | Requests a source files facet (default value: `NULL`) | +| `tenantId` | term | Requests a tenantId facet | +| `shared` | term | Requests a shared/local facet | ##### Contributors facets | Option | Type | Description | |:------------------------|:----:|:---------------------------------------| | `contributorNameTypeId` | term | Requests a contributor name type facet | +| `instances.tenantId` | term | Requests a tenantId facet | +| `instances.shared` | term | Requests a shared/local facet | + +##### Subjects facets + +| Option | Type | Description | +|:------------------------|:----:|:---------------------------------------| +| `instances.tenantId` | term | Requests a tenantId facet | +| `instances.shared` | term | Requests a shared/local facet | #### Sorting results diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 6d1aec497..3b5ec4840 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -49,7 +49,7 @@ }, { "id": "search", - "version": "1.1", + "version": "1.2", "handlers": [ { "methods": [ "GET" ], diff --git a/src/main/java/org/folio/search/controller/FacetsController.java b/src/main/java/org/folio/search/controller/FacetsController.java index c4035c8f0..b3d0d65a4 100644 --- a/src/main/java/org/folio/search/controller/FacetsController.java +++ b/src/main/java/org/folio/search/controller/FacetsController.java @@ -3,6 +3,7 @@ import static org.folio.search.utils.SearchUtils.AUTHORITY_RESOURCE; import static org.folio.search.utils.SearchUtils.CONTRIBUTOR_RESOURCE; import static org.folio.search.utils.SearchUtils.INSTANCE_RESOURCE; +import static org.folio.search.utils.SearchUtils.INSTANCE_SUBJECT_RESOURCE; import java.util.List; import java.util.Map; @@ -27,7 +28,8 @@ public class FacetsController implements FacetsApi { private static final Map RECORD_TYPE_TO_RESOURCE_MAP = Map.of( RecordType.INSTANCES, INSTANCE_RESOURCE, RecordType.AUTHORITIES, AUTHORITY_RESOURCE, - RecordType.CONTRIBUTORS, CONTRIBUTOR_RESOURCE + RecordType.CONTRIBUTORS, CONTRIBUTOR_RESOURCE, + RecordType.SUBJECTS, INSTANCE_SUBJECT_RESOURCE ); private final FacetService facetService; diff --git a/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java b/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java index 7c4b1b5be..cf573564f 100644 --- a/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java +++ b/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java @@ -69,7 +69,7 @@ public SearchSourceBuilder convert(String query, String resource) { */ public SearchSourceBuilder convertForConsortia(String query, String resource) { var sourceBuilder = convert(query, resource); - var queryBuilder = consortiumSearchHelper.filterQueryForActiveAffiliation(sourceBuilder.query()); + var queryBuilder = consortiumSearchHelper.filterQueryForActiveAffiliation(sourceBuilder.query(), resource); return sourceBuilder.query(queryBuilder); } diff --git a/src/main/java/org/folio/search/service/browse/AuthorityBrowseService.java b/src/main/java/org/folio/search/service/browse/AuthorityBrowseService.java index b78b5899e..c85455480 100644 --- a/src/main/java/org/folio/search/service/browse/AuthorityBrowseService.java +++ b/src/main/java/org/folio/search/service/browse/AuthorityBrowseService.java @@ -6,6 +6,7 @@ import static org.folio.search.model.index.AuthRefType.REFERENCE; import static org.folio.search.model.types.ResponseGroupType.BROWSE; import static org.folio.search.utils.LogUtils.collectionToLogMsg; +import static org.folio.search.utils.SearchUtils.AUTHORITY_RESOURCE; import static org.opensearch.index.query.QueryBuilders.boolQuery; import static org.opensearch.index.query.QueryBuilders.termQuery; import static org.opensearch.index.query.QueryBuilders.termsQuery; @@ -64,7 +65,7 @@ protected SearchSourceBuilder getSearchQuery(BrowseRequest request, BrowseContex var boolQuery = boolQuery().filter(FILTER_QUERY); ctx.getFilters().forEach(boolQuery::filter); - var query = consortiumSearchHelper.filterQueryForActiveAffiliation(boolQuery); + var query = consortiumSearchHelper.filterQueryForActiveAffiliation(boolQuery, AUTHORITY_RESOURCE); return searchSource().query(query) .searchAfter(new Object[] {ctx.getAnchor().toLowerCase(ROOT)}) .sort(fieldSort(request.getTargetField()).order(isBrowsingForward ? ASC : DESC)) @@ -80,7 +81,7 @@ protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest request, Browse var boolQuery = boolQuery().filter(FILTER_QUERY).must(termQuery(request.getTargetField(), context.getAnchor())); context.getFilters().forEach(boolQuery::filter); - var query = consortiumSearchHelper.filterQueryForActiveAffiliation(boolQuery); + var query = consortiumSearchHelper.filterQueryForActiveAffiliation(boolQuery, AUTHORITY_RESOURCE); return searchSource().query(query).from(0).size(1).fetchSource(getIncludedSourceFields(request), null); } diff --git a/src/main/java/org/folio/search/service/browse/CallNumberBrowseQueryProvider.java b/src/main/java/org/folio/search/service/browse/CallNumberBrowseQueryProvider.java index bd55a86c7..f3f187972 100644 --- a/src/main/java/org/folio/search/service/browse/CallNumberBrowseQueryProvider.java +++ b/src/main/java/org/folio/search/service/browse/CallNumberBrowseQueryProvider.java @@ -4,6 +4,7 @@ import static org.apache.commons.lang3.BooleanUtils.isFalse; import static org.folio.search.model.types.ResponseGroupType.CN_BROWSE; import static org.folio.search.utils.CallNumberUtils.getCallNumberAsLong; +import static org.folio.search.utils.SearchUtils.INSTANCE_RESOURCE; import static org.opensearch.index.query.QueryBuilders.boolQuery; import static org.opensearch.index.query.QueryBuilders.rangeQuery; import static org.opensearch.script.Script.DEFAULT_SCRIPT_LANG; @@ -59,7 +60,7 @@ public SearchSourceBuilder get(BrowseRequest request, BrowseContext ctx, boolean var multiplier = queryConfiguration.getRangeQueryLimitMultiplier(); var pageSize = (int) Math.max(MIN_QUERY_SIZE, Math.ceil(ctx.getLimit(isBrowsingForward) * multiplier)); var initialQuery = getQuery(ctx, request, pageSize, isBrowsingForward); - var query = consortiumSearchHelper.filterQueryForActiveAffiliation(initialQuery); + var query = consortiumSearchHelper.filterQueryForActiveAffiliation(initialQuery, INSTANCE_RESOURCE); var searchSource = searchSource().from(0).size(pageSize) .query(query) .sort(scriptSort(script, STRING).order(isBrowsingForward ? ASC : DESC)); diff --git a/src/main/java/org/folio/search/service/browse/ContributorBrowseService.java b/src/main/java/org/folio/search/service/browse/ContributorBrowseService.java index ea9bb1bdd..1d7ddae08 100644 --- a/src/main/java/org/folio/search/service/browse/ContributorBrowseService.java +++ b/src/main/java/org/folio/search/service/browse/ContributorBrowseService.java @@ -44,7 +44,7 @@ protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest request, Browse log.debug("getAnchorSearchQuery:: by [request: {}]", request); var boolQuery = boolQuery().must(termQuery(request.getTargetField(), context.getAnchor())); context.getFilters().forEach(boolQuery::filter); - var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(context, boolQuery); + var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(context, boolQuery, request.getResource()); return searchSource().query(query) .size(context.getLimit(context.isBrowsingForward())) .from(0); @@ -62,7 +62,7 @@ protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ct ctx.getFilters().forEach(boolQuery::filter); query = boolQuery; } - query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, query); + query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, query, req.getResource()); return searchSource().query(query) .searchAfter(new Object[] {ctx.getAnchor().toLowerCase(ROOT), null, null, null}) .sort(fieldSort(req.getTargetField()).order(isBrowsingForward ? ASC : DESC)) diff --git a/src/main/java/org/folio/search/service/browse/SubjectBrowseService.java b/src/main/java/org/folio/search/service/browse/SubjectBrowseService.java index bd5b45898..ae760a8e2 100644 --- a/src/main/java/org/folio/search/service/browse/SubjectBrowseService.java +++ b/src/main/java/org/folio/search/service/browse/SubjectBrowseService.java @@ -39,7 +39,7 @@ public void setConsortiumSearchHelper(ConsortiumSearchHelper consortiumSearchHel protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest request, BrowseContext context) { log.debug("getAnchorSearchQuery:: by [request: {}]", request); var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(context, - termQuery(request.getTargetField(), context.getAnchor())); + termQuery(request.getTargetField(), context.getAnchor()), request.getResource()); return searchSource().query(query) .size(context.getLimit(context.isBrowsingForward())) .from(0); @@ -56,7 +56,7 @@ protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ct ctx.getFilters().forEach(boolQuery::filter); query = boolQuery; } - query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, query); + query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, query, req.getResource()); return searchSource().query(query) .searchAfter(new Object[] {ctx.getAnchor().toLowerCase(ROOT)}) .sort(fieldSort(req.getTargetField()).order(isBrowsingForward ? ASC : DESC)) diff --git a/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceService.java b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceService.java index de993c534..711864965 100644 --- a/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceService.java +++ b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceService.java @@ -11,6 +11,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.ListUtils; import org.folio.search.domain.dto.ResourceEvent; @@ -27,6 +28,7 @@ * 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 ConsortiumInstanceService { @@ -53,6 +55,7 @@ public class ConsortiumInstanceService { * @return events that are not related to consortium tenants */ public List saveInstances(List instanceEvents) { + log.info("Saving consortium instances to DB [size: {}]", instanceEvents.size()); if (CollectionUtils.isEmpty(instanceEvents)) { return instanceEvents; } diff --git a/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java b/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java index 1e7d16a4d..1965b7484 100644 --- a/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java +++ b/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java @@ -1,6 +1,7 @@ package org.folio.search.service.consortium; -import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.folio.search.utils.SearchUtils.CONTRIBUTOR_RESOURCE; +import static org.folio.search.utils.SearchUtils.INSTANCE_SUBJECT_RESOURCE; import static org.folio.search.utils.SearchUtils.SHARED_FIELD_NAME; import static org.folio.search.utils.SearchUtils.TENANT_ID_FIELD_NAME; import static org.opensearch.index.query.QueryBuilders.boolQuery; @@ -32,78 +33,31 @@ public class ConsortiumSearchHelper { private final FolioExecutionContext folioExecutionContext; private final ConsortiumTenantService consortiumTenantService; - public QueryBuilder filterQueryForActiveAffiliation(QueryBuilder query) { + public QueryBuilder filterQueryForActiveAffiliation(QueryBuilder query, String resource) { var contextTenantId = folioExecutionContext.getTenantId(); var centralTenantId = consortiumTenantService.getCentralTenant(contextTenantId); if (centralTenantId.isEmpty()) { return query; } - return filterQueryForActiveAffiliation(query, contextTenantId, centralTenantId.get()); + return filterQueryForActiveAffiliation(query, contextTenantId, centralTenantId.get(), resource); } - public QueryBuilder filterQueryForActiveAffiliation(QueryBuilder query, String tenantId, String centralTenantId) { - return filterQueryForActiveAffiliation(EMPTY, query, tenantId, centralTenantId); - } - - public QueryBuilder filterQueryForActiveAffiliation(String fieldPrefix, QueryBuilder query, String tenantId, - String centralTenantId) { + public QueryBuilder filterQueryForActiveAffiliation(QueryBuilder query, String tenantId, + String centralTenantId, String resource) { var boolQuery = prepareBoolQueryForActiveAffiliation(query); - addActiveAffiliationClauses(fieldPrefix, boolQuery, tenantId, centralTenantId); + addActiveAffiliationClauses(boolQuery, tenantId, centralTenantId, resource); return boolQuery; } - private BoolQueryBuilder prepareBoolQueryForActiveAffiliation(QueryBuilder query) { - BoolQueryBuilder boolQuery; - if (query instanceof MatchAllQueryBuilder) { - boolQuery = boolQuery(); - } else if (query instanceof BoolQueryBuilder bq) { - boolQuery = bq; - } else { - boolQuery = boolQuery().must(query); - } - boolQuery.minimumShouldMatch(1); - return boolQuery; - } - - private void addActiveAffiliationClauses(String fieldPrefix, BoolQueryBuilder boolQuery, String contextTenantId, - String centralTenantId) { - var affiliationShouldClauses = getAffiliationShouldClauses(fieldPrefix, contextTenantId, centralTenantId); - if (boolQuery.should().isEmpty()) { - affiliationShouldClauses.forEach(boolQuery::should); - } else { - var innerBoolQuery = boolQuery(); - affiliationShouldClauses.forEach(innerBoolQuery::should); - boolQuery.must(innerBoolQuery); - } - } - - private LinkedList getAffiliationShouldClauses(String fieldPrefix, - String contextTenantId, String centralTenantId) { - var affiliationShouldClauses = new LinkedList(); - addTenantIdAffiliationShouldClause(fieldPrefix, contextTenantId, centralTenantId, affiliationShouldClauses); - addSharedAffiliationShouldClause(fieldPrefix, affiliationShouldClauses); - return affiliationShouldClauses; - } - - private void addTenantIdAffiliationShouldClause(String fieldPrefix, String contextTenantId, String centralTenantId, - LinkedList affiliationShouldClauses) { - if (!contextTenantId.equals(centralTenantId)) { - affiliationShouldClauses.add(termQuery(fieldPrefix + TENANT_ID_FIELD_NAME, contextTenantId)); - } - } - - private void addSharedAffiliationShouldClause(String fieldPrefix, LinkedList affiliationShouldClauses) { - affiliationShouldClauses.add(termQuery(fieldPrefix + SHARED_FIELD_NAME, true)); - } - /** * Modifies query to support both 'instances.shared' filter and Active Affiliation. * 'instances.shared' filter have precedence over Active Affiliation so in case of 'false' value - only local records * will be returned (original query have only 'tenantId' additional filter). - * */ - public QueryBuilder filterBrowseQueryForActiveAffiliation(BrowseContext browseContext, QueryBuilder query) { + */ + public QueryBuilder filterBrowseQueryForActiveAffiliation(BrowseContext browseContext, QueryBuilder query, + String resource) { var contextTenantId = folioExecutionContext.getTenantId(); var centralTenantId = consortiumTenantService.getCentralTenant(contextTenantId); var sharedFilter = getBrowseSharedFilter(browseContext); @@ -116,7 +70,7 @@ public QueryBuilder filterBrowseQueryForActiveAffiliation(BrowseContext browseCo var shared = sharedFilter.map(this::sharedFilterValue).orElse(true); if (Boolean.TRUE.equals(shared)) { - return filterQueryForActiveAffiliation("instances.", query, contextTenantId, centralTenantId.get()); + return filterQueryForActiveAffiliation(query, contextTenantId, centralTenantId.get(), resource); } var boolQuery = prepareBoolQueryForActiveAffiliation(query); @@ -128,13 +82,6 @@ public QueryBuilder filterBrowseQueryForActiveAffiliation(BrowseContext browseCo return boolQuery; } - private void removeOriginalSharedFilterFromQuery(QueryBuilder queryBuilder) { - if (queryBuilder instanceof BoolQueryBuilder bqb) { - bqb.filter().removeIf(filter -> filter instanceof TermQueryBuilder tqb - && tqb.fieldName().equals(BROWSE_SHARED_FILTER_KEY)); - } - } - public Set filterSubResourcesForConsortium( BrowseContext context, T resource, Function> subResourceExtractor) { @@ -149,19 +96,72 @@ public Set filterSubResourcesForConsortium( var sharedFilter = getBrowseSharedFilter(context); Predicate subResourcesFilter = sharedFilter.isPresent() && !sharedFilterValue(sharedFilter.get()) - ? subResource -> subResource.getTenantId().equals(contextTenantId) - : subResource -> subResource.getTenantId().equals(contextTenantId) || subResource.getShared(); + ? subResource -> subResource.getTenantId().equals(contextTenantId) + : subResource -> subResource.getTenantId().equals(contextTenantId) || subResource.getShared(); return subResources.stream() .filter(subResourcesFilter) .collect(Collectors.toSet()); } + private BoolQueryBuilder prepareBoolQueryForActiveAffiliation(QueryBuilder query) { + BoolQueryBuilder boolQuery; + if (query instanceof MatchAllQueryBuilder) { + boolQuery = boolQuery(); + } else if (query instanceof BoolQueryBuilder bq) { + boolQuery = bq; + } else { + boolQuery = boolQuery().must(query); + } + boolQuery.minimumShouldMatch(1); + return boolQuery; + } + + private void addActiveAffiliationClauses(BoolQueryBuilder boolQuery, String contextTenantId, + String centralTenantId, String resource) { + var affiliationShouldClauses = getAffiliationShouldClauses(contextTenantId, centralTenantId, resource); + if (boolQuery.should().isEmpty()) { + affiliationShouldClauses.forEach(boolQuery::should); + } else { + var innerBoolQuery = boolQuery(); + affiliationShouldClauses.forEach(innerBoolQuery::should); + boolQuery.must(innerBoolQuery); + } + } + + private LinkedList getAffiliationShouldClauses(String contextTenantId, String centralTenantId, + String resource) { + var affiliationShouldClauses = new LinkedList(); + addTenantIdAffiliationShouldClause(contextTenantId, centralTenantId, affiliationShouldClauses, + resource); + addSharedAffiliationShouldClause(affiliationShouldClauses, resource); + return affiliationShouldClauses; + } + + private void addTenantIdAffiliationShouldClause(String contextTenantId, String centralTenantId, + LinkedList affiliationShouldClauses, String resource) { + if (!contextTenantId.equals(centralTenantId)) { + affiliationShouldClauses.add(termQuery(getFieldForResource(TENANT_ID_FIELD_NAME, resource), contextTenantId)); + } + } + + private void addSharedAffiliationShouldClause(LinkedList affiliationShouldClauses, + String resource) { + affiliationShouldClauses.add(termQuery(getFieldForResource(SHARED_FIELD_NAME, resource), true)); + } + + private void removeOriginalSharedFilterFromQuery(QueryBuilder queryBuilder) { + if (queryBuilder instanceof BoolQueryBuilder bqb) { + bqb.filter().removeIf(filter -> filter instanceof TermQueryBuilder tqb + && tqb.fieldName().equals(BROWSE_SHARED_FILTER_KEY)); + } + } + private Optional getBrowseSharedFilter(BrowseContext context) { return context.getFilters().stream() .map(filter -> filter instanceof TermQueryBuilder termFilter && termFilter.fieldName().equals(BROWSE_SHARED_FILTER_KEY) - ? termFilter - : null) + ? termFilter + : null) .filter(Objects::nonNull) .findFirst(); } @@ -170,4 +170,11 @@ private boolean sharedFilterValue(TermQueryBuilder sharedQuery) { return sharedQuery.value() instanceof Boolean boolValue && boolValue || sharedQuery.value() instanceof String stringValue && Boolean.parseBoolean(stringValue); } + + private String getFieldForResource(String fieldName, String resourceName) { + if (resourceName.equals(CONTRIBUTOR_RESOURCE) || resourceName.equals(INSTANCE_SUBJECT_RESOURCE)) { + return "instances." + fieldName; + } + return fieldName; + } } diff --git a/src/main/resources/dev-log4j2.properties b/src/main/resources/dev-log4j2.properties index 3dde41d90..800c30571 100644 --- a/src/main/resources/dev-log4j2.properties +++ b/src/main/resources/dev-log4j2.properties @@ -15,8 +15,8 @@ appender.console.name = STDOUT appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{HH:mm:ss} [$${folio:requestid}] [$${folio:tenantid}] [$${folio:userid}] [$${folio:moduleid}] %-5p %-20.20C{1} %m%n -rootLogger.level = info -rootLogger.appenderRefs = info +rootLogger.level = debug +rootLogger.appenderRefs = debug rootLogger.appenderRef.stdout.ref = STDOUT # Debugging elasticsearch client diff --git a/src/main/resources/model/contributor.json b/src/main/resources/model/contributor.json index 1a89ff75f..78f8e47cc 100644 --- a/src/main/resources/model/contributor.json +++ b/src/main/resources/model/contributor.json @@ -27,11 +27,11 @@ }, "tenantId": { "index": "keyword", - "searchTypes": [ "filter" ] + "searchTypes": [ "facet", "filter" ] }, "shared": { "index": "bool", - "searchTypes": [ "filter" ], + "searchTypes": [ "facet", "filter" ], "default": false } } diff --git a/src/main/resources/model/instance.json b/src/main/resources/model/instance.json index dd3d4a08d..a558407df 100644 --- a/src/main/resources/model/instance.json +++ b/src/main/resources/model/instance.json @@ -435,6 +435,7 @@ "index": "keyword" }, "tenantId": { + "searchTypes": [ "facet", "filter" ], "index": "keyword" }, "sourceId": { diff --git a/src/main/resources/model/instance_subject.json b/src/main/resources/model/instance_subject.json index f27e45f1d..40d55cceb 100644 --- a/src/main/resources/model/instance_subject.json +++ b/src/main/resources/model/instance_subject.json @@ -21,11 +21,11 @@ }, "tenantId": { "index": "keyword", - "searchTypes": [ "filter" ] + "searchTypes": [ "facet", "filter" ] }, "shared": { "index": "bool", - "searchTypes": [ "filter" ], + "searchTypes": [ "facet", "filter" ], "default": false } } diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index 032d615a3..881a99257 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -608,7 +608,7 @@ components: updateIndexDynamicSettingsRequest: $ref: schemas/request/updateIndexDynamicSettingsRequest.json RecordType: - enum: [ instances, authorities, contributors ] + enum: [ instances, authorities, contributors, subjects ] type: string CallNumberType: enum: [ lc, dewey, nlm, sudoc, other, local ] diff --git a/src/test/java/org/folio/search/controller/BrowseContributorConsortiumIT.java b/src/test/java/org/folio/search/controller/BrowseContributorConsortiumIT.java index 588bf58b5..6f4861cde 100644 --- a/src/test/java/org/folio/search/controller/BrowseContributorConsortiumIT.java +++ b/src/test/java/org/folio/search/controller/BrowseContributorConsortiumIT.java @@ -8,14 +8,19 @@ import static org.awaitility.Durations.ONE_SECOND; import static org.folio.search.support.base.ApiEndpoints.instanceContributorBrowsePath; import static org.folio.search.support.base.ApiEndpoints.instanceSearchPath; +import static org.folio.search.support.base.ApiEndpoints.recordFacetsPath; import static org.folio.search.utils.SearchUtils.getIndexName; import static org.folio.search.utils.TestConstants.CONSORTIUM_TENANT_ID; import static org.folio.search.utils.TestConstants.TENANT_ID; import static org.folio.search.utils.TestUtils.array; import static org.folio.search.utils.TestUtils.asJsonString; import static org.folio.search.utils.TestUtils.contributorBrowseItem; +import static org.folio.search.utils.TestUtils.facet; +import static org.folio.search.utils.TestUtils.facetItem; +import static org.folio.search.utils.TestUtils.mapOf; import static org.folio.search.utils.TestUtils.parseResponse; import static org.folio.search.utils.TestUtils.randomId; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.opensearch.index.query.QueryBuilders.matchAllQuery; import static org.opensearch.search.builder.SearchSourceBuilder.searchSource; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -25,12 +30,17 @@ import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.function.Consumer; +import java.util.stream.Stream; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; import org.folio.search.domain.dto.Contributor; +import org.folio.search.domain.dto.Facet; +import org.folio.search.domain.dto.FacetResult; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.InstanceContributorBrowseResult; +import org.folio.search.domain.dto.RecordType; import org.folio.search.support.base.BaseIntegrationTest; import org.folio.search.utils.SearchUtils; import org.folio.spring.test.type.IntegrationTest; @@ -38,7 +48,11 @@ import org.folio.tenant.domain.dto.TenantAttributes; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opensearch.action.search.SearchRequest; import org.opensearch.client.RequestOptions; import org.opensearch.client.RestHighLevelClient; @@ -78,11 +92,100 @@ static void prepare(@Autowired RestHighLevelClient restHighLevelClient) throws I }); } + //todo: move 4 methods below to consortium integration test base in a scope of MSEARCH-562 + @SneakyThrows + protected static void setUpTenant(String tenantName, int expectedCount, Instance... instances) { + setUpTenant(tenantName, instanceSearchPath(), () -> { }, asList(instances), expectedCount, + instance -> inventoryApi.createInstance(tenantName, instance)); + } + + @SneakyThrows + private static void setUpTenant(String tenant, String validationPath, Runnable postInitAction, + List records, Integer expectedCount, Consumer consumer) { + enableTenant(tenant); + postInitAction.run(); + saveRecords(tenant, validationPath, records, expectedCount, consumer); + } + + @SneakyThrows + protected static void enableTenant(String tenant) { + var tenantAttributes = new TenantAttributes().moduleTo("mod-search"); + tenantAttributes.addParametersItem(new Parameter("centralTenantId").value(CONSORTIUM_TENANT_ID)); + + mockMvc.perform(post("/_/tenant", randomId()) + .content(asJsonString(tenantAttributes)) + .headers(defaultHeaders(tenant)) + .contentType(APPLICATION_JSON)) + .andExpect(status().isNoContent()); + } + @AfterAll static void cleanUp() { removeTenant(); } + @MethodSource("facetQueriesProvider") + @ParameterizedTest(name = "[{index}] query={0}, facets={1}") + @DisplayName("getFacetsForContributors_parameterized") + void getFacetsForContributors_parameterized(String query, String[] facets, Map expected) { + var actual = parseResponse(doGet(recordFacetsPath(RecordType.CONTRIBUTORS, query, facets)), FacetResult.class); + + expected.forEach((facetName, expectedFacet) -> { + var actualFacet = actual.getFacets().get(facetName); + + assertThat(actualFacet).isNotNull(); + assertThat(actualFacet.getValues()) + .containsExactlyInAnyOrderElementsOf(expectedFacet.getValues()); + }); + } + + @Test + void browseByContributor_shared() { + var request = get(instanceContributorBrowsePath()).param("query", + "(" + prepareQuery("name >= {value} or name < {value}", '"' + "Bon Jovi" + '"') + ") " + + "and instances.shared==true").param("limit", "5"); + + var actual = parseResponse(doGet(request), InstanceContributorBrowseResult.class); + var expected = new InstanceContributorBrowseResult().totalRecords(12).prev(null).next("George Harrison").items( + List.of( + contributorBrowseItem(1, "Anthony Kiedis", NAME_TYPE_IDS[0], AUTHORITY_IDS[1], TYPE_IDS[0]), + contributorBrowseItem(1, "Anthony Kiedis", NAME_TYPE_IDS[1], AUTHORITY_IDS[1], TYPE_IDS[2]), + contributorBrowseItem(2, true, "Bon Jovi", NAME_TYPE_IDS[0], AUTHORITY_IDS[0], + TYPE_IDS[0], TYPE_IDS[1], TYPE_IDS[2]), + contributorBrowseItem(1, true, "Bon Jovi", NAME_TYPE_IDS[1], AUTHORITY_IDS[1], TYPE_IDS[0]), + contributorBrowseItem(2, "George Harrison", NAME_TYPE_IDS[1], AUTHORITY_IDS[0], TYPE_IDS[2]))); + + assertThat(actual).isEqualTo(expected); + } + + @Test + void browseByContributor_local() { + var request = get(instanceContributorBrowsePath()).param("query", + "(" + prepareQuery("name >= {value} or name < {value}", '"' + "Bon Jovi" + '"') + ") " + + "and instances.shared==false").param("limit", "5"); + + var actual = parseResponse(doGet(request), InstanceContributorBrowseResult.class); + var expected = new InstanceContributorBrowseResult().totalRecords(8).prev(null).next("John Lennon").items( + List.of( + contributorBrowseItem(1, true, "Bon Jovi", NAME_TYPE_IDS[0], AUTHORITY_IDS[0], + TYPE_IDS[1], TYPE_IDS[2]), + contributorBrowseItem(2, "George Harrison", NAME_TYPE_IDS[1], AUTHORITY_IDS[0], TYPE_IDS[2]), + contributorBrowseItem(2, "John Lennon", NAME_TYPE_IDS[2], AUTHORITY_IDS[1], TYPE_IDS[0]))); + + assertThat(actual).isEqualTo(expected); + } + + + private static Stream facetQueriesProvider() { + return Stream.of( + arguments("cql.allRecords=1", array("instances.shared"), mapOf("instances.shared", + facet(facetItem("false", 8), facetItem("true", 5)))), + arguments("cql.allRecords=1", array("instances.tenantId"), + mapOf("instances.tenantId", facet(facetItem(TENANT_ID, 8), + facetItem(CONSORTIUM_TENANT_ID, 5)))) + ); + } + private static Instance[] instancesMember() { return contributorBrowseInstanceData().subList(3, 7).stream() .map(BrowseContributorConsortiumIT::instance).toArray(Instance[]::new); @@ -145,69 +248,6 @@ private static Contributor contributor(String name, String nameTypeId, String au .authorityId(authorityId); } - @Test - void browseByContributor_shared() { - var request = get(instanceContributorBrowsePath()).param("query", - "(" + prepareQuery("name >= {value} or name < {value}", '"' + "Bon Jovi" + '"') + ") " - + "and instances.shared==true").param("limit", "5"); - - var actual = parseResponse(doGet(request), InstanceContributorBrowseResult.class); - var expected = new InstanceContributorBrowseResult().totalRecords(12).prev(null).next("George Harrison").items( - List.of( - contributorBrowseItem(1, "Anthony Kiedis", NAME_TYPE_IDS[0], AUTHORITY_IDS[1], TYPE_IDS[0]), - contributorBrowseItem(1, "Anthony Kiedis", NAME_TYPE_IDS[1], AUTHORITY_IDS[1], TYPE_IDS[2]), - contributorBrowseItem(2, true, "Bon Jovi", NAME_TYPE_IDS[0], AUTHORITY_IDS[0], - TYPE_IDS[0], TYPE_IDS[1], TYPE_IDS[2]), - contributorBrowseItem(1, true, "Bon Jovi", NAME_TYPE_IDS[1], AUTHORITY_IDS[1], TYPE_IDS[0]), - contributorBrowseItem(2, "George Harrison", NAME_TYPE_IDS[1], AUTHORITY_IDS[0], TYPE_IDS[2]))); - - assertThat(actual).isEqualTo(expected); - } - - @Test - void browseByContributor_local() { - var request = get(instanceContributorBrowsePath()).param("query", - "(" + prepareQuery("name >= {value} or name < {value}", '"' + "Bon Jovi" + '"') + ") " - + "and instances.shared==false").param("limit", "5"); - - var actual = parseResponse(doGet(request), InstanceContributorBrowseResult.class); - var expected = new InstanceContributorBrowseResult().totalRecords(8).prev(null).next("John Lennon").items( - List.of( - contributorBrowseItem(1, true, "Bon Jovi", NAME_TYPE_IDS[0], AUTHORITY_IDS[0], - TYPE_IDS[1], TYPE_IDS[2]), - contributorBrowseItem(2, "George Harrison", NAME_TYPE_IDS[1], AUTHORITY_IDS[0], TYPE_IDS[2]), - contributorBrowseItem(2, "John Lennon", NAME_TYPE_IDS[2], AUTHORITY_IDS[1], TYPE_IDS[0]))); - - assertThat(actual).isEqualTo(expected); - } - - //todo: move 4 methods below to consortium integration test base in a scope of MSEARCH-562 - @SneakyThrows - protected static void setUpTenant(String tenantName, int expectedCount, Instance... instances) { - setUpTenant(tenantName, instanceSearchPath(), () -> { }, asList(instances), expectedCount, - instance -> inventoryApi.createInstance(tenantName, instance)); - } - - @SneakyThrows - private static void setUpTenant(String tenant, String validationPath, Runnable postInitAction, - List records, Integer expectedCount, Consumer consumer) { - enableTenant(tenant); - postInitAction.run(); - saveRecords(tenant, validationPath, records, expectedCount, consumer); - } - - @SneakyThrows - protected static void enableTenant(String tenant) { - var tenantAttributes = new TenantAttributes().moduleTo("mod-search"); - tenantAttributes.addParametersItem(new Parameter("centralTenantId").value(CONSORTIUM_TENANT_ID)); - - mockMvc.perform(post("/_/tenant", randomId()) - .content(asJsonString(tenantAttributes)) - .headers(defaultHeaders(tenant)) - .contentType(APPLICATION_JSON)) - .andExpect(status().isNoContent()); - } - private static void saveRecords(String tenant, String validationPath, List records, Integer expectedCount, Consumer consumer) { records.forEach(consumer); diff --git a/src/test/java/org/folio/search/controller/BrowseSubjectConsortiumIT.java b/src/test/java/org/folio/search/controller/BrowseSubjectConsortiumIT.java index 86586383a..b3f0df270 100644 --- a/src/test/java/org/folio/search/controller/BrowseSubjectConsortiumIT.java +++ b/src/test/java/org/folio/search/controller/BrowseSubjectConsortiumIT.java @@ -9,13 +9,19 @@ import static org.folio.search.model.Pair.pair; import static org.folio.search.support.base.ApiEndpoints.instanceSearchPath; import static org.folio.search.support.base.ApiEndpoints.instanceSubjectBrowsePath; +import static org.folio.search.support.base.ApiEndpoints.recordFacetsPath; import static org.folio.search.utils.SearchUtils.getIndexName; import static org.folio.search.utils.TestConstants.CONSORTIUM_TENANT_ID; import static org.folio.search.utils.TestConstants.TENANT_ID; +import static org.folio.search.utils.TestUtils.array; import static org.folio.search.utils.TestUtils.asJsonString; +import static org.folio.search.utils.TestUtils.facet; +import static org.folio.search.utils.TestUtils.facetItem; +import static org.folio.search.utils.TestUtils.mapOf; import static org.folio.search.utils.TestUtils.parseResponse; import static org.folio.search.utils.TestUtils.randomId; import static org.folio.search.utils.TestUtils.subjectBrowseItem; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.opensearch.index.query.QueryBuilders.matchAllQuery; import static org.opensearch.search.builder.SearchSourceBuilder.searchSource; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -24,10 +30,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.SneakyThrows; +import org.folio.search.domain.dto.Facet; +import org.folio.search.domain.dto.FacetResult; import org.folio.search.domain.dto.Instance; +import org.folio.search.domain.dto.RecordType; import org.folio.search.domain.dto.Subject; import org.folio.search.domain.dto.SubjectBrowseResult; import org.folio.search.model.Pair; @@ -38,10 +49,15 @@ import org.folio.tenant.domain.dto.TenantAttributes; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opensearch.action.search.SearchRequest; import org.opensearch.client.RequestOptions; import org.opensearch.client.RestHighLevelClient; +import org.opensearch.search.SearchHit; import org.springframework.beans.factory.annotation.Autowired; @IntegrationTest @@ -54,15 +70,25 @@ class BrowseSubjectConsortiumIT extends BaseIntegrationTest { @BeforeAll static void prepare(@Autowired RestHighLevelClient restHighLevelClient) { - setUpTenant(CONSORTIUM_TENANT_ID, INSTANCES_CENTRAL.length, INSTANCES_CENTRAL); - setUpTenant(TENANT_ID, INSTANCES_CENTRAL.length + INSTANCES_MEMBER.length, INSTANCES_MEMBER); + setUpTenant(CONSORTIUM_TENANT_ID); + setUpTenant(TENANT_ID); + saveRecords(CONSORTIUM_TENANT_ID, instanceSearchPath(), asList(INSTANCES_CENTRAL), + INSTANCES_CENTRAL.length, + instance -> inventoryApi.createInstance(CONSORTIUM_TENANT_ID, instance)); + saveRecords(TENANT_ID, instanceSearchPath(), asList(INSTANCES_MEMBER), + INSTANCES_CENTRAL.length + INSTANCES_MEMBER.length, + instance -> inventoryApi.createInstance(TENANT_ID, instance)); await().atMost(ONE_MINUTE).pollInterval(ONE_SECOND).untilAsserted(() -> { var searchRequest = new SearchRequest() - .source(searchSource().query(matchAllQuery()).trackTotalHits(true).from(0).size(0)) + .source(searchSource().query(matchAllQuery()).trackTotalHits(true).from(0).size(100)) .indices(getIndexName(SearchUtils.INSTANCE_SUBJECT_RESOURCE, centralTenant)); var searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); assertThat(searchResponse.getHits().getTotalHits().value).isEqualTo(23); + System.out.println("Resulted subjects"); + for (SearchHit hit : searchResponse.getHits()) { + System.out.println(hit.getSourceAsMap()); + } }); } @@ -116,6 +142,16 @@ private static List> subjectBrowseInstanceData() { ); } + private static Stream facetQueriesProvider() { + return Stream.of( + arguments("cql.allRecords=1", array("instances.shared"), mapOf("instances.shared", + facet(facetItem("false", 15), facetItem("true", 10)))), + arguments("cql.allRecords=1", array("instances.tenantId"), + mapOf("instances.tenantId", facet(facetItem(TENANT_ID, 15), + facetItem(CONSORTIUM_TENANT_ID, 10)))) + ); + } + @Test void browseBySubject_browsingAround_shared() { var request = get(instanceSubjectBrowsePath()) @@ -154,6 +190,21 @@ void browseBySubject_browsingAround_local() { subjectBrowseItem(1, "Science--Philosophy")))); } + @MethodSource("facetQueriesProvider") + @ParameterizedTest(name = "[{index}] query={0}, facets={1}") + @DisplayName("getFacetsForSubjects_parameterized") + void getFacetsForSubjects_parameterized(String query, String[] facets, Map expected) { + var actual = parseResponse(doGet(recordFacetsPath(RecordType.SUBJECTS, query, facets)), FacetResult.class); + + expected.forEach((facetName, expectedFacet) -> { + var actualFacet = actual.getFacets().get(facetName); + + assertThat(actualFacet).isNotNull(); + assertThat(actualFacet.getValues()) + .containsExactlyInAnyOrderElementsOf(expectedFacet.getValues()); + }); + } + //todo: move 4 methods below to consortium integration test base in a scope of MSEARCH-562 @SneakyThrows protected static void setUpTenant(String tenantName, int expectedCount, Instance... instances) { diff --git a/src/test/java/org/folio/search/cql/CqlSearchQueryConverterTest.java b/src/test/java/org/folio/search/cql/CqlSearchQueryConverterTest.java index fc27b670b..48d664535 100644 --- a/src/test/java/org/folio/search/cql/CqlSearchQueryConverterTest.java +++ b/src/test/java/org/folio/search/cql/CqlSearchQueryConverterTest.java @@ -84,7 +84,7 @@ class CqlSearchQueryConverterTest { void setUp() { when(searchFieldProvider.getModifiedField(any(), any())).thenAnswer(f -> f.getArguments()[0]); doAnswer(invocation -> invocation.getArgument(0)) - .when(consortiumSearchHelper).filterQueryForActiveAffiliation(any()); + .when(consortiumSearchHelper).filterQueryForActiveAffiliation(any(), any()); } @MethodSource("convertCqlQueryDataProvider") @@ -362,7 +362,7 @@ void convertForConsortia_positive_whenConsortiaDisabled() { @Test void convertForConsortia_positive() { var consortiumQueryMock = disMaxQuery(); - when(consortiumSearchHelper.filterQueryForActiveAffiliation(any())).thenReturn(consortiumQueryMock); + when(consortiumSearchHelper.filterQueryForActiveAffiliation(any(), any())).thenReturn(consortiumQueryMock); doReturn(Optional.of(filterField())).when(searchFieldProvider).getPlainFieldByPath(RESOURCE_NAME, "f1"); var cqlQuery = "f1==value"; var actual = cqlSearchQueryConverter.convertForConsortia(cqlQuery, RESOURCE_NAME); diff --git a/src/test/java/org/folio/search/service/browse/AuthorityBrowseServiceTest.java b/src/test/java/org/folio/search/service/browse/AuthorityBrowseServiceTest.java index f9c1b3241..d51b2226d 100644 --- a/src/test/java/org/folio/search/service/browse/AuthorityBrowseServiceTest.java +++ b/src/test/java/org/folio/search/service/browse/AuthorityBrowseServiceTest.java @@ -76,7 +76,7 @@ class AuthorityBrowseServiceTest { @BeforeEach void setUp() { doAnswer(invocation -> invocation.getArgument(0)) - .when(consortiumSearchHelper).filterQueryForActiveAffiliation(any()); + .when(consortiumSearchHelper).filterQueryForActiveAffiliation(any(), any()); authorityBrowseService.setDocumentConverter(documentConverter); authorityBrowseService.setSearchRepository(searchRepository); authorityBrowseService.setBrowseContextProvider(browseContextProvider); @@ -324,7 +324,7 @@ void browse_positive_aroundIncludingMissingAnchorWithoutHighlighting() { @Test void getSearchQuery_positive_consortium() { var query = disMaxQuery(); - when(consortiumSearchHelper.filterQueryForActiveAffiliation(any())).thenReturn(query); + when(consortiumSearchHelper.filterQueryForActiveAffiliation(any(), any())).thenReturn(query); var actual = authorityBrowseService.getSearchQuery( BrowseRequest.builder().targetField("test").build(), @@ -335,7 +335,7 @@ void getSearchQuery_positive_consortium() { @Test void getAnchorSearchQuery_positive_consortium() { var query = disMaxQuery(); - when(consortiumSearchHelper.filterQueryForActiveAffiliation(any())).thenReturn(query); + when(consortiumSearchHelper.filterQueryForActiveAffiliation(any(), any())).thenReturn(query); var actual = authorityBrowseService.getSearchQuery( BrowseRequest.builder().targetField("test").build(), diff --git a/src/test/java/org/folio/search/service/browse/CallNumberBrowseQueryProviderTest.java b/src/test/java/org/folio/search/service/browse/CallNumberBrowseQueryProviderTest.java index 5000947d3..27fcc3c70 100644 --- a/src/test/java/org/folio/search/service/browse/CallNumberBrowseQueryProviderTest.java +++ b/src/test/java/org/folio/search/service/browse/CallNumberBrowseQueryProviderTest.java @@ -66,7 +66,7 @@ class CallNumberBrowseQueryProviderTest { @BeforeEach public void setUpMocks() { lenient().doAnswer(invocation -> invocation.getArgument(0)) - .when(consortiumSearchHelper).filterQueryForActiveAffiliation(any()); + .when(consortiumSearchHelper).filterQueryForActiveAffiliation(any(), any()); } @Test @@ -122,7 +122,7 @@ void get_positive_forwardWithEnabledOptimization() { @Test void get_positive_forwardConsortium() { var query = disMaxQuery(); - when(consortiumSearchHelper.filterQueryForActiveAffiliation(any())).thenReturn(query); + when(consortiumSearchHelper.filterQueryForActiveAffiliation(any(), any())).thenReturn(query); when(searchFieldProvider.getSourceFields(RESOURCE_NAME, CN_BROWSE)).thenReturn(new String[] {"id", "title"}); var context = BrowseContext.builder().anchor(ANCHOR).succeedingLimit(5).build(); diff --git a/src/test/java/org/folio/search/service/browse/ContributorBrowseServiceTest.java b/src/test/java/org/folio/search/service/browse/ContributorBrowseServiceTest.java index 4bbbfb911..dc6dc4bcd 100644 --- a/src/test/java/org/folio/search/service/browse/ContributorBrowseServiceTest.java +++ b/src/test/java/org/folio/search/service/browse/ContributorBrowseServiceTest.java @@ -79,7 +79,7 @@ void getSearchQuery_positive(Boolean isBrowsingForward) { var browseContext = BrowseContext.builder().anchor("test").succeedingLimit(1).precedingLimit(1).build(); var queryMock = disMaxQuery(); - when(consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(eq(browseContext), any())) + when(consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(eq(browseContext), any(), any())) .thenReturn(queryMock); var result = service.getSearchQuery(browseRequest, browseContext, isBrowsingForward); @@ -94,7 +94,7 @@ void getAnchorSearchQuery_positive() { .anchor("test").succeedingQuery(rangeQuery("test")).succeedingLimit(1).build(); var queryMock = disMaxQuery(); - when(consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(eq(browseContext), any())) + when(consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(eq(browseContext), any(), any())) .thenReturn(queryMock); var result = service.getAnchorSearchQuery(browseRequest, browseContext); diff --git a/src/test/java/org/folio/search/service/browse/SubjectBrowseServiceTest.java b/src/test/java/org/folio/search/service/browse/SubjectBrowseServiceTest.java index 9f2d471b6..45a3cb11a 100644 --- a/src/test/java/org/folio/search/service/browse/SubjectBrowseServiceTest.java +++ b/src/test/java/org/folio/search/service/browse/SubjectBrowseServiceTest.java @@ -72,7 +72,7 @@ class SubjectBrowseServiceTest { @BeforeEach public void setUpMocks() { doAnswer(invocation -> invocation.getArgument(1)) - .when(consortiumSearchHelper).filterBrowseQueryForActiveAffiliation(any(), any()); + .when(consortiumSearchHelper).filterBrowseQueryForActiveAffiliation(any(), any(), any()); lenient().doAnswer(invocation -> ((SubjectResource) invocation.getArgument(1)).getInstances()) .when(consortiumSearchHelper).filterSubResourcesForConsortium(any(), any(), any()); } diff --git a/src/test/java/org/folio/search/service/consortium/ConsortiumSearchHelperTest.java b/src/test/java/org/folio/search/service/consortium/ConsortiumSearchHelperTest.java index 5da1ecf2e..fa23bab5d 100644 --- a/src/test/java/org/folio/search/service/consortium/ConsortiumSearchHelperTest.java +++ b/src/test/java/org/folio/search/service/consortium/ConsortiumSearchHelperTest.java @@ -2,8 +2,9 @@ import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; -import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.assertj.core.api.Assertions.assertThat; +import static org.folio.search.utils.SearchUtils.CONTRIBUTOR_RESOURCE; +import static org.folio.search.utils.SearchUtils.INSTANCE_SUBJECT_RESOURCE; import static org.folio.search.utils.TestConstants.CONSORTIUM_TENANT_ID; import static org.folio.search.utils.TestConstants.TENANT_ID; import static org.mockito.ArgumentMatchers.any; @@ -55,9 +56,9 @@ void filterQueryForActiveAffiliation_positive_basic() { when(context.getTenantId()).thenReturn(TENANT_ID); when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.of(CONSORTIUM_TENANT_ID)); - consortiumSearchHelper.filterQueryForActiveAffiliation(query); + consortiumSearchHelper.filterQueryForActiveAffiliation(query, "resource"); - verify(consortiumSearchHelper).filterQueryForActiveAffiliation(query, TENANT_ID, CONSORTIUM_TENANT_ID); + verify(consortiumSearchHelper).filterQueryForActiveAffiliation(query, TENANT_ID, CONSORTIUM_TENANT_ID, "resource"); } @Test @@ -66,10 +67,10 @@ void filterQueryForActiveAffiliation_positive_basicNotConsortiumTenant() { when(context.getTenantId()).thenReturn(TENANT_ID); when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.empty()); - var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query); + var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query, "resource"); assertThat(actual).isEqualTo(query); - verify(consortiumSearchHelper, times(0)).filterQueryForActiveAffiliation(any(), any(), any()); + verify(consortiumSearchHelper, times(0)).filterQueryForActiveAffiliation(any(), any(), any(), any()); } @Test @@ -82,7 +83,7 @@ void filterQueryForActiveAffiliation_positive_basicConsortiumCentralTenant() { when(context.getTenantId()).thenReturn(TENANT_ID); when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.of(TENANT_ID)); - var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query); + var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query, "resource"); assertThat(actual).isEqualTo(expected); } @@ -95,10 +96,11 @@ void filterQueryForActiveAffiliation_positive_noPrefix() { .should(termQuery("tenantId", TENANT_ID)) .should(termQuery("shared", true)); - var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query, TENANT_ID, CONSORTIUM_TENANT_ID); + var actual = + consortiumSearchHelper.filterQueryForActiveAffiliation(query, TENANT_ID, CONSORTIUM_TENANT_ID, "resource"); assertThat(actual).isEqualTo(expected); - verify(consortiumSearchHelper).filterQueryForActiveAffiliation(EMPTY, query, TENANT_ID, CONSORTIUM_TENANT_ID); + verify(consortiumSearchHelper).filterQueryForActiveAffiliation(query, TENANT_ID, CONSORTIUM_TENANT_ID, "resource"); } @Test @@ -109,8 +111,8 @@ void filterQueryForActiveAffiliation_positive() { .should(termQuery(TENANT_ID_FIELD, TENANT_ID)) .should(termQuery(SHARED_FIELD, true)); - var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(SUBRESOURCE_PREFIX, query, TENANT_ID, - CONSORTIUM_TENANT_ID); + var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query, TENANT_ID, CONSORTIUM_TENANT_ID, + CONTRIBUTOR_RESOURCE); assertThat(actual).isEqualTo(expected); } @@ -123,8 +125,8 @@ void filterQueryForActiveAffiliation_positive_boolQuery() { .should(termQuery(TENANT_ID_FIELD, TENANT_ID)) .should(termQuery(SHARED_FIELD, true)); - var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(SUBRESOURCE_PREFIX, query, TENANT_ID, - CONSORTIUM_TENANT_ID); + var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query, TENANT_ID, + CONSORTIUM_TENANT_ID, INSTANCE_SUBJECT_RESOURCE); assertThat(actual).isEqualTo(expected); } @@ -139,8 +141,8 @@ void filterQueryForActiveAffiliation_positive_boolQueryWithShould() { .should(termQuery(TENANT_ID_FIELD, TENANT_ID)) .should(termQuery(SHARED_FIELD, true))); - var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(SUBRESOURCE_PREFIX, query, TENANT_ID, - CONSORTIUM_TENANT_ID); + var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query, TENANT_ID, + CONSORTIUM_TENANT_ID, INSTANCE_SUBJECT_RESOURCE); assertThat(actual).isEqualTo(expected); } @@ -154,8 +156,8 @@ void filterQueryForActiveAffiliation_positive_otherQuery() { .should(termQuery(TENANT_ID_FIELD, TENANT_ID)) .should(termQuery(SHARED_FIELD, true)); - var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(SUBRESOURCE_PREFIX, query, TENANT_ID, - CONSORTIUM_TENANT_ID); + var actual = consortiumSearchHelper.filterQueryForActiveAffiliation(query, TENANT_ID, + CONSORTIUM_TENANT_ID, CONTRIBUTOR_RESOURCE); assertThat(actual).isEqualTo(expected); } @@ -168,7 +170,8 @@ void filterBrowseQueryForActiveAffiliation_positive_notConsortiumTenant() { when(context.getTenantId()).thenReturn(TENANT_ID); when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.empty()); - var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query); + var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query, + "resource"); assertThat(actual).isEqualTo(query); assertThat(browseContext.getFilters()).isEmpty(); @@ -182,9 +185,10 @@ void filterBrowseQueryForActiveAffiliation_positive_consortiumCentralTenant() { .must(termQuery(TENANT_ID_FIELD, TENANT_ID)); when(context.getTenantId()).thenReturn(TENANT_ID); - when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.ofNullable(TENANT_ID)); + when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.of(TENANT_ID)); - var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query); + var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query, + "resource"); assertThat(actual).isEqualTo(expected); } @@ -197,10 +201,10 @@ void filterBrowseQueryForActiveAffiliation_positive_shared() { when(context.getTenantId()).thenReturn(TENANT_ID); when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.of(CONSORTIUM_TENANT_ID)); - consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query); + consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query, "resource"); - verify(consortiumSearchHelper).filterQueryForActiveAffiliation("instances.", query, TENANT_ID, - CONSORTIUM_TENANT_ID); + verify(consortiumSearchHelper).filterQueryForActiveAffiliation(query, TENANT_ID, + CONSORTIUM_TENANT_ID, "resource"); } @Test @@ -213,7 +217,8 @@ void filterBrowseQueryForActiveAffiliation_positive_local() { when(context.getTenantId()).thenReturn(TENANT_ID); when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.of(CONSORTIUM_TENANT_ID)); - var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query); + var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query, + "resource"); assertThat(actual).isEqualTo(expected); assertThat(browseContext.getFilters()).isNotEmpty(); @@ -232,7 +237,8 @@ void filterBrowseQueryForActiveAffiliation_positive_localWithShould() { when(context.getTenantId()).thenReturn(TENANT_ID); when(tenantService.getCentralTenant(TENANT_ID)).thenReturn(Optional.of(CONSORTIUM_TENANT_ID)); - var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query); + var actual = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(browseContext, query, + "resource"); assertThat(actual).isEqualTo(expected); assertThat(browseContext.getFilters()).isNotEmpty(); diff --git a/src/test/java/org/folio/search/support/api/InventoryApi.java b/src/test/java/org/folio/search/support/api/InventoryApi.java index d27c81b76..1a5ff1288 100644 --- a/src/test/java/org/folio/search/support/api/InventoryApi.java +++ b/src/test/java/org/folio/search/support/api/InventoryApi.java @@ -26,12 +26,14 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.ResourceEvent; import org.springframework.kafka.core.KafkaTemplate; +@Log4j2 @RequiredArgsConstructor public class InventoryApi { @@ -51,7 +53,15 @@ public void createInstance(String tenantId, Map instance) { INSTANCE_STORE.computeIfAbsent(tenantId, k -> new LinkedHashMap<>()).put(instanceId, instance); var instanceEvent = kafkaResourceEvent(tenantId, CREATE, instance, null); - kafkaTemplate.send(inventoryInstanceTopic(tenantId), instanceId, instanceEvent); + kafkaTemplate.send(inventoryInstanceTopic(tenantId), instanceId, instanceEvent) + .whenComplete((stringResourceEventSendResult, throwable) -> { + if (throwable != null) { + log.error("Failed sending instance resource event", throwable); + } else { + var topic = stringResourceEventSendResult.getRecordMetadata().topic(); + log.info("Succeeded sending instance resource event to topic: {}", topic); + } + }); createNestedResources(instance, INSTANCE_HOLDING_FIELD_NAME, hr -> createHolding(tenantId, instanceId, hr)); createNestedResources(instance, INSTANCE_ITEM_FIELD_NAME, item -> createItem(tenantId, instanceId, item)); }