Skip to content

Commit

Permalink
fix(browse-classifications): implement endpoint to browse by classifi…
Browse files Browse the repository at this point in the history
…cations

Closes: MSEARCH-665
Signed-off-by: psmagin <[email protected]>
  • Loading branch information
psmagin committed Feb 23, 2024
1 parent 947daea commit 5b22313
Show file tree
Hide file tree
Showing 26 changed files with 417 additions and 253 deletions.
19 changes: 18 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
},
{
"id": "browse",
"version": "1.2",
"version": "1.3",
"handlers": [
{
"methods": [
Expand All @@ -197,6 +197,18 @@
"user-tenants.collection.get"
]
},
{
"methods": [
"GET"
],
"pathPattern": "/browse/classification-numbers/{browseOptionId}/instances",
"permissionsRequired": [
"browse.classification-numbers.instances.collection.get"
],
"modulePermissions": [
"user-tenants.collection.get"
]
},
{
"methods": [
"GET"
Expand Down Expand Up @@ -469,6 +481,11 @@
"displayName": "Browse - provides collections of browse items for instance by call number",
"description": "Browse instances by given query"
},
{
"permissionName": "browse.classification-numbers.instances.collection.get",
"displayName": "Browse - provides collections of browse by classification number",
"description": "Browse by classification number"
},
{
"permissionName": "browse.subjects.instances.collection.get",
"displayName": "Browse - provides collections of browse items for instance by subjects",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import java.util.Set;
import lombok.Data;
import org.folio.search.domain.dto.TenantConfiguredFeature;
import org.folio.search.model.types.ClassificationType;
import org.folio.search.model.types.IndexingDataFormat;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -47,8 +46,6 @@ public class SearchConfigurationProperties {
*/
private Map<TenantConfiguredFeature, Boolean> searchFeatures = emptyMap();

private Map<ClassificationType, String[]> browseClassificationTypes = emptyMap();

/**
* Indexing settings for different resources.
*/
Expand Down
35 changes: 33 additions & 2 deletions src/main/java/org/folio/search/controller/BrowseController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import static org.folio.search.utils.SearchUtils.AUTHORITY_BROWSING_FIELD;
import static org.folio.search.utils.SearchUtils.AUTHORITY_RESOURCE;
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.CONTRIBUTOR_RESOURCE;
import static org.folio.search.utils.SearchUtils.INSTANCE_CLASSIFICATION_RESOURCE;
import static org.folio.search.utils.SearchUtils.INSTANCE_RESOURCE;
import static org.folio.search.utils.SearchUtils.INSTANCE_SUBJECT_RESOURCE;
import static org.folio.search.utils.SearchUtils.SHELVING_ORDER_BROWSING_FIELD;
Expand All @@ -14,16 +16,21 @@

import lombok.RequiredArgsConstructor;
import org.folio.search.domain.dto.AuthorityBrowseResult;
import org.folio.search.domain.dto.BrowseOptionType;
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.SubjectBrowseResult;
import org.folio.search.exception.RequestValidationException;
import org.folio.search.model.BrowseResult;
import org.folio.search.model.service.BrowseRequest;
import org.folio.search.model.service.BrowseRequest.BrowseRequestBuilder;
import org.folio.search.rest.resource.BrowseApi;
import org.folio.search.service.browse.AuthorityBrowseService;
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.SubjectBrowseService;
import org.folio.search.service.consortium.TenantProvider;
Expand All @@ -42,6 +49,7 @@ public class BrowseController implements BrowseApi {
private final AuthorityBrowseService authorityBrowseService;
private final CallNumberBrowseService callNumberBrowseService;
private final ContributorBrowseService contributorBrowseService;
private final ClassificationBrowseService classificationBrowseService;
private final TenantProvider tenantProvider;

@Override
Expand Down Expand Up @@ -80,6 +88,21 @@ public ResponseEntity<CallNumberBrowseResult> browseInstancesByCallNumber(String
.next(instanceByCallNumber.getNext()));
}

@Override
public ResponseEntity<ClassificationNumberBrowseResult> browseInstancesByClassificationNumber(
BrowseOptionType browseOptionId, String query, String tenant, Integer limit, Boolean expandAll,
Boolean highlightMatch, Integer precedingRecordsCount) {

var browseRequest = getBrowseRequestBuilder(query, tenant, limit, expandAll, highlightMatch, precedingRecordsCount)
.resource(INSTANCE_CLASSIFICATION_RESOURCE)
.browseOptionType(browseOptionId)
.targetField(CLASSIFICATION_NUMBER_BROWSING_FIELD)
.build();

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

@Override
public ResponseEntity<ContributorBrowseResult> browseInstancesByContributor(String query, String tenant,
Integer limit, Boolean highlightMatch,
Expand Down Expand Up @@ -110,9 +133,17 @@ public ResponseEntity<SubjectBrowseResult> browseInstancesBySubject(String query
.next(browseResult.getNext()));
}

private ClassificationNumberBrowseResult toBrowseResultDto(BrowseResult<ClassificationNumberBrowseItem> result) {
return new ClassificationNumberBrowseResult()
.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) {
Boolean expandAll, Boolean highlightMatch,
Integer precedingRecordsCount) {
if (precedingRecordsCount != null && precedingRecordsCount >= limit) {
throw new RequestValidationException("Preceding records count must be less than request limit",
"precedingRecordsCount", String.valueOf(precedingRecordsCount));
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import lombok.Setter;
import lombok.ToString;
import org.folio.search.configuration.jpa.StringListConverter;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.Hibernate;

@Getter
@Setter
Expand Down Expand Up @@ -46,12 +46,8 @@ public final boolean equals(Object o) {
if (o == null) {
return false;
}
Class<?> effectiveClass = o instanceof HibernateProxy
? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass()
: o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy
? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass()
: this.getClass();
Class<?> effectiveClass = Hibernate.getClass(o);
Class<?> thisEffectiveClass = Hibernate.getClass(this);
if (thisEffectiveClass != effectiveClass) {
return false;
}
Expand Down
10 changes: 3 additions & 7 deletions src/main/java/org/folio/search/model/config/BrowseConfigId.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.Hibernate;

@Getter
@Embeddable
Expand All @@ -32,12 +32,8 @@ public final boolean equals(Object o) {
if (o == null) {
return false;
}
Class<?> effectiveClass = o instanceof HibernateProxy
? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass()
: o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy
? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass()
: this.getClass();
Class<?> effectiveClass = Hibernate.getClass(o);
Class<?> thisEffectiveClass = Hibernate.getClass(this);
if (thisEffectiveClass != effectiveClass) {
return false;
}
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/org/folio/search/model/service/BrowseRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.folio.search.domain.dto.BrowseOptionType;
import org.folio.search.model.ResourceRequest;

@Data
@Builder()
@Builder
@RequiredArgsConstructor(staticName = "of")
public class BrowseRequest implements ResourceRequest {

Expand All @@ -20,6 +21,11 @@ public class BrowseRequest implements ResourceRequest {
*/
private final String tenantId;

/**
* Browse option type.
*/
private final BrowseOptionType browseOptionType;

/**
* A CQL query string with search conditions.
*/
Expand Down Expand Up @@ -63,7 +69,14 @@ public class BrowseRequest implements ResourceRequest {
public static BrowseRequest of(String resource, String tenantId, String query, Integer limit, String targetField,
String subField, Boolean expandAll, Boolean highlightMatch,
Integer precedingRecordsCount) {
return new BrowseRequest(resource, tenantId, query, limit, targetField, subField, null, expandAll,
return new BrowseRequest(resource, tenantId, null, query, limit, targetField, subField, null, expandAll,
highlightMatch, precedingRecordsCount);
}

public static BrowseRequest of(String resource, String tenantId, BrowseOptionType optionType, String query,
Integer limit, String targetField, String subField, Boolean expandAll,
Boolean highlightMatch, Integer precedingRecordsCount) {
return new BrowseRequest(resource, tenantId, optionType, query, limit, targetField, subField, null, expandAll,
highlightMatch, precedingRecordsCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.folio.search.service.browse;

import static org.folio.search.utils.SearchUtils.BROWSE_FIELDS_MAP;
import static org.folio.search.utils.SearchUtils.CLASSIFICATION_TYPE_ID_FIELD;
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 lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.search.domain.dto.BrowseConfig;
import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.ClassificationNumberBrowseItem;
import org.folio.search.model.BrowseResult;
import org.folio.search.model.SearchResult;
import org.folio.search.model.index.ClassificationResource;
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.config.BrowseConfigService;
import org.folio.search.service.consortium.ConsortiumSearchHelper;
import org.folio.search.utils.ShelvingOrderCalculationHelper;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.springframework.stereotype.Service;

@Log4j2
@Service
@RequiredArgsConstructor
public class ClassificationBrowseService
extends AbstractBrowseServiceBySearchAfter<ClassificationNumberBrowseItem, ClassificationResource> {

private final ConsortiumSearchHelper consortiumSearchHelper;
private final BrowseConfigService configService;

@Override
protected String getValueForBrowsing(ClassificationNumberBrowseItem browseItem) {
return browseItem.getClassificationNumber();
}

@Override
protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest req, BrowseContext ctx) {
log.debug("getAnchorSearchQuery:: by [request: {}]", req);
var termQueryBuilder = termQuery(req.getTargetField(), ctx.getAnchor());
var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx,
termQueryBuilder, req.getResource());
return searchSource().query(query)
.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(BrowseType.INSTANCE_CLASSIFICATION, req.getBrowseOptionType());

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

return searchSource().query(query)
.searchAfter(new Object[] {normalizedAnchor})
.sort(fieldSort(browseField).order(isBrowsingForward ? ASC : DESC))
.size(ctx.getLimit(isBrowsingForward) + 1)
.from(0);
}

@Override
protected ClassificationNumberBrowseItem getEmptyBrowseItem(BrowseContext context) {
return new ClassificationNumberBrowseItem()
.classificationNumber(context.getAnchor())
.totalRecords(0)
.isAnchor(true);
}

@Override
protected BrowseResult<ClassificationNumberBrowseItem> mapToBrowseResult(BrowseContext ctx,
SearchResult<ClassificationResource> res,
boolean isAnchor) {
return BrowseResult.of(res)
.map(resource -> new ClassificationNumberBrowseItem()
.classificationNumber(resource.number())
.classificationTypeId(resource.typeId())
.isAnchor(isAnchor ? true : null)
.totalRecords(getTotalRecords(ctx, resource)));
}

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

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

private Integer getTotalRecords(BrowseContext ctx, ClassificationResource classificationResource) {
return consortiumSearchHelper.filterSubResourcesForConsortium(ctx, classificationResource,
ClassificationResource::instances).stream()
.map(InstanceSubResource::getInstanceId)
.distinct()
.map(e -> 1)
.reduce(0, Integer::sum);
}

}
Loading

0 comments on commit 5b22313

Please sign in to comment.