Skip to content

Commit

Permalink
feat(search-instances): implement endpoint for consolidate holdings a…
Browse files Browse the repository at this point in the history
…ccess in consortium

Closes: MSEARCH-692
Signed-off-by: psmagin <[email protected]>
  • Loading branch information
psmagin committed Mar 11, 2024
1 parent f3078a6 commit 453afc2
Show file tree
Hide file tree
Showing 32 changed files with 1,446 additions and 464 deletions.
23 changes: 23 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,24 @@
}
]
},
{
"id": "consortium-search",
"version": "1.0",
"handlers": [
{
"methods": [
"GET"
],
"pathPattern": "/search/consortium/holdings",
"permissionsRequired": [
"consortium-search.holdings.collection.get"
],
"modulePermissions": [
"user-tenants.collection.get"
]
}
]
},
{
"id": "resource-ids-streaming",
"version": "0.3",
Expand Down Expand Up @@ -589,6 +607,11 @@
"permissionName": "browse.config.item.put",
"displayName": "Browse - updates configuration entry for browse type",
"description": "Updates configuration entry for browse type"
},
{
"permissionName": "consortium-search.holdings.collection.get",
"displayName": "Consortium Search - fetch holdings records",
"description": "Returns holdings records in consortium"
}
],
"launchDescriptor": {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/folio/search/configuration/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.CallNumberType;
import org.folio.search.domain.dto.RecordType;
import org.folio.search.domain.dto.SortOrder;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
Expand All @@ -18,6 +19,7 @@ public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToCallNumberTypeEnumConverter());
registry.addConverter(new StringToBrowseTypeConverter());
registry.addConverter(new StringToBrowseOptionTypeConverter());
registry.addConverter(new StringToSortOrderTypeConverter());
}

private static final class StringToRecordTypeEnumConverter implements Converter<String, RecordType> {
Expand Down Expand Up @@ -47,4 +49,11 @@ public BrowseOptionType convert(String source) {
return BrowseOptionType.fromValue(source.toLowerCase());
}
}

private static final class StringToSortOrderTypeConverter implements Converter<String, SortOrder> {
@Override
public SortOrder convert(String source) {
return SortOrder.fromValue(source.toLowerCase());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.folio.search.controller;

import lombok.RequiredArgsConstructor;
import org.folio.search.domain.dto.ConsortiumHoldingCollection;
import org.folio.search.domain.dto.SortOrder;
import org.folio.search.exception.RequestValidationException;
import org.folio.search.model.service.ConsortiumSearchContext;
import org.folio.search.model.types.ResourceType;
import org.folio.search.rest.resource.SearchConsortiumApi;
import org.folio.search.service.consortium.ConsortiumInstanceService;
import org.folio.search.service.consortium.ConsortiumTenantService;
import org.folio.spring.integration.XOkapiHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/")
public class SearchConsortiumController implements SearchConsortiumApi {

static final String REQUEST_NOT_ALLOWED_MSG =
"The request allowed only for central tenant of consortium environment";

private final ConsortiumTenantService consortiumTenantService;
private final ConsortiumInstanceService instanceService;

@Override
public ResponseEntity<ConsortiumHoldingCollection> getConsortiumHoldings(String tenantHeader, String instanceId,
String tenantId, Integer limit,
Integer offset, String sortBy,
SortOrder sortOrder) {
checkAllowance(tenantHeader);
var context = ConsortiumSearchContext.builderFor(ResourceType.HOLDINGS)
.filter("instanceId", instanceId)
.filter("tenantId", tenantId)
.limit(limit)
.offset(offset)
.sortBy(sortBy)
.sortOrder(sortOrder)
.build();
return ResponseEntity.ok(instanceService.fetchHoldings(context));
}

private void checkAllowance(String tenantHeader) {
var centralTenant = consortiumTenantService.getCentralTenant(tenantHeader);
if (centralTenant.isEmpty() || !centralTenant.get().equals(tenantHeader)) {
throw new RequestValidationException(REQUEST_NOT_ALLOWED_MSG, XOkapiHeaders.TENANT, tenantHeader);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.folio.search.model.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.folio.search.domain.dto.SortOrder;
import org.folio.search.exception.RequestValidationException;
import org.folio.search.model.Pair;
import org.folio.search.model.types.ResourceType;

@Getter
public class ConsortiumSearchContext {

static final String SORT_NOT_ALLOWED_MSG = "Not allowed sort field for %s";
static final String FILTER_REQUIRED_MSG = "At least one filter criteria required";

private static final Map<ResourceType, List<String>> ALLOWED_SORT_FIELDS = Map.of(
ResourceType.HOLDINGS, List.of("id", "hrid", "tenantId", "instanceId",
"callNumberPrefix", "callNumber", "copyNumber", "permanentLocationId")
);

private static final Map<ResourceType, String> DEFAULT_SORT_FIELD = Map.of(
ResourceType.HOLDINGS, "id"
);

private final ResourceType resourceType;
private final List<Pair<String, String>> filters;
private final Integer limit;
private final Integer offset;
private final String sortBy;
private final SortOrder sortOrder;

ConsortiumSearchContext(ResourceType resourceType, List<Pair<String, String>> filters, Integer limit, Integer offset,
String sortBy, SortOrder sortOrder) {
this.resourceType = resourceType;
this.filters = filters;
if (sortBy != null && !ALLOWED_SORT_FIELDS.get(resourceType).contains(sortBy)) {
throw new RequestValidationException(SORT_NOT_ALLOWED_MSG.formatted(resourceType.getValue()), "sortBy", sortBy);
}
if (sortBy != null && (filters.isEmpty())) {
throw new RequestValidationException(FILTER_REQUIRED_MSG, null, null);
}
this.limit = limit;
this.offset = offset;
this.sortBy = sortBy;
this.sortOrder = sortOrder;
}

public static ConsortiumSearchContextBuilder builderFor(ResourceType resourceType) {
return new ConsortiumSearchContextBuilder(resourceType);
}

public static class ConsortiumSearchContextBuilder {
private final ResourceType resourceType;
private List<Pair<String, String>> filters = new ArrayList<>();
private Integer limit;
private Integer offset;
private String sortBy;
private SortOrder sortOrder;

ConsortiumSearchContextBuilder(ResourceType resourceType) {
this.resourceType = resourceType;
}

public ConsortiumSearchContextBuilder filter(String name, String value) {
if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(value)) {
this.filters.add(Pair.pair(name, value));
}
return this;
}

public ConsortiumSearchContextBuilder limit(Integer limit) {
this.limit = limit;
return this;
}

public ConsortiumSearchContextBuilder offset(Integer offset) {
this.offset = offset;
return this;
}

public ConsortiumSearchContextBuilder sortBy(String sortBy) {
this.sortBy = sortBy;
return this;
}

public ConsortiumSearchContextBuilder sortOrder(SortOrder sortOrder) {
this.sortOrder = sortOrder;
return this;
}

public ConsortiumSearchContext build() {
return new ConsortiumSearchContext(this.resourceType, this.filters, this.limit, this.offset,
this.sortBy, this.sortOrder);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
public enum ResourceType {

INSTANCE("instance"),
HOLDINGS("holdings"),
AUTHORITY("authority"),
CLASSIFICATION_TYPE("classification-type");

private final String value;

ResourceType(String value) {

this.value = value;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.folio.search.service.consortium;

import static org.folio.search.service.consortium.ConsortiumSearchQueryBuilder.CONSORTIUM_TABLES;
import static org.folio.search.utils.JdbcUtils.getFullTableName;
import static org.folio.search.utils.JdbcUtils.getParamPlaceholder;

Expand All @@ -13,6 +14,8 @@
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.search.domain.dto.ConsortiumHolding;
import org.folio.search.model.types.ResourceType;
import org.folio.spring.FolioExecutionContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
Expand All @@ -22,7 +25,6 @@
@RequiredArgsConstructor
public class ConsortiumInstanceRepository {

static final String CONSORTIUM_INSTANCE_TABLE_NAME = "consortium_instance";
private static final String SELECT_BY_ID_SQL = "SELECT * FROM %s WHERE instance_id IN (%s)";
private static final String DELETE_BY_TENANT_AND_ID_SQL = "DELETE FROM %s WHERE tenant_id = ? AND instance_id = ?;";
private static final String UPSERT_SQL = """
Expand Down Expand Up @@ -74,12 +76,27 @@ public void delete(Set<ConsortiumInstanceId> instanceIds) {
);
}

public List<ConsortiumHolding> fetchHoldings(ConsortiumSearchQueryBuilder searchQueryBuilder) {
return jdbcTemplate.query(con -> searchQueryBuilder.buildSelectQuery(context, con),
(rs, rowNum) -> new ConsortiumHolding()
.id(rs.getString("id"))
.hrid(rs.getString("hrid"))
.tenantId(rs.getString("tenantId"))
.instanceId(rs.getString("instanceId"))
.callNumberPrefix(rs.getString("callNumberPrefix"))
.callNumber(rs.getString("callNumber"))
.copyNumber(rs.getString("copyNumber"))
.permanentLocationId(rs.getString("permanentLocationId"))
.discoverySuppress(rs.getBoolean("discoverySuppress"))
);
}

private ConsortiumInstance toConsortiumInstance(ResultSet rs) throws SQLException {
var id = new ConsortiumInstanceId(rs.getString(TENANT_ID_COLUMN), rs.getString(INSTANCE_ID_COLUMN));
return new ConsortiumInstance(id, rs.getString(JSON_COLUMN));
}

private String getTableName() {
return getFullTableName(context, CONSORTIUM_INSTANCE_TABLE_NAME);
return getFullTableName(context, CONSORTIUM_TABLES.get(ResourceType.INSTANCE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.folio.search.domain.dto.ConsortiumHolding;
import org.folio.search.domain.dto.ConsortiumHoldingCollection;
import org.folio.search.domain.dto.ResourceEvent;
import org.folio.search.domain.dto.ResourceEventType;
import org.folio.search.model.event.ConsortiumInstanceEvent;
import org.folio.search.model.service.ConsortiumSearchContext;
import org.folio.search.utils.JsonConverter;
import org.folio.search.utils.SearchConverterUtils;
import org.folio.spring.FolioExecutionContext;
Expand Down Expand Up @@ -147,6 +150,11 @@ public List<ResourceEvent> fetchInstances(Iterable<String> instanceIds) {
return resourceEvents;
}

public ConsortiumHoldingCollection fetchHoldings(ConsortiumSearchContext context) {
List<ConsortiumHolding> holdingList = repository.fetchHoldings(new ConsortiumSearchQueryBuilder(context));
return new ConsortiumHoldingCollection().holdings(holdingList).totalRecords(holdingList.size());
}

@SuppressWarnings("unchecked")
private void addListItems(List<Map<String, Object>> mergedList, Map<String, Object> instanceMap, String key) {
var items = instanceMap.get(key);
Expand Down
Loading

0 comments on commit 453afc2

Please sign in to comment.