Skip to content

Commit

Permalink
feat(cn-browse): Implement browsing endpoint for Call Numbers
Browse files Browse the repository at this point in the history
Closes: MSEARCH-865
  • Loading branch information
psmagin committed Dec 11, 2024
1 parent 24bae72 commit bb0721a
Show file tree
Hide file tree
Showing 44 changed files with 1,544 additions and 1,252 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Call Numbers Browse: Implement Database Structure and Logic for Managing Call Numbers ([MSEARCH-862](https://folio-org.atlassian.net/browse/MSEARCH-862))
* Call Numbers Browse: Implement Call Number Browse Config ([MSEARCH-863](https://folio-org.atlassian.net/browse/MSEARCH-863))
* Call Numbers Browse: Implement Indexing and Re-indexing Mechanisms for Call-Numbers ([MSEARCH-864](https://folio-org.atlassian.net/browse/MSEARCH-864))
* Call Numbers Browse: Implement Browsing Endpoint for Call-Numbers ([MSEARCH-865](https://folio-org.atlassian.net/browse/MSEARCH-865))

### Bug fixes
* Remove shelving order calculation for local call-number types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void addFormatters(FormatterRegistry registry) {
private static final class StringToRecordTypeEnumConverter implements Converter<String, RecordType> {
@Override
public RecordType convert(String source) {
return RecordType.valueOf(source.toUpperCase());
return RecordType.valueOf(source.toUpperCase().replace('-', '_'));
}
}

Expand Down
53 changes: 42 additions & 11 deletions src/main/java/org/folio/search/controller/BrowseController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,29 @@
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.folio.search.model.types.ResourceType.AUTHORITY;
import static org.folio.search.model.types.ResourceType.INSTANCE;
import static org.folio.search.model.types.ResourceType.INSTANCE_CALL_NUMBER;
import static org.folio.search.model.types.ResourceType.INSTANCE_CLASSIFICATION;
import static org.folio.search.model.types.ResourceType.INSTANCE_CONTRIBUTOR;
import static org.folio.search.model.types.ResourceType.INSTANCE_SUBJECT;
import static org.folio.search.utils.SearchUtils.AUTHORITY_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.CALL_NUMBER_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.CLASSIFICATION_NUMBER_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.CONTRIBUTOR_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.LEGACY_CALL_NUMBER_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.SHELVING_ORDER_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.SUBJECT_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.TYPED_CALL_NUMBER_BROWSING_FIELD;

import lombok.RequiredArgsConstructor;
import org.folio.search.domain.dto.AuthorityBrowseResult;
import org.folio.search.domain.dto.BrowseOptionType;
import org.folio.search.domain.dto.CallNumberBrowseItem;
import org.folio.search.domain.dto.CallNumberBrowseResult;
import org.folio.search.domain.dto.CallNumberType;
import org.folio.search.domain.dto.ClassificationNumberBrowseItem;
import org.folio.search.domain.dto.ClassificationNumberBrowseResult;
import org.folio.search.domain.dto.ContributorBrowseResult;
import org.folio.search.domain.dto.LegacyCallNumberBrowseResult;
import org.folio.search.domain.dto.SubjectBrowseResult;
import org.folio.search.exception.RequestValidationException;
import org.folio.search.model.BrowseResult;
Expand All @@ -32,6 +36,7 @@
import org.folio.search.service.browse.CallNumberBrowseService;
import org.folio.search.service.browse.ClassificationBrowseService;
import org.folio.search.service.browse.ContributorBrowseService;
import org.folio.search.service.browse.LegacyCallNumberBrowseService;
import org.folio.search.service.browse.SubjectBrowseService;
import org.folio.search.service.consortium.TenantProvider;
import org.springframework.http.ResponseEntity;
Expand All @@ -47,9 +52,10 @@ public class BrowseController implements BrowseApi {

private final SubjectBrowseService subjectBrowseService;
private final AuthorityBrowseService authorityBrowseService;
private final CallNumberBrowseService callNumberBrowseService;
private final LegacyCallNumberBrowseService legacyCallNumberBrowseService;
private final ContributorBrowseService contributorBrowseService;
private final ClassificationBrowseService classificationBrowseService;
private final CallNumberBrowseService callNumberBrowseService;
private final TenantProvider tenantProvider;

@Override
Expand All @@ -67,20 +73,36 @@ public ResponseEntity<AuthorityBrowseResult> browseAuthorities(String query, Str
}

@Override
public ResponseEntity<CallNumberBrowseResult> browseInstancesByCallNumber(String query, String tenant,
Integer limit, Boolean expandAll,
Boolean highlightMatch,
Integer precedingRecordsCount,
CallNumberType callNumberType) {
public ResponseEntity<CallNumberBrowseResult> browseInstancesByCallNumber(BrowseOptionType browseOptionId,
String query, String tenant,
Integer limit, Boolean highlightMatch,
Integer precedingRecordsCount) {
var browseRequest = getBrowseRequestBuilder(query, tenant, limit, false, highlightMatch, precedingRecordsCount)
.resource(INSTANCE_CALL_NUMBER)
.browseOptionType(browseOptionId)
.targetField(CALL_NUMBER_BROWSING_FIELD)
.build();

var browseResult = callNumberBrowseService.browse(browseRequest);
return ResponseEntity.ok(toCallNumberBrowseResultDto(browseResult));
}

@Override
public ResponseEntity<LegacyCallNumberBrowseResult> browseInstancesByCallNumberLegacy(String query, String tenant,
Integer limit,
Boolean expandAll,
Boolean highlightMatch,
Integer precedingRecordsCount,
CallNumberType callNumberType) {
var browseRequest = getBrowseRequestBuilder(query, tenant, limit, expandAll, highlightMatch, precedingRecordsCount)
.resource(INSTANCE)
.targetField(SHELVING_ORDER_BROWSING_FIELD)
.subField(callNumberType == null ? CALL_NUMBER_BROWSING_FIELD : TYPED_CALL_NUMBER_BROWSING_FIELD)
.subField(callNumberType == null ? LEGACY_CALL_NUMBER_BROWSING_FIELD : TYPED_CALL_NUMBER_BROWSING_FIELD)
.refinedCondition(callNumberType != null ? callNumberType.getValue() : null)
.build();

var instanceByCallNumber = callNumberBrowseService.browse(browseRequest);
return ResponseEntity.ok(new CallNumberBrowseResult()
var instanceByCallNumber = legacyCallNumberBrowseService.browse(browseRequest);
return ResponseEntity.ok(new LegacyCallNumberBrowseResult()
.items(instanceByCallNumber.getRecords())
.totalRecords(instanceByCallNumber.getTotalRecords())
.prev(instanceByCallNumber.getPrev())
Expand All @@ -99,7 +121,7 @@ public ResponseEntity<ClassificationNumberBrowseResult> browseInstancesByClassif
.build();

var browseResult = classificationBrowseService.browse(browseRequest);
return ResponseEntity.ok(toBrowseResultDto(browseResult));
return ResponseEntity.ok(toClassificationBrowseResultDto(browseResult));
}

@Override
Expand Down Expand Up @@ -132,14 +154,23 @@ public ResponseEntity<SubjectBrowseResult> browseInstancesBySubject(String query
.next(browseResult.getNext()));
}

private ClassificationNumberBrowseResult toBrowseResultDto(BrowseResult<ClassificationNumberBrowseItem> result) {
private ClassificationNumberBrowseResult toClassificationBrowseResultDto(
BrowseResult<ClassificationNumberBrowseItem> result) {
return new ClassificationNumberBrowseResult()
.totalRecords(result.getTotalRecords())
.items(result.getRecords())
.prev(result.getPrev())
.next(result.getNext());
}

private CallNumberBrowseResult toCallNumberBrowseResultDto(BrowseResult<CallNumberBrowseItem> result) {
return new CallNumberBrowseResult()
.totalRecords(result.getTotalRecords())
.items(result.getRecords())
.prev(result.getPrev())
.next(result.getNext());
}

private BrowseRequestBuilder getBrowseRequestBuilder(String query, String tenant, Integer limit,
Boolean expandAll, Boolean highlightMatch,
Integer precedingRecordsCount) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class FacetsController implements FacetsApi {
RecordType.AUTHORITIES, ResourceType.AUTHORITY,
RecordType.CONTRIBUTORS, ResourceType.INSTANCE_CONTRIBUTOR,
RecordType.SUBJECTS, ResourceType.INSTANCE_SUBJECT,
RecordType.CLASSIFICATIONS, ResourceType.INSTANCE_CLASSIFICATION
RecordType.CLASSIFICATIONS, ResourceType.INSTANCE_CLASSIFICATION,
RecordType.CALL_NUMBERS, ResourceType.INSTANCE_CALL_NUMBER
);

private final FacetService facetService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,6 @@ public boolean isAnchorIncluded(boolean isForward) {
return isForward ? this.succeedingQuery.includeLower() : this.precedingQuery.includeUpper();
}

/**
* Checks if anchor is included in the range query or not.
*
* @return {@code true} if anchor is included, {@code false} - otherwise
*/
public boolean isAnchorIncluded() {
return isAnchorIncluded(true);
}

/**
* Returns limit for browsing.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ private void logBrowseRequest(BrowseRequest request, String functionName) {
}

private void logMultiSearchRequest(BrowseRequest request, int size) {
log.info("browseAround:: Attempting to multi-search request [tenant: {}, searchSource.size: {}]",
log.debug("browseAround:: Attempting to multi-search request [tenant: {}, searchSource.size: {}]",
request.getTenantId(), size);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package org.folio.search.service.browse;

import static java.util.Locale.ROOT;
import static org.folio.search.utils.SearchUtils.BROWSE_FIELDS_MAP;
import static org.folio.search.utils.SearchUtils.DEFAULT_SHELVING_ORDER_BROWSING_FIELD;
import static org.opensearch.index.query.QueryBuilders.boolQuery;
import static org.opensearch.index.query.QueryBuilders.matchAllQuery;
import static org.opensearch.index.query.QueryBuilders.termQuery;
import static org.opensearch.search.builder.SearchSourceBuilder.searchSource;
import static org.opensearch.search.sort.SortBuilders.fieldSort;
import static org.opensearch.search.sort.SortOrder.ASC;
import static org.opensearch.search.sort.SortOrder.DESC;

import java.util.Set;
import java.util.function.Function;
import lombok.extern.log4j.Log4j2;
import org.folio.search.domain.dto.BrowseConfig;
import org.folio.search.domain.dto.BrowseType;
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.BrowseConfigServiceDecorator;
import org.folio.search.service.consortium.ConsortiumSearchHelper;
import org.folio.search.utils.ShelvingOrderCalculationHelper;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.index.query.TermQueryBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;

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

private final ConsortiumSearchHelper consortiumSearchHelper;
private final BrowseConfigServiceDecorator configService;

protected AbstractShelvingOrderBrowseServiceBySearchAfter(ConsortiumSearchHelper consortiumSearchHelper,
BrowseConfigServiceDecorator configService) {
this.consortiumSearchHelper = consortiumSearchHelper;
this.configService = configService;
}

@Override
protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest req, BrowseContext ctx) {
log.debug("getAnchorSearchQuery:: by [request: {}]", req);
var config = configService.getConfig(getBrowseType(), req.getBrowseOptionType());

var browseField = getBrowseField(config);
var termQueryBuilder = getQuery(ctx, config, termQuery(req.getTargetField(), ctx.getAnchor()));
var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, termQueryBuilder, req.getResource());
var sortOrder = ctx.isBrowsingForward() ? ASC : DESC;
return searchSource().query(query)
.sort(fieldSort(browseField).order(sortOrder))
.sort(fieldSort(req.getTargetField()).order(sortOrder))
.size(ctx.getLimit(ctx.isBrowsingForward()))
.from(0);
}

@Override
protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ctx, boolean isBrowsingForward) {
log.debug("getSearchQuery:: by [request: {}, isBrowsingForward: {}]", req, isBrowsingForward);
var config = configService.getConfig(getBrowseType(), req.getBrowseOptionType());

var browseField = getBrowseField(config);
var normalizedAnchor = ShelvingOrderCalculationHelper.calculate(ctx.getAnchor(), config.getShelvingAlgorithm());
var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, getQuery(ctx, config, null),
req.getResource());

var sortOrder = isBrowsingForward ? ASC : DESC;
return searchSource().query(query)
.searchAfter(new Object[] {normalizedAnchor.toLowerCase(ROOT), ctx.getAnchor().toLowerCase(ROOT)})
.sort(fieldSort(browseField).order(sortOrder))
.sort(fieldSort(req.getTargetField()).order(sortOrder))
.size(ctx.getLimit(isBrowsingForward) + 1)
.from(0);
}

/**
* Returns the type of browsing supported by this service.
*
* @return the {@link BrowseType} for this service
*/
protected abstract BrowseType getBrowseType();

/**
* Returns the field name used for type identification in the browse service.
*
* @return the type ID field name
*/
protected abstract String getTypeIdField();

protected Integer getTotalRecords(BrowseContext ctx, R resource, Function<R, Set<InstanceSubResource>> func) {
return consortiumSearchHelper.filterSubResourcesForConsortium(ctx, resource, func)
.stream()
.map(InstanceSubResource::getCount)
.reduce(0, Integer::sum);
}

private QueryBuilder getQuery(BrowseContext ctx, BrowseConfig config, TermQueryBuilder anchorQuery) {
var typeIds = config.getTypeIds();
if (config.getTypeIds().isEmpty() && ctx.getFilters().isEmpty()) {
if (anchorQuery != null) {
return anchorQuery;
}
return matchAllQuery();
} else {
var boolQueryMain = boolQuery();
if (!config.getTypeIds().isEmpty()) {
var boolQuery = boolQuery();
for (var typeId : typeIds) {
boolQuery.should(termQuery(getTypeIdField(), typeId.toString()));
}
boolQueryMain.must(boolQuery);
}
if (!ctx.getFilters().isEmpty()) {
ctx.getFilters().forEach(boolQueryMain::filter);
}
if (anchorQuery != null) {
boolQueryMain.must(anchorQuery);
}
return boolQueryMain;
}
}

private static String getBrowseField(BrowseConfig config) {
return BROWSE_FIELDS_MAP.getOrDefault(config.getShelvingAlgorithm(), DEFAULT_SHELVING_ORDER_BROWSING_FIELD);
}

}
Loading

0 comments on commit bb0721a

Please sign in to comment.