Skip to content

Commit

Permalink
feat(consortium): Implement active affiliation context for browse (#408)
Browse files Browse the repository at this point in the history
- Move consortium filtering logic into a new class ConsortiumSerachHelper
- Add active affiliation logic for browse authorities/call numbers/subjects/contributors
- Add active affiliation logic to sub-resource result filtration on subjects/contributors

Closes: MSEARCH-580
  • Loading branch information
viacheslavkol authored Sep 6, 2023
1 parent ccdea1b commit e4b567a
Show file tree
Hide file tree
Showing 18 changed files with 720 additions and 190 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* Add tenantId/shared fields to contributors/subjects ([MSEARCH-551](https://issues.folio.org/browse/MSEARCH-551))
* 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))

### Bug fixes
* Fix bug when number of titles response is greater than real ([MSEARCH-526](https://issues.folio.org/browse/MSEARCH-526))
Expand Down
71 changes: 3 additions & 68 deletions src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,17 @@
import static org.folio.search.utils.SearchQueryUtils.isBoolQuery;
import static org.folio.search.utils.SearchQueryUtils.isDisjunctionFilterQuery;
import static org.folio.search.utils.SearchQueryUtils.isFilterQuery;
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;
import static org.opensearch.index.query.QueryBuilders.termQuery;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
import org.folio.search.model.types.SearchType;
import org.folio.search.service.consortium.ConsortiumTenantService;
import org.folio.search.service.consortium.ConsortiumSearchHelper;
import org.folio.search.service.metadata.SearchFieldProvider;
import org.folio.spring.FolioExecutionContext;
import org.opensearch.index.query.BoolQueryBuilder;
import org.opensearch.index.query.MatchAllQueryBuilder;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.springframework.stereotype.Component;
Expand All @@ -43,8 +37,7 @@ public class CqlSearchQueryConverter {
private final CqlSortProvider cqlSortProvider;
private final SearchFieldProvider searchFieldProvider;
private final CqlTermQueryConverter cqlTermQueryConverter;
private final FolioExecutionContext folioExecutionContext;
private final ConsortiumTenantService consortiumTenantService;
private final ConsortiumSearchHelper consortiumSearchHelper;

/**
* Converts given CQL search query value to the elasticsearch {@link SearchSourceBuilder} object.
Expand All @@ -66,9 +59,6 @@ public SearchSourceBuilder convert(String query, String resource) {
return queryBuilder.query(enhancedQuery);
}

//todo(MSEARCH-576): may be reworked after implemented for browse/streamIds.
// Implemented separately because it crashes 'browse/streamIds' functionality.

/**
* Converts given CQL search query value to the elasticsearch {@link SearchSourceBuilder} object.
* Wraps base 'convert' and adds tenantId+shared filter in case of consortia mode
Expand All @@ -79,7 +69,7 @@ public SearchSourceBuilder convert(String query, String resource) {
*/
public SearchSourceBuilder convertForConsortia(String query, String resource) {
var sourceBuilder = convert(query, resource);
var queryBuilder = filterForActiveAffiliation(sourceBuilder.query());
var queryBuilder = consortiumSearchHelper.filterQueryForActiveAffiliation(sourceBuilder.query());

return sourceBuilder.query(queryBuilder);
}
Expand Down Expand Up @@ -156,61 +146,6 @@ private BoolQueryBuilder flattenBoolQuery(CQLBooleanNode node, String resource,
return boolQuery;
}

private QueryBuilder filterForActiveAffiliation(QueryBuilder query) {
var contextTenantId = folioExecutionContext.getTenantId();
var centralTenantId = consortiumTenantService.getCentralTenant(contextTenantId);
if (centralTenantId.isEmpty()) {
return query;
}

var boolQuery = prepareBoolQueryForActiveAffiliation(query);
addActiveAffiliationClauses(boolQuery, contextTenantId, centralTenantId.get());

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(BoolQueryBuilder boolQuery, String contextTenantId, String centralTenantId) {
var affiliationShouldClauses = getAffiliationShouldClauses(contextTenantId, centralTenantId);
if (boolQuery.should().isEmpty()) {
affiliationShouldClauses.forEach(boolQuery::should);
} else {
var innerBoolQuery = boolQuery();
affiliationShouldClauses.forEach(innerBoolQuery::should);
boolQuery.must(innerBoolQuery);
}
}

private LinkedList<QueryBuilder> getAffiliationShouldClauses(String contextTenantId, String centralTenantId) {
var affiliationShouldClauses = new LinkedList<QueryBuilder>();
addTenantIdAffiliationShouldClause(contextTenantId, centralTenantId, affiliationShouldClauses);
addSharedAffiliationShouldClause(affiliationShouldClauses);
return affiliationShouldClauses;
}

private void addTenantIdAffiliationShouldClause(String contextTenantId, String centralTenantId,
LinkedList<QueryBuilder> affiliationShouldClauses) {
if (!contextTenantId.equals(centralTenantId)) {
affiliationShouldClauses.add(termQuery(TENANT_ID_FIELD_NAME, contextTenantId));
}
}

private void addSharedAffiliationShouldClause(LinkedList<QueryBuilder> affiliationShouldClauses) {
affiliationShouldClauses.add(termQuery(SHARED_FIELD_NAME, true));
}

private QueryBuilder enhanceQuery(QueryBuilder query, String resource) {
Predicate<String> filterFieldCheck = field -> isFilterField(field, resource);
if (isDisjunctionFilterQuery(query, filterFieldCheck)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package org.folio.search.model.index;

import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SubjectResource {

private String id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,21 @@
import static org.springframework.core.GenericTypeResolver.resolveTypeArguments;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.log4j.Log4j2;
import org.folio.search.model.BrowseResult;
import org.folio.search.model.SearchResult;
import org.folio.search.model.index.InstanceSubResource;
import org.folio.search.model.service.BrowseContext;
import org.folio.search.model.service.BrowseRequest;
import org.folio.search.repository.SearchRepository;
import org.folio.search.service.converter.ElasticsearchDocumentConverter;
import org.opensearch.action.search.MultiSearchResponse.Item;
import org.opensearch.index.query.TermQueryBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

@Log4j2
public abstract class AbstractBrowseServiceBySearchAfter<T, R> extends AbstractBrowseService<T> {

private static final String SHARED_FILTER_KEY = "instances.shared";

protected SearchRepository searchRepository;
protected ElasticsearchDocumentConverter documentConverter;
protected Class<R> browseResponseClass;
Expand Down Expand Up @@ -130,28 +121,6 @@ protected abstract SearchSourceBuilder getSearchQuery(
protected abstract BrowseResult<T> mapToBrowseResult(BrowseContext context, SearchResult<R> searchResult,
boolean isAnchor);

protected Set<InstanceSubResource> filterSubResourcesForConsortium(
BrowseContext context, R resource,
Function<R, Set<InstanceSubResource>> subResourceExtractor) {

var subResources = subResourceExtractor.apply(resource);
var sharedFilter = getBrowseFilter(context, SHARED_FILTER_KEY);

return sharedFilter.map(shared -> subResources.stream()
.filter(subResource -> subResource.getShared().equals(Boolean.valueOf(shared)))
.collect(Collectors.toSet()))
.orElse(subResources);
}

private Optional<String> getBrowseFilter(BrowseContext context, String filterKey) {
return context.getFilters().stream()
.map(filter -> filter instanceof TermQueryBuilder termFilter && termFilter.fieldName().equals(filterKey)
? String.valueOf(termFilter.value())
: null)
.filter(Objects::nonNull)
.findFirst();
}

private BrowseResult<T> createBrowseResult(Item[] responses, BrowseRequest request, BrowseContext context) {
var precedingResult = documentConverter.convertToSearchResult(responses[0].getResponse(), browseResponseClass);
var succeedingResult = documentConverter.convertToSearchResult(responses[1].getResponse(), browseResponseClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.folio.search.model.SearchResult;
import org.folio.search.model.service.BrowseContext;
import org.folio.search.model.service.BrowseRequest;
import org.folio.search.service.consortium.ConsortiumSearchHelper;
import org.folio.search.service.metadata.SearchFieldProvider;
import org.opensearch.index.query.TermsQueryBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
Expand All @@ -37,6 +38,7 @@ public class AuthorityBrowseService extends AbstractBrowseServiceBySearchAfter<A
List.of(AUTHORIZED.getTypeValue(), REFERENCE.getTypeValue()));

private final SearchFieldProvider searchFieldProvider;
private final ConsortiumSearchHelper consortiumSearchHelper;

@Override
protected BrowseResult<AuthorityBrowseItem> mapToBrowseResult(BrowseContext context, SearchResult<Authority> result,
Expand All @@ -62,7 +64,8 @@ protected SearchSourceBuilder getSearchQuery(BrowseRequest request, BrowseContex

var boolQuery = boolQuery().filter(FILTER_QUERY);
ctx.getFilters().forEach(boolQuery::filter);
return searchSource().query(boolQuery)
var query = consortiumSearchHelper.filterQueryForActiveAffiliation(boolQuery);
return searchSource().query(query)
.searchAfter(new Object[] {ctx.getAnchor().toLowerCase(ROOT)})
.sort(fieldSort(request.getTargetField()).order(isBrowsingForward ? ASC : DESC))
.size(ctx.getLimit(isBrowsingForward) + 1)
Expand All @@ -77,7 +80,8 @@ protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest request, Browse

var boolQuery = boolQuery().filter(FILTER_QUERY).must(termQuery(request.getTargetField(), context.getAnchor()));
context.getFilters().forEach(boolQuery::filter);
return searchSource().query(boolQuery).from(0).size(1).fetchSource(getIncludedSourceFields(request), null);
var query = consortiumSearchHelper.filterQueryForActiveAffiliation(boolQuery);
return searchSource().query(query).from(0).size(1).fetchSource(getIncludedSourceFields(request), null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public class BrowseContextProvider {
public BrowseContext get(BrowseRequest request) {
log.debug("get:: by [query: {}, resource: {}]", request.getQuery(), request.getResource());

// todo(MSEARCH-580): use 'convertForConsortia' or/and check todo item for 'convertForConsortia'
var searchSource = cqlSearchQueryConverter.convert(request.getQuery(), request.getResource());
var cqlQuery = request.getQuery();
if (isNotEmpty(searchSource.sorts())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.folio.search.model.service.BrowseContext;
import org.folio.search.model.service.BrowseRequest;
import org.folio.search.model.types.CallNumberType;
import org.folio.search.service.consortium.ConsortiumSearchHelper;
import org.folio.search.service.metadata.SearchFieldProvider;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.script.Script;
Expand All @@ -38,6 +39,7 @@ public class CallNumberBrowseQueryProvider {
private final SearchFieldProvider searchFieldProvider;
private final SearchQueryConfigurationProperties queryConfiguration;
private final CallNumberBrowseRangeService callNumberBrowseRangeService;
private final ConsortiumSearchHelper consortiumSearchHelper;

/**
* Creates query as {@link SearchSourceBuilder} object for call number browsing.
Expand All @@ -56,8 +58,10 @@ 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 searchSource = searchSource().from(0).size(pageSize)
.query(getQuery(ctx, request, pageSize, isBrowsingForward))
.query(query)
.sort(scriptSort(script, STRING).order(isBrowsingForward ? ASC : DESC));

if (isFalse(request.getExpandAll())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.opensearch.search.sort.SortOrder.ASC;
import static org.opensearch.search.sort.SortOrder.DESC;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import one.util.streamex.StreamEx;
import org.folio.search.domain.dto.InstanceContributorBrowseItem;
Expand All @@ -20,26 +21,31 @@
import org.folio.search.model.index.InstanceSubResource;
import org.folio.search.model.service.BrowseContext;
import org.folio.search.model.service.BrowseRequest;
import org.folio.search.service.consortium.ConsortiumSearchHelper;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.sort.SortMode;
import org.springframework.stereotype.Service;

@Log4j2
@Service
@RequiredArgsConstructor
public class ContributorBrowseService extends
AbstractBrowseServiceBySearchAfter<InstanceContributorBrowseItem, ContributorResource> {

private static final String MISSING_LAST_PROP = "_last";
private static final String CONTRIBUTOR_NAME_TYPE_ID_FIELD = "contributorNameTypeId";
private static final String CONTRIBUTOR_TYPE_ID_FIELD = "instances.contributorTypeId";

private final ConsortiumSearchHelper consortiumSearchHelper;

@Override
protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest request, BrowseContext context) {
log.debug("getAnchorSearchQuery:: by [request: {}]", request);
var boolQuery = boolQuery().must(termQuery(request.getTargetField(), context.getAnchor()));
context.getFilters().forEach(boolQuery::filter);
return searchSource().query(boolQuery)
var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(context, boolQuery);
return searchSource().query(query)
.size(context.getLimit(context.isBrowsingForward()))
.from(0);
}
Expand All @@ -56,6 +62,7 @@ protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ct
ctx.getFilters().forEach(boolQuery::filter);
query = boolQuery;
}
query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, query);
return searchSource().query(query)
.searchAfter(new Object[] {ctx.getAnchor().toLowerCase(ROOT), null, null, null})
.sort(fieldSort(req.getTargetField()).order(isBrowsingForward ? ASC : DESC))
Expand All @@ -77,7 +84,7 @@ protected BrowseResult<InstanceContributorBrowseItem> mapToBrowseResult(BrowseCo
boolean isAnchor) {
return BrowseResult.of(res)
.map(item -> {
var filteredInstanceResources = filterSubResourcesForConsortium(context, item,
var filteredInstanceResources = consortiumSearchHelper.filterSubResourcesForConsortium(context, item,
ContributorResource::getInstances);
var typeIds = filteredInstanceResources.stream()
.map(InstanceSubResource::getTypeId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,30 @@
import org.folio.search.model.index.SubjectResource;
import org.folio.search.model.service.BrowseContext;
import org.folio.search.model.service.BrowseRequest;
import org.folio.search.service.consortium.ConsortiumSearchHelper;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Log4j2
@Service
@RequiredArgsConstructor
public class SubjectBrowseService extends AbstractBrowseServiceBySearchAfter<SubjectBrowseItem, SubjectResource> {

private ConsortiumSearchHelper consortiumSearchHelper;

@Autowired
public void setConsortiumSearchHelper(ConsortiumSearchHelper consortiumSearchHelper) {
this.consortiumSearchHelper = consortiumSearchHelper;
}

@Override
protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest request, BrowseContext context) {
log.debug("getAnchorSearchQuery:: by [request: {}]", request);
return searchSource().query(termQuery(request.getTargetField(), context.getAnchor()))
var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(context,
termQuery(request.getTargetField(), context.getAnchor()));
return searchSource().query(query)
.size(context.getLimit(context.isBrowsingForward()))
.from(0);
}
Expand All @@ -45,6 +56,7 @@ protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ct
ctx.getFilters().forEach(boolQuery::filter);
query = boolQuery;
}
query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, query);
return searchSource().query(query)
.searchAfter(new Object[] {ctx.getAnchor().toLowerCase(ROOT)})
.sort(fieldSort(req.getTargetField()).order(isBrowsingForward ? ASC : DESC))
Expand All @@ -65,7 +77,8 @@ protected BrowseResult<SubjectBrowseItem> mapToBrowseResult(BrowseContext contex
.value(subjectResource.getValue())
.authorityId(subjectResource.getAuthorityId())
.isAnchor(isAnchor ? true : null)
.totalRecords(filterSubResourcesForConsortium(context, subjectResource, SubjectResource::getInstances).size()));
.totalRecords(consortiumSearchHelper.filterSubResourcesForConsortium(
context, subjectResource, SubjectResource::getInstances).size()));
}

@Override
Expand Down
Loading

0 comments on commit e4b567a

Please sign in to comment.