Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Migrate Package Endpoints to HoldingsIQ v2 API ([MODKBEKBJ-804](https://folio-org.atlassian.net/browse/MODKBEKBJ-804))
* Add additional fields to POST /eholdings/packages endpoint ([MODKBEKBJ-823](https://folio-org.atlassian.net/browse/MODKBEKBJ-823))
* Update visibility hidden reason handling ([MODKBEKBJ-823](https://folio-org.atlassian.net/browse/MODKBEKBJ-823))
* Add access types to eholdings/providers/{id}/packages response ([MODKBEKBJ-839](https://folio-org.atlassian.net/browse/MODKBEKBJ-839))

### Bug fixes
* Fix offset handling when retrieving holdings from HoldingsIQ. ([MODKBEKBJ-825](https://folio-org.atlassian.net/browse/MODKBEKBJ-825))
Expand Down
9 changes: 9 additions & 0 deletions ramls/providers.raml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ traits:
is: [packageQueriable, taggable, accessible, packageFilterable,
sortable: {defaultValue: 'relevance', possibleValues: 'name, relevance'},
pageable: {maxCountValue: 100, defaultCountValue: 25}]
queryParameters:
include:
displayName: Include access type
type: string
description: |
Include related objects in response
Possible values are
- accessType
required: false
responses:
200:
description: OK
Expand Down
8 changes: 8 additions & 0 deletions ramls/types/packages/packageCollectionItem.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
"type": "object",
"description": "Displays if any resources are included in relationships",
"$ref": "packageRelationships.json"
},
"included": {
"type": "array",
"description": "List of included objects",
"items": {
"type": "object",
"javaType": "java.lang.Object"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@

import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.folio.holdingsiq.model.PackageData;
import org.folio.holdingsiq.model.PackageId;
import org.folio.holdingsiq.model.Packages;
import org.folio.repository.accesstypes.DbAccessType;
import org.folio.repository.packages.DbPackage;
import org.folio.rest.jaxrs.model.AccessType;
import org.folio.rest.jaxrs.model.HasOneRelationship;
import org.folio.rest.jaxrs.model.MetaDataIncluded;
import org.folio.rest.jaxrs.model.MetaTotalResults;
import org.folio.rest.jaxrs.model.PackageCollection;
import org.folio.rest.jaxrs.model.PackageCollectionItem;
import org.folio.rest.jaxrs.model.RelationshipData;
import org.folio.rest.jaxrs.model.Tags;
import org.folio.rest.util.RestConstants;
import org.folio.rmapi.result.PackageCollectionResult;
Expand All @@ -21,22 +26,27 @@
public class PackageCollectionResultConverter implements Converter<PackageCollectionResult, PackageCollection> {

private final Converter<PackageData, PackageCollectionItem> packageCollectionItemConverter;
private final Converter<DbAccessType, AccessType> accessTypeConverter;

public PackageCollectionResultConverter(
Converter<PackageData, PackageCollectionItem> packageCollectionItemConverter) {
Converter<PackageData, PackageCollectionItem> packageCollectionItemConverter,
Converter<DbAccessType, AccessType> accessTypeConverter) {
this.packageCollectionItemConverter = packageCollectionItemConverter;
this.accessTypeConverter = accessTypeConverter;
}

@Override
public PackageCollection convert(PackageCollectionResult packagesResult) {
Packages packages = packagesResult.packages();
List<DbPackage> dbPackages = packagesResult.dbPackages();
List<PackageCollectionItem> packageList = mapItems(packages.getPackagesList(),
var packages = packagesResult.packages();
var dbPackages = packagesResult.dbPackages();
var accessTypes = packagesResult.accessTypes();
var packageList = mapItems(packages.getPackagesList(),
packageData -> {
PackageCollectionItem item = packageCollectionItemConverter.convert(packageData);
var item = packageCollectionItemConverter.convert(packageData);
item.getAttributes()
.withTags(new Tags()
.withTagList(getTagsById(dbPackages, createPackageId(packageData))));
addAccessType(item, packageData.getFullPackageId(), accessTypes);
return item;
});

Expand All @@ -46,6 +56,23 @@ public PackageCollection convert(PackageCollectionResult packagesResult) {
.withData(packageList);
}

private void addAccessType(PackageCollectionItem item, String packageFullId,
Map<String, DbAccessType> accessTypes) {
var dbAccessType = accessTypes.get(packageFullId);
if (dbAccessType == null) {
return;
}
var accessType = accessTypeConverter.convert(dbAccessType);
item.getIncluded().add(accessType);
item.getRelationships()
.withAccessType(new HasOneRelationship()
.withData(new RelationshipData()
.withId(accessType.getId())
.withType(AccessType.Type.ACCESS_TYPES.value()))
.withMeta(new MetaDataIncluded()
.withIncluded(true)));
}

private List<String> getTagsById(List<DbPackage> packages, PackageId packageId) {
return packages.stream()
.filter(dbPackage -> dbPackage.getId().equals(packageId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,8 @@ private Function<TitleCollectionResult, CompletionStage<TitleCollectionResult>>
RmApiTemplateContext context) {
return titleCollection -> {
Map<String, TitleResult> resourceIdToAccessType = mapResourceIdToTitleResult(titleCollection);
String credentialsId = context.getCredentialsId();
String tenant = context.getRequestContext().getTenant();
return accessTypesService.findPerRecord(credentialsId, new ArrayList<>(resourceIdToAccessType.keySet()),
RecordType.RESOURCE, tenant)
return relatedEntitiesLoader.loadAccessTypes(new ArrayList<>(resourceIdToAccessType.keySet()),
RecordType.RESOURCE, context)
.thenApply(accessTypeMap -> {
populateResourceAccessTypes(resourceIdToAccessType, accessTypeMap);
return titleCollection;
Expand Down
38 changes: 26 additions & 12 deletions src/main/java/org/folio/rest/impl/EholdingsProvidersImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static java.util.Collections.emptyList;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.folio.common.ListUtils.parseByComma;
import static org.folio.db.RowSetUtils.toUUID;
import static org.folio.rest.util.ExceptionMappers.error422InputValidationMapper;
import static org.folio.rest.util.IdParser.getPackageIds;
Expand Down Expand Up @@ -72,6 +73,7 @@
public class EholdingsProvidersImpl implements EholdingsProviders {

private static final String GET_PROVIDER_NOT_FOUND_MESSAGE = "Provider not found";
private static final String ACCESS_TYPE_INCLUDE_PARAM = "accessType";

@Autowired
private Converter<ProviderPutRequest, VendorPut> putRequestConverter;
Expand Down Expand Up @@ -197,7 +199,7 @@ tags, new RequestContext(headers).getTenant())
@Override
@Validate
@HandleValidationErrors
public void getEholdingsProvidersPackagesByProviderId(String providerId, String q, String queryField,
public void getEholdingsProvidersPackagesByProviderId(String providerId, String include, String q, String queryField,
String queryType, boolean highlight, List<String> filterTags,
List<String> filterAccessType, String filterSelected,
String filterType, String filterVisibility, String filterAccess,
Expand All @@ -222,7 +224,9 @@ public void getEholdingsProvidersPackagesByProviderId(String providerId, String
.count(count)
.build();

RmApiTemplate template = templateFactory.createTemplate(okapiHeaders, asyncResultHandler);
var includedObjects = parseByComma(include);

var template = templateFactory.createTemplate(okapiHeaders, asyncResultHandler);
if (filter.isTagsFilter()) {
template.requestAction(
context -> filteredEntitiesLoader.fetchPackagesByTagFilter(TagFilter.from(filter), context));
Expand All @@ -231,7 +235,7 @@ public void getEholdingsProvidersPackagesByProviderId(String providerId, String
.fetchPackagesByAccessTypeFilter(AccessTypeFilter.from(filter), context)
.thenApply(packages -> new PackageCollectionResult(packages, emptyList())));
} else {
template.requestAction(retrieveFilteredPackages(parsedProviderId, filter));
template.requestAction(retrieveFilteredPackages(parsedProviderId, filter, includedObjects));
}
template
.addErrorMapper(ResourceNotFoundException.class, exception ->
Expand All @@ -241,13 +245,30 @@ public void getEholdingsProvidersPackagesByProviderId(String providerId, String
}

private Function<RmApiTemplateContext, CompletableFuture<?>> retrieveFilteredPackages(int providerId,
PackageRecordFilter filter) {
PackageRecordFilter filter,
List<String> includedObjects) {
var packageFilter = filter.toClientFilter(searchProperties);
var pageable = filter.toPageable();
return context ->
context.getPackagesService()
.retrievePackages(providerId, packageFilter, pageable)
.thenCompose(packages -> loadTags(packages, context));
.thenCompose(packages -> loadTagsAndAccessTypes(packages, includedObjects, context));
}

private CompletableFuture<PackageCollectionResult> loadTagsAndAccessTypes(Packages packages,
List<String> includedObjects,
RmApiTemplateContext context) {
var credentialsId = toUUID(context.getCredentialsId());
var tagsFuture = packageRepository.findByIds(getPackageIds(packages), credentialsId,
context.getRequestContext().getTenant());
if (includedObjects.contains(ACCESS_TYPE_INCLUDE_PARAM)) {
var packageIds = packages.getPackagesList().stream()
.map(packageData -> packageData.getVendorId() + "-" + packageData.getPackageId())
.toList();
return tagsFuture.thenCombine(relatedEntitiesLoader.loadAccessTypes(packageIds, RecordType.PACKAGE, context),
(dbPackages, accessTypes) -> new PackageCollectionResult(packages, dbPackages, accessTypes));
}
return tagsFuture.thenApply(dbPackages -> new PackageCollectionResult(packages, dbPackages));
Comment thread
viacheslavkol marked this conversation as resolved.
}

private CompletableFuture<VendorResult> loadTags(VendorResult result, RmApiTemplateContext context) {
Expand All @@ -258,13 +279,6 @@ private CompletableFuture<VendorResult> loadTags(VendorResult result, RmApiTempl
return relatedEntitiesLoader.loadTags(result, recordKey, context).thenApply(v -> result);
}

private CompletableFuture<PackageCollectionResult> loadTags(Packages packages, RmApiTemplateContext context) {
UUID credentialsId = toUUID(context.getCredentialsId());
String tenant = context.getRequestContext().getTenant();
return packageRepository.findByIds(getPackageIds(packages), credentialsId, tenant)
.thenApply(dbPackages -> new PackageCollectionResult(packages, dbPackages));
}

private CompletableFuture<Void> updateTags(DbProvider provider, Tags tags, String tenant) {
if (Objects.isNull(tags)) {
return completedFuture(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package org.folio.rmapi.result;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.folio.holdingsiq.model.Packages;
import org.folio.repository.accesstypes.DbAccessType;
import org.folio.repository.packages.DbPackage;

public record PackageCollectionResult(Packages packages, List<DbPackage> dbPackages) { }
public record PackageCollectionResult(Packages packages, List<DbPackage> dbPackages,
Map<String, DbAccessType> accessTypes) {

public PackageCollectionResult(Packages packages, List<DbPackage> dbPackages) {
this(packages, dbPackages, Collections.emptyMap());
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package org.folio.service.loader;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.folio.repository.RecordKey;
import org.folio.repository.RecordType;
import org.folio.repository.accesstypes.DbAccessType;
import org.folio.rest.util.template.RmApiTemplateContext;
import org.folio.rmapi.result.Accessible;
import org.folio.rmapi.result.Tagable;
Expand All @@ -11,4 +15,7 @@ public interface RelatedEntitiesLoader {
CompletableFuture<Void> loadAccessType(Accessible accessible, RecordKey recordKey, RmApiTemplateContext context);

CompletableFuture<Void> loadTags(Tagable tagable, RecordKey recordKey, RmApiTemplateContext context);

CompletableFuture<Map<String, DbAccessType>> loadAccessTypes(List<String> recordIds, RecordType recordType,
RmApiTemplateContext context);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package org.folio.service.loader;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import javax.ws.rs.NotFoundException;
import lombok.extern.log4j.Log4j2;
import org.folio.repository.RecordKey;
import org.folio.repository.RecordType;
import org.folio.repository.accesstypes.DbAccessType;
import org.folio.repository.tag.DbTag;
import org.folio.repository.tag.TagRepository;
import org.folio.rest.jaxrs.model.Tags;
Expand Down Expand Up @@ -48,6 +52,15 @@ public CompletableFuture<Void> loadAccessType(Accessible accessible, RecordKey r
return future;
}

@Override
public CompletableFuture<Map<String, DbAccessType>> loadAccessTypes(List<String> recordIds, RecordType recordType,
RmApiTemplateContext context) {
log.debug("loadAccessTypes:: by [recordIds count: {}, recordType: {}]", recordIds.size(), recordType);
String tenant = context.getRequestContext().getTenant();
return accessTypesService.findPerRecord(context.getCredentialsId(), new ArrayList<>(recordIds), recordType, tenant)
.toCompletableFuture();
}

@Override
public CompletableFuture<Void> loadTags(Tagable tagable, RecordKey recordKey, RmApiTemplateContext context) {
String tenant = context.getRequestContext().getTenant();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.folio.rest.converter.packages;

import static org.folio.util.TestUtil.readJsonFile;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import org.folio.holdingsiq.model.Packages;
import org.folio.repository.accesstypes.DbAccessType;
import org.folio.rest.jaxrs.model.AccessType;
import org.folio.rest.jaxrs.model.PackageCollection;
import org.folio.rmapi.result.PackageCollectionResult;
import org.folio.spring.config.TestConfig;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestConfig.class)
class PackageCollectionResultConverterTest {

private static final String PACKAGES_FIXTURE = "responses/rmapi/packages/get-packages-by-provider-id.json";
private static final String PACKAGE_FULL_ID = "19-3964";
private static final String ACCESS_TYPE_NAME = "Subscribed";
private static final String ACCESS_TYPE_DESCRIPTION = "Access Type description";

@Autowired
private ConversionService conversionService;

@Test
void shouldIncludeAccessTypeInPackageItemWhenAccessTypeIsMapped() {
var packages = readJsonFile(PACKAGES_FIXTURE, Packages.class);
var accessTypeId = UUID.randomUUID();
var credentialsId = UUID.randomUUID();
var dbAccessType = DbAccessType.builder()
.id(accessTypeId)
.credentialsId(credentialsId)
.name(ACCESS_TYPE_NAME)
.description(ACCESS_TYPE_DESCRIPTION)
.build();
Map<String, DbAccessType> accessTypes = Map.of(PACKAGE_FULL_ID, dbAccessType);
var result = new PackageCollectionResult(packages, Collections.emptyList(), accessTypes);

var packageCollection = conversionService.convert(result, PackageCollection.class);

assertNotNull(packageCollection);
var item = packageCollection.getData().getFirst();

assertEquals(1, item.getIncluded().size());
var includedAccessType = (AccessType) item.getIncluded().getFirst();
assertEquals(accessTypeId.toString(), includedAccessType.getId());
assertEquals(AccessType.Type.ACCESS_TYPES, includedAccessType.getType());

var accessTypeRel = item.getRelationships().getAccessType();
assertNotNull(accessTypeRel);
assertEquals(accessTypeId.toString(), accessTypeRel.getData().getId());
assertEquals("accessTypes", accessTypeRel.getData().getType());
assertTrue(accessTypeRel.getMeta().getIncluded());
}

@Test
void shouldNotIncludeAccessTypeWhenAccessTypesMapIsEmpty() {
var packages = readJsonFile(PACKAGES_FIXTURE, Packages.class);
var result = new PackageCollectionResult(packages, Collections.emptyList());

var packageCollection = conversionService.convert(result, PackageCollection.class);

assertNotNull(packageCollection);
var item = packageCollection.getData().getFirst();

assertTrue(item.getIncluded().isEmpty());
assertNull(item.getRelationships().getAccessType());
}
}
Loading
Loading