diff --git a/NEWS.md b/NEWS.md
index 7d7763f52..94134d6c2 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -7,7 +7,7 @@
* Requires `API_NAME vX.Y`
### Features
-* Description ([ISSUE](https://folio-org.atlassian.net/browse/ISSUE))
+* Migrate Package Endpoints to HoldingsIQ v2 API ([MODKBEKBJ-804](https://folio-org.atlassian.net/browse/MODKBEKBJ-804))
### Bug fixes
* Fix offset handling when retrieving holdings from HoldingsIQ. ([MODKBEKBJ-825](https://folio-org.atlassian.net/browse/MODKBEKBJ-825))
diff --git a/pom.xml b/pom.xml
index 5cc307d6b..1ef2afe8f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,7 +40,7 @@
36.0.0
4.0.0
6.0.0
- 5.0.0
+ 6.0.0-SNAPSHOT
1.11.0
5.1.2
@@ -50,7 +50,6 @@
1.22.0
1.11.0
1.18.46
- 4.4.16
5.12.0
3.20.0
@@ -61,11 +60,12 @@
7.0.8
- 4.13.2
+ 6.1.0
6.0.0
5.23.0
3.27.7
1.5.3
+ 3.13.2
5.0.0
@@ -88,24 +88,6 @@
13.6.0
-
-
- default
-
- true
-
-
- false
-
-
-
- no-coverage
-
- true
-
-
-
-
@@ -129,6 +111,13 @@
pom
import
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
@@ -195,11 +184,6 @@
${lombok.version}
provided
-
- org.apache.httpcomponents
- httpcore
- ${httpcore.version}
-
org.folio
folio-di-support
@@ -240,22 +224,27 @@
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
io.vertx
- vertx-unit
+ vertx-junit5
test
- junit
- junit
- ${junit.version}
+ org.springframework
+ spring-test
+ ${spring.version}
+ test
+
+
+ org.folio
+ postgres-testing
+ ${rmb.version}
test
-
-
- org.hamcrest
- hamcrest-core
-
-
io.rest-assured
@@ -275,6 +264,12 @@
${mockito.version}
test
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
org.assertj
assertj-core
@@ -288,37 +283,9 @@
test
- org.folio
- folio-service-tools-test
- ${folio-service-tools.version}
-
-
- org.junit.jupiter
- junit-jupiter-engine
-
-
- org.junit.jupiter
- junit-jupiter-api
-
-
- org.junit.platform
- junit-platform-engine
-
-
- org.junit.platform
- junit-platform-commons
-
-
- org.apache.logging.log4j
- log4j-slf4j2-impl
-
-
- test
-
-
- org.springframework
- spring-test
- ${spring.version}
+ org.wiremock
+ wiremock
+ ${wiremock.version}
test
@@ -686,15 +653,11 @@
${maven-surefire-plugin.version}
false
-
- **/org/folio/rest/impl/integrationsuite/*.java
-
**/Test*.java
**/*Test.java
**/*Tests.java
**/*TestCase.java
- org/folio/rest/impl/IntegrationTestSuite.java
@@ -709,9 +672,6 @@
prepare-agent
-
- ${test.coverage.skip}
-
jacoco-coverage-report
@@ -719,9 +679,6 @@
report
-
- ${test.coverage.skip}
-
diff --git a/ramls/packages.raml b/ramls/packages.raml
index f45fc4494..205935605 100644
--- a/ramls/packages.raml
+++ b/ramls/packages.raml
@@ -11,9 +11,10 @@ documentation:
traits:
queriable: !include traits/queriable.raml
+ packageQueriable: !include traits/packageQueriable.raml
sortable: !include traits/sortable.raml
pageable: !include traits/pageable.raml
- filterable: !include traits/filterable.raml
+ packageFilterable: !include traits/packageFilterable.raml
taggable: !include traits/taggable.raml
accessible: !include traits/accessible.raml
packageResourcesFilterable: !include traits/packageResourcesFilterable.raml
@@ -34,7 +35,7 @@ types:
displayName: Packages
get:
description: Retrieve a collection of packages based on the search query.
- is: [queriable, filterable, taggable, accessible,
+ is: [packageQueriable, packageFilterable, taggable, accessible,
sortable: {defaultValue: 'relevance', possibleValues: 'name, relevance'},
pageable: {maxCountValue: 100, defaultCountValue: 25}]
queryParameters:
diff --git a/ramls/providers.raml b/ramls/providers.raml
index f04629cac..a81ae4c3b 100644
--- a/ramls/providers.raml
+++ b/ramls/providers.raml
@@ -22,7 +22,8 @@ traits:
queriable: !include traits/queriable.raml
sortable: !include traits/sortable.raml
pageable: !include traits/pageable.raml
- filterable: !include traits/filterable.raml
+ packageFilterable: !include traits/packageFilterable.raml
+ packageQueriable: !include traits/packageQueriable.raml
taggable: !include traits/taggable.raml
accessible: !include traits/accessible.raml
includable: !include traits/includePackages.raml
@@ -128,7 +129,7 @@ traits:
/packages:
get:
description: Search within a list of packages associated with a given provider.
- is: [queriable, taggable, accessible, filterable,
+ is: [packageQueriable, taggable, accessible, packageFilterable,
sortable: {defaultValue: 'relevance', possibleValues: 'name, relevance'},
pageable: {maxCountValue: 100, defaultCountValue: 25}]
responses:
diff --git a/ramls/traits/filterable.raml b/ramls/traits/filterable.raml
deleted file mode 100644
index ba50e9af1..000000000
--- a/ramls/traits/filterable.raml
+++ /dev/null
@@ -1,16 +0,0 @@
-queryParameters:
- filter[selected]:
- displayName: Selection status
- type: string
- description: Filter to narrow down results based on selection status. Possible values are all, true, false, ebsco.
- example: all
- required: false
- filter[type]:
- displayName: Content type
- type: string
- description: |
- Filter to narrow down results based on content type. Defaults to all.
- Possible values are all, aggregatedfulltext, abstractandindex, ebook, ejournal, print, unknown, streamingmedia, mixedcontent, onlinereference.
- example: ebook
- default: all
- required: false
diff --git a/ramls/traits/packageFilterable.raml b/ramls/traits/packageFilterable.raml
new file mode 100644
index 000000000..7b4933555
--- /dev/null
+++ b/ramls/traits/packageFilterable.raml
@@ -0,0 +1,33 @@
+queryParameters:
+ filter[selected]:
+ displayName: Selection status
+ type: string
+ description: Filter to narrow down results based on selection status. Possible values are all, true, false, ebsco.
+ example: all
+ required: false
+ filter[type]:
+ displayName: Content type
+ type: string
+ description: |
+ Filter to narrow down results based on content type. Defaults to all.
+ Possible values: all, aggregatedfulltext, abstractandindex, ebook, ejournal, print, unknown, streamingmedia, mixedcontent, onlinereference.
+ example: ebook
+ default: all
+ required: false
+ filter[visibility]:
+ displayName: Visibility status
+ type: string
+ description: |
+ Limits the results based on customer visibility status per product category.
+ Only functions when selection=selected is included in the request.
+ Possible values: visibleInPF, visibleInFTF, visibleInMARC, hiddenInPF, hiddenInFTF, hiddenInMARC
+ example: visibleInPF
+ required: false
+ filter[access]:
+ displayName: Package access
+ type: string
+ description: |
+ Limits the results based on Package Free Access.
+ Possible values: public, controlled
+ example: public
+ required: false
diff --git a/ramls/traits/packageQueriable.raml b/ramls/traits/packageQueriable.raml
new file mode 100644
index 000000000..0bd12f79e
--- /dev/null
+++ b/ramls/traits/packageQueriable.raml
@@ -0,0 +1,27 @@
+queryParameters:
+ q:
+ displayName: Search query
+ type: string
+ description: String used to search to retrieve a collection
+ example: ABC-CLIO
+ required: false
+ q[field]:
+ displayName: Search field
+ type: string
+ description: |
+ Field to search.
+ name - Searches on Package Names and Alternative Names.
+ keyword - Searches on Package Name, Alternative Name, Vendor Name, Managed Description and Custom Description.
+ default: name
+ q[type]:
+ displayName: Search type
+ type: string
+ description: |
+ Type of search to be performed.
+ Possible values: exactphrase, advanced, any, contains, exactmatch, beginswith
+ highlight:
+ displayName: Hit highlighting
+ type: boolean
+ description: If enabled, highlight which parts of the Package result were matched on during searching
+ required: false
+ default: false
\ No newline at end of file
diff --git a/ramls/types/packages/packageAltName.json b/ramls/types/packages/packageAltName.json
new file mode 100644
index 000000000..9779a9e51
--- /dev/null
+++ b/ramls/types/packages/packageAltName.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Alternate names schema",
+ "description": "Alternate names schema",
+ "javaType": "org.folio.rest.jaxrs.model.PackageAltName",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "integer",
+ "description": "The id of the alternate name of the package."
+ },
+ "altName": {
+ "type": "string",
+ "description": "The alternate name of the package"
+ }
+ },
+ "required": [
+ "altName"
+ ]
+}
diff --git a/ramls/types/packages/packageDataAttributes.json b/ramls/types/packages/packageDataAttributes.json
index e202821ad..2bbc171c2 100644
--- a/ramls/types/packages/packageDataAttributes.json
+++ b/ramls/types/packages/packageDataAttributes.json
@@ -6,86 +6,147 @@
"type": "object",
"additionalProperties": false,
"properties": {
+ "allowKbToAddTitles": {
+ "description": "Allow KB to add titles",
+ "example": true,
+ "type": "boolean"
+ },
"contentType": {
- "type": "string",
+ "$ref": "contentTypeEnum.json",
"description": "Content Type of Package",
- "$ref" : "contentTypeEnum.json",
- "example": "Online Reference"
+ "example": "Online Reference",
+ "type": "string"
+ },
+ "customAltNames": {
+ "description": "The list of custom alternate names for the package",
+ "items": {
+ "$ref": "packageAltName.json",
+ "type": "object"
+ },
+ "type": "array"
},
"customCoverage": {
- "type": "object",
+ "$ref": "../coverage.json",
"description": "Custom Coverage",
- "$ref": "../coverage.json"
+ "type": "object"
+ },
+ "customDescription": {
+ "description": "The custom description of the package",
+ "example": "Package of medicinal journals (custom)",
+ "type": "string"
+ },
+ "customDisplayName": {
+ "description": "The custom description of the package",
+ "type": "string"
+ },
+ "isAvailableForSelection": {
+ "description": "Whether this package is available for full selection or not",
+ "example": false,
+ "type": "boolean"
},
"isCustom": {
- "type": "boolean",
"description": "Whether this package is custom or not",
- "example": false
+ "example": false,
+ "type": "boolean"
+ },
+ "isFreeAccess": {
+ "description": "Whether this package has free access designation or not",
+ "example": false,
+ "type": "boolean"
+ },
+ "isPrimaryPackage": {
+ "description": "Whether this package is a vendor's primary package",
+ "example": false,
+ "type": "boolean"
},
"isSelected": {
- "type": "boolean",
"description": "Whether this package is selected or not",
- "example": false
+ "example": false,
+ "type": "boolean"
+ },
+ "managedAltNames": {
+ "description": "The list of managed alternate names for the package",
+ "items": {
+ "$ref": "packageAltName.json",
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "managedDescription": {
+ "description": "The managed description of the package",
+ "example": "Package of medicinal journals",
+ "type": "string"
},
"name": {
- "type": "string",
"description": "Package name",
- "example": "Shenbao"
+ "example": "Shenbao",
+ "type": "string"
},
"packageId": {
- "type": "integer",
"description": "Package Id",
- "example": 1152699
+ "example": 1152699,
+ "type": "integer"
+ },
+ "packageToken": {
+ "$ref": "../token.json",
+ "description": "Package Token",
+ "type": "object"
},
"packageType": {
- "type": "string",
"description": "Package type",
- "example": "Complete"
+ "example": "Complete",
+ "type": "string"
},
"providerId": {
- "type": "integer",
"description": "Provider Id",
- "example": 91525
+ "example": 91525,
+ "type": "integer"
},
"providerName": {
- "type": "string",
"description": "Provider name",
- "example": "Green Apple - Qingpingguo"
+ "example": "Green Apple - Qingpingguo",
+ "type": "string"
+ },
+ "proxiedUrl": {
+ "description": "URL with proxy applied",
+ "type": "string"
+ },
+ "proxy": {
+ "$ref": "../proxy.json",
+ "description": "Proxy",
+ "type": "object"
},
"selectedCount": {
- "type": "integer",
"description": "Selected count",
- "example": 0
+ "example": 0,
+ "type": "integer"
+ },
+ "tags": {
+ "$ref": "../../raml-util/schemas/tags.schema",
+ "description": "Package tags",
+ "type": "object"
},
"titleCount": {
- "type": "integer",
"description": "Title count",
- "example": 1
+ "example": 1,
+ "type": "integer"
+ },
+ "url": {
+ "description": "Package URL",
+ "type": "string"
+ },
+ "visibility": {
+ "description": "Visibility details",
+ "items": {
+ "$ref": "packageVisibility.json",
+ "type": "object"
+ },
+ "type": "array"
},
"visibilityData": {
"type": "object",
"description": "Visibility data",
"$ref": "../visibilityData.json"
- },
- "allowKbToAddTitles": {
- "type": "boolean",
- "description": "Allow KB to add titles",
- "example": true
- },
- "packageToken": {
- "type": "object",
- "description": "Package Token",
- "$ref": "../token.json"
- },
- "proxy": {
- "type": "object",
- "description": "Proxy",
- "$ref": "../proxy.json"
- },
- "tags": {
- "type": "object",
- "description": "Package tags",
- "$ref": "../../raml-util/schemas/tags.schema"
}
}
}
diff --git a/ramls/types/packages/packagePutDataAttributes.json b/ramls/types/packages/packagePutDataAttributes.json
index 1cc39ca20..90ef096cb 100644
--- a/ramls/types/packages/packagePutDataAttributes.json
+++ b/ramls/types/packages/packagePutDataAttributes.json
@@ -6,62 +6,96 @@
"type": "object",
"additionalProperties": false,
"properties": {
+ "accessTypeId": {
+ "$ref": "../../raml-util/schemas/uuid.schema",
+ "description": "Access type id",
+ "example": "f973c3b6-85fc-4d35-bda8-f31b568957bf",
+ "type": "string"
+ },
+ "allowKbToAddTitles": {
+ "description": "Allow KB to add titles",
+ "example": true,
+ "type": "boolean"
+ },
"contentType": {
- "type": "string",
+ "$ref": "contentTypeEnum.json",
"description": "Content Type of Package",
- "$ref" : "contentTypeEnum.json",
- "example": "Online Reference"
+ "example": "Online Reference",
+ "type": "string"
},
- "accessTypeId": {
- "type": "string",
- "description": "Access type id",
- "$ref": "../../raml-util/schemas/uuid.schema",
- "example": "f973c3b6-85fc-4d35-bda8-f31b568957bf"
+ "customAltNames": {
+ "description": "The list of custom alternate names for the package",
+ "items": {
+ "$ref": "packageAltName.json",
+ "type": "object"
+ },
+ "type": "array"
},
"customCoverage": {
- "type": "object",
+ "$ref": "../coverage.json",
"description": "Custom Coverage",
- "$ref": "../coverage.json"
+ "type": "object"
+ },
+ "customDescription": {
+ "description": "The custom description of the package",
+ "example": "Package of medicinal journals (custom)",
+ "type": "string"
+ },
+ "customDisplayName": {
+ "description": "The custom description of the package",
+ "type": "string"
},
"isCustom": {
- "type": "boolean",
"description": "Whether this package is custom or not",
- "example": false
+ "example": false,
+ "type": "boolean"
},
- "isSelected": {
- "type": "boolean",
- "description": "Whether this package is selected or not",
- "example": false
+ "isFreeAccess": {
+ "description": "Whether this package has free access designation or not",
+ "example": false,
+ "type": "boolean"
},
"isFullPackage": {
- "type": "boolean",
"description": "Whether this package is partially selected or not",
- "example": false
+ "example": false,
+ "type": "boolean"
+ },
+ "isSelected": {
+ "description": "Whether this package is selected or not",
+ "example": false,
+ "type": "boolean"
},
"name": {
- "type": "string",
"description": "Package name",
- "example": "Shenbao"
- },
- "visibilityData": {
- "type": "object",
- "description": "Visibility data",
- "$ref": "../visibilityData.json"
- },
- "allowKbToAddTitles": {
- "type": "boolean",
- "description": "Allow KB to add titles",
- "example": true
+ "example": "Shenbao",
+ "type": "string"
},
"packageToken": {
- "type": "object",
+ "$ref": "../token.json",
"description": "Package Token",
- "$ref": "../token.json"
+ "type": "object"
},
"proxy": {
- "type": "object",
+ "$ref": "../proxy.json",
"description": "Proxy",
- "$ref": "../proxy.json"
+ "type": "object"
+ },
+ "url": {
+ "description": "Package URL",
+ "type": "string"
+ },
+ "visibility": {
+ "description": "Visibility details",
+ "items": {
+ "$ref": "packageVisibility.json",
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "visibilityData": {
+ "type": "object",
+ "description": "Visibility data",
+ "$ref": "../visibilityData.json"
}
}
}
diff --git a/ramls/types/packages/packageVisibility.json b/ramls/types/packages/packageVisibility.json
new file mode 100644
index 000000000..a33e5d7fa
--- /dev/null
+++ b/ramls/types/packages/packageVisibility.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Visibility details schema",
+ "description": "Visibility details schema",
+ "javaType": "org.folio.rest.jaxrs.model.PackageVisibility",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "category": {
+ "type": "string",
+ "description": "Visibility category",
+ "enum": [
+ "PF",
+ "FTF",
+ "MARC"
+ ]
+ },
+ "reason": {
+ "type": "string",
+ "description": "Reason the resource was hidden"
+ },
+ "hidden": {
+ "type": "boolean",
+ "description": "Hidden status"
+ }
+ },
+ "required": [
+ "category",
+ "hidden"
+ ]
+}
diff --git a/src/main/java/org/folio/client/uc/UcApigeeEbscoClientImpl.java b/src/main/java/org/folio/client/uc/UcApigeeEbscoClientImpl.java
index d5807fba4..debf87d78 100644
--- a/src/main/java/org/folio/client/uc/UcApigeeEbscoClientImpl.java
+++ b/src/main/java/org/folio/client/uc/UcApigeeEbscoClientImpl.java
@@ -1,5 +1,6 @@
package org.folio.client.uc;
+import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
import static org.folio.util.FutureUtils.mapVertxFuture;
import io.vertx.core.Completable;
@@ -22,8 +23,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import lombok.extern.log4j.Log4j2;
-import org.apache.http.HttpHeaders;
-import org.apache.http.entity.ContentType;
+import org.folio.HttpHeaders;
import org.folio.client.uc.configuration.GetPackageUcConfiguration;
import org.folio.client.uc.configuration.GetTitlePackageUcConfiguration;
import org.folio.client.uc.configuration.GetTitleUcConfiguration;
@@ -133,7 +133,7 @@ public CompletableFuture getUsageMetricType(UcConfiguration config
private Completable> handleResponse(Promise> promise) {
return (response, failure) -> {
if (failure != null) {
- if (ContentType.APPLICATION_JSON.getMimeType().equals(response.getHeader(HttpHeaders.CONTENT_TYPE))) {
+ if (APPLICATION_JSON.toString().equalsIgnoreCase(response.getHeader(HttpHeaders.CONTENT_TYPE))) {
JsonObject body = response.bodyAsJsonObject();
promise.fail(new UcFailedRequestException(response.statusCode(), body));
}
diff --git a/src/main/java/org/folio/common/ListUtils.java b/src/main/java/org/folio/common/ListUtils.java
index a82d9112c..462e9776f 100644
--- a/src/main/java/org/folio/common/ListUtils.java
+++ b/src/main/java/org/folio/common/ListUtils.java
@@ -21,6 +21,13 @@ public static List mapItems(Collection source, Function super T,
return source.stream().map(mapper).collect(Collectors.toList());
}
+ public static List mapItemsNullable(Collection source, Function super T, ? extends R> mapper) {
+ if (source == null) {
+ return null;
+ }
+ return source.stream().map(mapper).collect(Collectors.toList());
+ }
+
public static String createPlaceholders(int size) {
return String.join(",", Collections.nCopies(size, "?"));
}
diff --git a/src/main/java/org/folio/properties/common/SearchProperties.java b/src/main/java/org/folio/properties/common/SearchProperties.java
index 79a77554d..d1ae89d7e 100644
--- a/src/main/java/org/folio/properties/common/SearchProperties.java
+++ b/src/main/java/org/folio/properties/common/SearchProperties.java
@@ -1,3 +1,7 @@
package org.folio.properties.common;
-public record SearchProperties(String titlesSearchType, String packagesSearchType) { }
+public record SearchProperties(
+ String titlesSearchType,
+ String packagesSearchType,
+ String highlightTag
+) { }
diff --git a/src/main/java/org/folio/repository/providers/ProviderRepository.java b/src/main/java/org/folio/repository/providers/ProviderRepository.java
index 60a5024c3..454ff0ccd 100644
--- a/src/main/java/org/folio/repository/providers/ProviderRepository.java
+++ b/src/main/java/org/folio/repository/providers/ProviderRepository.java
@@ -11,5 +11,5 @@ public interface ProviderRepository {
CompletableFuture delete(String vendorId, UUID credentialsId, String tenantId);
- CompletableFuture> findIdsByTagFilter(TagFilter tagFilter, UUID credentialsId, String tenantId);
+ CompletableFuture> findIdsByTagFilter(TagFilter tagFilter, UUID credentialsId, String tenantId);
}
diff --git a/src/main/java/org/folio/repository/providers/ProviderRepositoryImpl.java b/src/main/java/org/folio/repository/providers/ProviderRepositoryImpl.java
index 81d6119a5..fa1d9ae00 100644
--- a/src/main/java/org/folio/repository/providers/ProviderRepositoryImpl.java
+++ b/src/main/java/org/folio/repository/providers/ProviderRepositoryImpl.java
@@ -28,6 +28,7 @@
import org.folio.db.exc.translation.DBExceptionTranslator;
import org.folio.rest.model.filter.TagFilter;
import org.folio.rest.persist.PostgresClient;
+import org.folio.rest.util.IdParser;
import org.springframework.stereotype.Component;
@Log4j2
@@ -76,7 +77,7 @@ public CompletableFuture delete(String vendorId, UUID credentialsId, Strin
}
@Override
- public CompletableFuture> findIdsByTagFilter(TagFilter tagFilter, UUID credentialsId, String tenantId) {
+ public CompletableFuture> findIdsByTagFilter(TagFilter tagFilter, UUID credentialsId, String tenantId) {
List tags = tagFilter.getTags();
if (CollectionUtils.isEmpty(tags)) {
return completedFuture(Collections.emptyList());
@@ -99,12 +100,12 @@ public CompletableFuture> findIdsByTagFilter(TagFilter tagFilter, UUI
return mapResult(promise.future().recover(excTranslator.translateOrPassBy()), this::mapProviderIds);
}
- private List mapProviderIds(RowSet resultSet) {
+ private List mapProviderIds(RowSet resultSet) {
return mapItems(resultSet, this::readProviderId);
}
- private Long readProviderId(Row row) {
- return Long.parseLong(row.getString(ID_COLUMN));
+ private Integer readProviderId(Row row) {
+ return IdParser.parseProviderId(row.getString(ID_COLUMN));
}
private PostgresClient pgClient(String tenantId) {
diff --git a/src/main/java/org/folio/rest/aspect/ValidationErrorHandlerAspect.aj b/src/main/java/org/folio/rest/aspect/ValidationErrorHandlerAspect.aj
index a9ceab78b..c2a6e50a8 100644
--- a/src/main/java/org/folio/rest/aspect/ValidationErrorHandlerAspect.aj
+++ b/src/main/java/org/folio/rest/aspect/ValidationErrorHandlerAspect.aj
@@ -1,39 +1,40 @@
package org.folio.rest.aspect;
-import org.folio.holdingsiq.service.exception.RequestValidationException;
-import org.folio.rest.annotations.Validate;
-import org.folio.rest.exception.InputValidationException;
-import org.folio.rest.util.ErrorUtil;
-
import io.vertx.core.AsyncResult;
-import io.vertx.core.Handler;
import io.vertx.core.Future;
-
-import org.aspectj.lang.annotation.SuppressAjWarnings;
-
-import javax.ws.rs.core.Response;
+import io.vertx.core.Handler;
import jakarta.validation.ValidationException;
+import javax.ws.rs.core.Response;
+import org.aspectj.lang.annotation.SuppressAjWarnings;
+import org.folio.holdingsiq.service.exception.RequestValidationException;
+import org.folio.rest.exception.InputValidationException;
+import org.folio.rest.exception.QueryParamsValidationException;
+import org.folio.rest.util.ErrorUtil;
public aspect ValidationErrorHandlerAspect {
- pointcut validatedMethodCall(Handler> asyncResultHandler) : execution(@HandleValidationErrors * *(.., Handler, *)) && args(.., asyncResultHandler, *);
+ pointcut validatedMethodCall(
+ Handler> asyncResultHandler): execution(@HandleValidationErrors * *(.., Handler, *)) && args(.., asyncResultHandler, *);
@SuppressAjWarnings({"adviceDidNotMatch"})
- void around(Handler asyncResultHandler) : validatedMethodCall(asyncResultHandler) {
- try{
+ void around(Handler> asyncResultHandler): validatedMethodCall(asyncResultHandler) {
+ try {
proceed(asyncResultHandler);
- }
- catch (ValidationException | RequestValidationException e){
- asyncResultHandler.handle(Future.succeededFuture(Response.status(400)
- .header("Content-Type", "application/vnd.api+json")
- .entity(ErrorUtil.createError(e.getMessage()))
- .build()));
- return;
- }
- catch (InputValidationException e){
+ } catch (ValidationException | RequestValidationException e) {
+ asyncResultHandler.handle(Future.succeededFuture(Response.status(400)
+ .header("Content-Type", "application/vnd.api+json")
+ .entity(ErrorUtil.createError(e.getMessage()))
+ .build()));
+ return;
+ } catch (QueryParamsValidationException e) {
+ asyncResultHandler.handle(Future.succeededFuture(Response.status(400)
+ .header("Content-Type", "application/vnd.api+json")
+ .entity(ErrorUtil.createErrors(e.getMessageDetail()))
+ .build()));
+ } catch (InputValidationException e) {
asyncResultHandler.handle(Future.succeededFuture(Response.status(422)
- .header("Content-Type", "application/vnd.api+json")
- .entity(ErrorUtil.createError(e.getMessage(), e.getMessageDetail()))
- .build()));
+ .header("Content-Type", "application/vnd.api+json")
+ .entity(ErrorUtil.createError(e.getMessage(), e.getMessageDetail()))
+ .build()));
return;
}
}
diff --git a/src/main/java/org/folio/rest/converter/costperuse/TitleCostPerUseConverter.java b/src/main/java/org/folio/rest/converter/costperuse/TitleCostPerUseConverter.java
index 03dbf25d4..d50fc2793 100644
--- a/src/main/java/org/folio/rest/converter/costperuse/TitleCostPerUseConverter.java
+++ b/src/main/java/org/folio/rest/converter/costperuse/TitleCostPerUseConverter.java
@@ -63,8 +63,7 @@ public TitleCostPerUse convert(TitleCostPerUseResult source) {
.withAttributes(new TitleCostPerUseDataAttributes()
.withUsage(usage)
.withAnalysis(analysis)
- .withParameters(convertParameters(source.getConfiguration()))
- );
+ .withParameters(convertParameters(source.getConfiguration())));
}
private Integer processPlatformUsages(TitleCostPerUseResult source,
diff --git a/src/main/java/org/folio/rest/converter/packages/PackageBulkFetchCollectionItemConverter.java b/src/main/java/org/folio/rest/converter/packages/PackageBulkFetchCollectionItemConverter.java
index 24c734d78..09dff51e2 100644
--- a/src/main/java/org/folio/rest/converter/packages/PackageBulkFetchCollectionItemConverter.java
+++ b/src/main/java/org/folio/rest/converter/packages/PackageBulkFetchCollectionItemConverter.java
@@ -14,9 +14,9 @@ public class PackageBulkFetchCollectionItemConverter implements Converter {
+public class PackageByIdConverter implements Converter {
private final Converter packageConverter;
public PackageByIdConverter(Converter packageConverter) {
@@ -15,7 +15,7 @@ public PackageByIdConverter(Converter packageConverter)
}
@Override
- public Package convert(PackageByIdData packageByIdData) {
- return packageConverter.convert(new PackageResult(packageByIdData));
+ public Package convert(PackageData packageData) {
+ return packageConverter.convert(new PackageResult(packageData));
}
}
diff --git a/src/main/java/org/folio/rest/converter/packages/PackageCollectionItemConverter.java b/src/main/java/org/folio/rest/converter/packages/PackageCollectionItemConverter.java
index 20902e7ab..041d59846 100644
--- a/src/main/java/org/folio/rest/converter/packages/PackageCollectionItemConverter.java
+++ b/src/main/java/org/folio/rest/converter/packages/PackageCollectionItemConverter.java
@@ -1,13 +1,18 @@
package org.folio.rest.converter.packages;
+import static org.folio.common.ListUtils.mapItemsNullable;
import static org.folio.rest.converter.common.ConverterConsts.CONTENT_TYPES;
import static org.folio.rest.converter.packages.PackageConverterUtils.createEmptyPackageRelationship;
import static org.folio.rest.util.RestConstants.PACKAGES_TYPE;
+import org.folio.holdingsiq.model.AlternateName;
import org.folio.holdingsiq.model.PackageData;
+import org.folio.holdingsiq.model.Visibility;
import org.folio.rest.jaxrs.model.Coverage;
+import org.folio.rest.jaxrs.model.PackageAltName;
import org.folio.rest.jaxrs.model.PackageCollectionItem;
import org.folio.rest.jaxrs.model.PackageDataAttributes;
+import org.folio.rest.jaxrs.model.PackageVisibility;
import org.folio.rest.jaxrs.model.VisibilityData;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@@ -20,26 +25,56 @@ public PackageCollectionItem convert(PackageData packageData) {
return new PackageCollectionItem()
.withId(packageData.getVendorId() + "-" + packageData.getPackageId())
.withType(PACKAGES_TYPE)
- .withAttributes(new PackageDataAttributes()
- .withContentType(CONTENT_TYPES.get(packageData.getContentType().toLowerCase()))
- .withCustomCoverage(convertCustomCoverage(packageData))
- .withIsCustom(packageData.getIsCustom())
- .withIsSelected(packageData.getIsSelected())
- .withName(packageData.getPackageName())
- .withPackageId(packageData.getPackageId())
- .withPackageType(packageData.getPackageType())
- .withProviderId(packageData.getVendorId())
- .withProviderName(packageData.getVendorName())
- .withSelectedCount(packageData.getSelectedCount())
- .withTitleCount(packageData.getTitleCount())
- .withAllowKbToAddTitles(packageData.getAllowEbscoToAddTitles())
- .withVisibilityData(convertVisibilityData(packageData)))
+ .withAttributes(convertAttributes(packageData))
.withRelationships(createEmptyPackageRelationship());
}
+ @SuppressWarnings("checkstyle:MethodLength")
+ private PackageDataAttributes convertAttributes(PackageData packageData) {
+ return new PackageDataAttributes()
+ .withAllowKbToAddTitles(packageData.getAllowEbscoToAddTitles())
+ .withContentType(CONTENT_TYPES.get(packageData.getContentType().toLowerCase()))
+ .withCustomAltNames(mapItemsNullable(packageData.getCustomAltNames(), this::convertAltName))
+ .withCustomCoverage(convertCustomCoverage(packageData))
+ .withCustomDescription(packageData.getCustomDescription())
+ .withCustomDisplayName(packageData.getCustomDisplayName())
+ .withIsAvailableForSelection(packageData.getAvailableForSelection())
+ .withIsCustom(packageData.getIsCustom())
+ .withIsFreeAccess(packageData.getPackageFreeAccess())
+ .withIsPrimaryPackage(packageData.getIsPrimaryPackage())
+ .withIsSelected(packageData.getIsSelected())
+ .withManagedAltNames(mapItemsNullable(packageData.getManagedAltNames(), this::convertAltName))
+ .withManagedDescription(packageData.getManagedDescription())
+ .withName(packageData.getPackageName())
+ .withPackageId(packageData.getPackageId())
+ .withPackageType(packageData.getPackageType())
+ .withProviderId(packageData.getVendorId())
+ .withProviderName(packageData.getVendorName())
+ .withProxiedUrl(packageData.getProxiedUrl())
+ .withSelectedCount(packageData.getSelectedCount())
+ .withTitleCount(packageData.getTitleCount())
+ .withUrl(packageData.getPackageUrl())
+ .withVisibility(mapItemsNullable(packageData.getVisibilityDetails(), this::convertVisibility))
+ .withVisibilityData(convertVisibilityData(packageData));
+ }
+
private VisibilityData convertVisibilityData(PackageData packageData) {
- return new VisibilityData().withIsHidden(packageData.getVisibilityData().getIsHidden())
- .withReason(packageData.getVisibilityData().getReason().equals("Hidden by EP") ? "Set by system" : "");
+ var isHidden = packageData.getVisibilityDetails().stream()
+ .map(Visibility::hidden)
+ .reduce(Boolean::logicalOr);
+ var hiddenByEp = packageData.getVisibilityDetails().stream()
+ .map(Visibility::reason)
+ .filter("Hidden by EP"::equals)
+ .findAny();
+ return new VisibilityData().withIsHidden(isHidden.orElse(false))
+ .withReason(hiddenByEp.isPresent() ? "Set by system" : "");
+ }
+
+ private PackageVisibility convertVisibility(Visibility visibility) {
+ return new PackageVisibility()
+ .withCategory(PackageVisibility.Category.fromValue(visibility.category().toUpperCase()))
+ .withHidden(visibility.hidden())
+ .withReason(visibility.reason());
}
private Coverage convertCustomCoverage(PackageData packageData) {
@@ -47,4 +82,10 @@ private Coverage convertCustomCoverage(PackageData packageData) {
.withBeginCoverage(packageData.getCustomCoverage().getBeginCoverage())
.withEndCoverage(packageData.getCustomCoverage().getEndCoverage());
}
+
+ private PackageAltName convertAltName(AlternateName alternateName) {
+ return new PackageAltName()
+ .withId(alternateName.id() == null ? null : alternateName.id())
+ .withAltName(alternateName.altName());
+ }
}
diff --git a/src/main/java/org/folio/rest/converter/packages/PackageCollectionResultConverter.java b/src/main/java/org/folio/rest/converter/packages/PackageCollectionResultConverter.java
index b60a11a01..628ede9b3 100644
--- a/src/main/java/org/folio/rest/converter/packages/PackageCollectionResultConverter.java
+++ b/src/main/java/org/folio/rest/converter/packages/PackageCollectionResultConverter.java
@@ -55,9 +55,6 @@ private List getTagsById(List packages, PackageId packageId)
}
private PackageId createPackageId(PackageData packageData) {
- return PackageId.builder()
- .providerIdPart(packageData.getVendorId())
- .packageIdPart(packageData.getPackageId())
- .build();
+ return new PackageId(packageData.getVendorId(), packageData.getPackageId());
}
}
diff --git a/src/main/java/org/folio/rest/converter/packages/PackageConverter.java b/src/main/java/org/folio/rest/converter/packages/PackageConverter.java
index 61101488b..251f9ff1c 100644
--- a/src/main/java/org/folio/rest/converter/packages/PackageConverter.java
+++ b/src/main/java/org/folio/rest/converter/packages/PackageConverter.java
@@ -8,7 +8,6 @@
import java.util.List;
import java.util.Objects;
-import org.folio.holdingsiq.model.PackageByIdData;
import org.folio.holdingsiq.model.PackageData;
import org.folio.holdingsiq.model.Titles;
import org.folio.holdingsiq.model.TokenInfo;
@@ -51,37 +50,37 @@ public PackageConverter(Converter packageCol
@Override
public Package convert(PackageResult result) {
- PackageByIdData packageByIdData = result.getPackageData();
+ PackageData packageData = result.getPackageData();
- Package packageData = new Package()
- .withData(packageCollectionItemConverter.convert(packageByIdData))
+ Package packageResult = new Package()
+ .withData(packageCollectionItemConverter.convert(packageData))
.withJsonapi(RestConstants.JSONAPI);
- packageData.getData()
+ packageResult.getData()
.withRelationships(createEmptyPackageRelationship())
.withType(PACKAGES_TYPE)
.getAttributes()
- .withProxy(convertToProxy(packageByIdData.getProxy()))
- .withPackageToken(tokenInfoConverter.convert(packageByIdData.getPackageToken()))
+ .withProxy(convertToProxy(packageData.getProxy()))
+ .withPackageToken(tokenInfoConverter.convert(packageData.getPackageToken()))
.withTags(result.getTags());
- addTitlesRelationship(result, packageData, packageByIdData);
- addProviderRelationship(result, packageData);
- addAccessTypeRelationship(result, packageData);
- return packageData;
+ addTitlesRelationship(result, packageResult, packageData);
+ addProviderRelationship(result, packageResult);
+ addAccessTypeRelationship(result, packageResult);
+ return packageResult;
}
- private void addTitlesRelationship(PackageResult result, Package packageData, PackageByIdData packageByIdData) {
+ private void addTitlesRelationship(PackageResult result, Package packageDto, PackageData packageData) {
Titles titles = result.getTitles();
if (titles != null) {
- packageData.getData()
+ packageDto.getData()
.withRelationships(new PackageRelationship()
.withResources(new HasManyRelationship()
.withMeta(new MetaDataIncluded()
.withIncluded(true))
- .withData(convertResourcesRelationship(packageByIdData, titles))));
+ .withData(convertResourcesRelationship(packageData, titles))));
- packageData
+ packageDto
.getIncluded()
.addAll(Objects.requireNonNull(resourcesConverter.convert(titles)).getData());
}
@@ -119,10 +118,10 @@ private void addAccessTypeRelationship(PackageResult result, Package packageData
return proxy != null ? new Proxy().withId(proxy.getId()).withInherited(proxy.getInherited()) : null;
}
- private List convertResourcesRelationship(PackageByIdData packageByIdData, Titles titles) {
+ private List convertResourcesRelationship(PackageData packageData, Titles titles) {
return mapItems(titles.getTitleList(),
title -> new RelationshipData()
- .withId(packageByIdData.getVendorId() + "-" + packageByIdData.getPackageId() + "-" + title.getTitleId())
+ .withId(packageData.getVendorId() + "-" + packageData.getPackageId() + "-" + title.getTitleId())
.withType(RESOURCES_TYPE));
}
}
diff --git a/src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java b/src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java
index 5d78b916e..9b01a2f86 100644
--- a/src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java
+++ b/src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java
@@ -1,32 +1,38 @@
package org.folio.rest.converter.packages;
+import static org.folio.common.ListUtils.mapItems;
import static org.folio.rest.converter.packages.PackageConverterUtils.CONTENT_TYPE_TO_RMAPI_CODE;
+import java.util.Arrays;
+import org.folio.holdingsiq.model.AlternateName;
import org.folio.holdingsiq.model.CoverageDates;
import org.folio.holdingsiq.model.PackagePut;
+import org.folio.holdingsiq.model.Proxy;
import org.folio.holdingsiq.model.TokenInfo;
+import org.folio.holdingsiq.model.Visibility;
import org.folio.rest.jaxrs.model.PackagePutDataAttributes;
import org.folio.rest.jaxrs.model.PackagePutRequest;
+import org.folio.rest.jaxrs.model.PackageVisibility;
import org.springframework.stereotype.Component;
@Component
public class PackageRequestConverter {
public PackagePut convertToRmApiCustomPackagePutRequest(PackagePutRequest request) {
- PackagePutDataAttributes attributes = request.getData().getAttributes();
- PackagePut.PackagePutBuilder builder = convertCommonAttributesToPackagePutRequest(attributes);
+ var attributes = request.getData().getAttributes();
+ var builder = convertCommonAttributesToPackagePutRequest(attributes);
builder.packageName(attributes.getName());
- Integer contentType = CONTENT_TYPE_TO_RMAPI_CODE.get(attributes.getContentType());
+ var contentType = CONTENT_TYPE_TO_RMAPI_CODE.get(attributes.getContentType());
builder.contentType(contentType != null ? contentType : 6);
return builder.build();
}
public PackagePut convertToRmApiPackagePutRequest(PackagePutRequest request) {
- PackagePutDataAttributes attributes = request.getData().getAttributes();
- PackagePut.PackagePutBuilder builder = convertCommonAttributesToPackagePutRequest(attributes);
+ var attributes = request.getData().getAttributes();
+ var builder = convertCommonAttributesToPackagePutRequest(attributes);
builder.allowEbscoToAddTitles(attributes.getAllowKbToAddTitles());
if (attributes.getPackageToken() != null) {
- TokenInfo tokenInfo = TokenInfo.builder()
+ var tokenInfo = TokenInfo.builder()
.value(attributes.getPackageToken().getValue())
.build();
builder.packageToken(tokenInfo);
@@ -34,14 +40,15 @@ public PackagePut convertToRmApiPackagePutRequest(PackagePutRequest request) {
return builder.build();
}
+ @SuppressWarnings("checkstyle:MethodLength")
private PackagePut.PackagePutBuilder convertCommonAttributesToPackagePutRequest(PackagePutDataAttributes attributes) {
- PackagePut.PackagePutBuilder builder = PackagePut.builder();
+ var builder = PackagePut.builder();
builder.isSelected(attributes.getIsSelected());
builder.isFullPackage(attributes.getIsFullPackage());
if (attributes.getProxy() != null) {
- org.folio.holdingsiq.model.Proxy proxy = org.folio.holdingsiq.model.Proxy.builder()
+ var proxy = Proxy.builder()
.id(attributes.getProxy().getId())
// RM API gives an error when we pass inherited as true along with updated proxy value
// Hard code it to false; it should not affect the state of inherited that RM API maintains
@@ -50,18 +57,35 @@ private PackagePut.PackagePutBuilder convertCommonAttributesToPackagePutRequest(
builder.proxy(proxy);
}
- if (attributes.getVisibilityData() != null) {
- builder.isHidden(attributes.getVisibilityData().getIsHidden());
- }
-
if (attributes.getCustomCoverage() != null) {
- CoverageDates coverageDates = CoverageDates.builder()
+ var coverageDates = CoverageDates.builder()
.beginCoverage(attributes.getCustomCoverage().getBeginCoverage())
.endCoverage(attributes.getCustomCoverage().getEndCoverage())
.build();
builder.customCoverage(coverageDates);
}
+ if (attributes.getCustomAltNames() != null) {
+ builder.customAltNames(mapItems(attributes.getCustomAltNames(),
+ packageAltName -> new AlternateName(packageAltName.getId(), packageAltName.getAltName())));
+ }
+
+ builder.customDescription(attributes.getCustomDescription());
+ builder.customDisplayName(attributes.getCustomDisplayName());
+ builder.packageFreeAccess(attributes.getIsFreeAccess());
+ builder.packageUrl(attributes.getUrl());
+
+ var visibilityData = attributes.getVisibilityData();
+ if (visibilityData == null || visibilityData.getIsHidden() == null) {
+ builder.visibilityDetails(mapItems(attributes.getVisibility(),
+ pv -> new Visibility(pv.getCategory().value(), pv.getHidden(), pv.getReason())));
+ } else {
+ var visibilities = Arrays.stream(PackageVisibility.Category.values())
+ .map(category -> new Visibility(category.value(), visibilityData.getIsHidden(), visibilityData.getReason()))
+ .toList();
+ builder.visibilityDetails(visibilities);
+ }
+
return builder;
}
}
diff --git a/src/main/java/org/folio/rest/converter/resources/ResourceCollectionResultConverter.java b/src/main/java/org/folio/rest/converter/resources/ResourceCollectionResultConverter.java
index bd04bcebc..d0b1b6635 100644
--- a/src/main/java/org/folio/rest/converter/resources/ResourceCollectionResultConverter.java
+++ b/src/main/java/org/folio/rest/converter/resources/ResourceCollectionResultConverter.java
@@ -70,17 +70,12 @@ private ResourceCollectionItem mapResourceCollectionItem(List resour
}
private ResourceId createResourceId(Title title) {
- return ResourceId.builder()
- .providerIdPart(title.getCustomerResourcesList().getFirst().getVendorId())
- .packageIdPart(title.getCustomerResourcesList().getFirst().getPackageId())
- .titleIdPart(title.getTitleId()).build();
+ return new ResourceId(title.getCustomerResourcesList().getFirst().getVendorId(),
+ title.getCustomerResourcesList().getFirst().getPackageId(),
+ title.getTitleId());
}
private ResourceId createResourceId(DbHoldingInfo holding) {
- return ResourceId.builder()
- .providerIdPart(holding.getVendorId())
- .packageIdPart(holding.getPackageId())
- .titleIdPart(holding.getTitleId())
- .build();
+ return new ResourceId(holding.getVendorId(), holding.getPackageId(), holding.getTitleId());
}
}
diff --git a/src/main/java/org/folio/rest/converter/resources/ResourceResultConverter.java b/src/main/java/org/folio/rest/converter/resources/ResourceResultConverter.java
index 295018d7e..5c141eb72 100644
--- a/src/main/java/org/folio/rest/converter/resources/ResourceResultConverter.java
+++ b/src/main/java/org/folio/rest/converter/resources/ResourceResultConverter.java
@@ -12,7 +12,7 @@
import java.util.List;
import java.util.function.Function;
import org.folio.holdingsiq.model.CustomerResources;
-import org.folio.holdingsiq.model.PackageByIdData;
+import org.folio.holdingsiq.model.PackageData;
import org.folio.holdingsiq.model.VendorById;
import org.folio.rest.jaxrs.model.AccessType;
import org.folio.rest.jaxrs.model.HasOneRelationship;
@@ -44,12 +44,12 @@ public class ResourceResultConverter implements Converter vendorConverter;
@Autowired
- private Converter packageByIdConverter;
+ private Converter packageByIdConverter;
@Override
public List convert(ResourceResult resourceResult) {
var rmapiTitle = resourceResult.getTitle();
- PackageByIdData packageData = resourceResult.getPackageData();
+ PackageData packageData = resourceResult.getPackageData();
VendorById vendor = resourceResult.getVendor();
AccessType accessType = resourceResult.getAccessType();
boolean includeTitle = resourceResult.isIncludeTitle();
@@ -63,7 +63,7 @@ public List convert(ResourceResult resourceResult) {
private Function convertToResource(org.folio.holdingsiq.model.Title rmapiTitle,
boolean titleHasSelectedResources,
boolean includeTitle,
- VendorById vendor, PackageByIdData packageData,
+ VendorById vendor, PackageData packageData,
AccessType accessType) {
return resource -> {
Resource resultResource = new Resource()
@@ -83,7 +83,7 @@ private Function convertToResource(org.folio.holdin
}
private void includedResources(org.folio.holdingsiq.model.Title rmapiTitle, boolean includeTitle,
- @Nullable VendorById vendor, @Nullable PackageByIdData packageData,
+ @Nullable VendorById vendor, @Nullable PackageData packageData,
@Nullable AccessType accessType, Resource resultResource) {
resultResource.setIncluded(new ArrayList<>());
if (includeTitle) {
@@ -112,7 +112,7 @@ private void includeAccessType(AccessType accessType, Resource resultResource) {
.withIncluded(true)));
}
- private void includePackage(PackageByIdData packageData, Resource resultResource) {
+ private void includePackage(PackageData packageData, Resource resultResource) {
resultResource.getIncluded().add(requireNonNull(packageByIdConverter.convert(packageData)).getData());
resultResource.getData()
.getRelationships()
diff --git a/src/main/java/org/folio/rest/exception/QueryParamsValidationException.java b/src/main/java/org/folio/rest/exception/QueryParamsValidationException.java
new file mode 100644
index 000000000..086f824a0
--- /dev/null
+++ b/src/main/java/org/folio/rest/exception/QueryParamsValidationException.java
@@ -0,0 +1,21 @@
+package org.folio.rest.exception;
+
+import java.io.Serial;
+import java.util.List;
+
+public class QueryParamsValidationException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private final List messageDetail;
+
+ public QueryParamsValidationException(List messageDetail) {
+ super("Query params validation failed.");
+ this.messageDetail = messageDetail;
+ }
+
+ public List getMessageDetail() {
+ return messageDetail;
+ }
+}
diff --git a/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java b/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java
index acfe81b7b..9a6dd172e 100644
--- a/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java
+++ b/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java
@@ -29,12 +29,12 @@
import org.folio.cache.VertxCache;
import org.folio.config.cache.VendorIdCacheKey;
import org.folio.holdingsiq.model.CustomerResources;
-import org.folio.holdingsiq.model.OkapiData;
-import org.folio.holdingsiq.model.PackageByIdData;
+import org.folio.holdingsiq.model.PackageData;
import org.folio.holdingsiq.model.PackageId;
import org.folio.holdingsiq.model.PackagePost;
import org.folio.holdingsiq.model.PackagePut;
import org.folio.holdingsiq.model.Packages;
+import org.folio.holdingsiq.model.RequestContext;
import org.folio.holdingsiq.model.Titles;
import org.folio.holdingsiq.service.exception.ResourceNotFoundException;
import org.folio.properties.common.SearchProperties;
@@ -64,7 +64,10 @@
import org.folio.rest.jaxrs.model.ResourceCollection;
import org.folio.rest.jaxrs.model.Tags;
import org.folio.rest.jaxrs.resource.EholdingsPackages;
-import org.folio.rest.model.filter.Filter;
+import org.folio.rest.model.filter.AccessTypeFilter;
+import org.folio.rest.model.filter.PackageRecordFilter;
+import org.folio.rest.model.filter.ResourceFilter;
+import org.folio.rest.model.filter.TagFilter;
import org.folio.rest.util.ErrorHandler;
import org.folio.rest.util.ErrorUtil;
import org.folio.rest.util.template.RmApiTemplate;
@@ -113,7 +116,7 @@ public class EholdingsPackagesImpl implements EholdingsPackages {
private RmApiTemplateFactory templateFactory;
@Autowired
@Qualifier("vendorIdCache")
- private VertxCache vendorIdCache;
+ private VertxCache vendorIdCache;
@Autowired
private TagRepository tagRepository;
@Autowired
@@ -138,29 +141,43 @@ public EholdingsPackagesImpl() {
SpringContextUtil.autowireDependencies(this, Vertx.currentContext());
}
+ @SuppressWarnings("checkstyle:MethodLength")
@Override
@Validate
@HandleValidationErrors
- public void getEholdingsPackages(String filterCustom, String q, String filterSelected, String filterType,
- List filterTags, List filterAccessType, String sort, int page,
- int count,
- Map okapiHeaders, Handler> asyncResultHandler,
- Context vertxContext) {
-
- var filter = Filter.getSortableFilter(prepareFilterForPackage(filterCustom, q, filterSelected, filterType,
- filterTags, filterAccessType), sort, page, count);
+ public void getEholdingsPackages(String filterCustom, String q, String queryField, String queryType,
+ boolean highlight, String filterSelected, String filterType, String filterVisibility,
+ String filterFreeAccess, List filterTags, List filterAccessType,
+ String sort, int page, int count, Map okapiHeaders,
+ Handler> asyncResultHandler, Context vertxContext) {
+ var filter = PackageRecordFilter.builder()
+ .query(q)
+ .queryField(queryField)
+ .queryType(queryType)
+ .highlight(highlight)
+ .filterCustom(filterCustom)
+ .filterSelected(filterSelected)
+ .filterType(filterType)
+ .filterVisibility(filterVisibility)
+ .filterFreeAccess(filterFreeAccess)
+ .filterTags(filterTags)
+ .filterAccessType(filterAccessType)
+ .sort(sort)
+ .page(page)
+ .count(count)
+ .build();
var template = templateFactory.createTemplate(okapiHeaders, asyncResultHandler);
if (filter.isTagsFilter()) {
template.requestAction(
- context -> filteredEntitiesLoader.fetchPackagesByTagFilter(filter.createTagFilter(), context));
+ context -> filteredEntitiesLoader.fetchPackagesByTagFilter(TagFilter.from(filter), context));
} else if (filter.isAccessTypeFilter()) {
template.requestAction(context -> filteredEntitiesLoader
- .fetchPackagesByAccessTypeFilter(filter.createAccessTypeFilter(), context));
+ .fetchPackagesByAccessTypeFilter(AccessTypeFilter.from(filter), context));
} else {
template
.requestAction(context -> {
- if (Boolean.TRUE.equals(filter.getFilterCustom())) {
+ if (Boolean.TRUE.equals(filter.resolveFilterCustom())) {
return getCustomProviderId(context)
.thenCompose(providerId -> retrievePackages(providerId, filter, context));
} else {
@@ -226,12 +243,13 @@ public void putEholdingsPackagesByPackageId(String packageId, String contentType
Map okapiHeaders,
Handler> asyncResultHandler, Context vertxContext) {
PackageId parsedPackageId = parsePackageId(packageId);
+ var packageIdPart = parsedPackageId.packageIdPart();
templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
- .requestAction(context -> context.getPackagesService().retrievePackage(parsedPackageId)
- .thenCompose(packageByIdData -> fetchAccessType(entity, context)
- .thenCompose(accessType -> processUpdateRequest(entity, packageByIdData, context)
+ .requestAction(context -> context.getPackagesService().retrievePackage(packageIdPart)
+ .thenCompose(packageData -> fetchAccessType(entity, context)
+ .thenCompose(accessType -> processUpdateRequest(entity, packageData, context)
.thenCompose(voidEntity -> {
- CompletableFuture future = context.getPackagesService().retrievePackage(parsedPackageId);
+ CompletableFuture future = context.getPackagesService().retrievePackage(packageIdPart);
return handleDeletedPackage(future, parsedPackageId, context);
})
.thenApply(packageById -> new PackageResult(packageById, null, null))
@@ -250,19 +268,21 @@ public void deleteEholdingsPackagesByPackageId(String packageId, Map> asyncResultHandler,
Context vertxContext) {
PackageId parsedPackageId = parsePackageId(packageId);
+ var packageIdPart = parsedPackageId.packageIdPart();
templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
.requestAction(context ->
- context.getPackagesService().retrievePackage(parsedPackageId)
+ context.getPackagesService().retrievePackage(packageIdPart)
.thenCompose(packageData -> {
if (BooleanUtils.isNotTrue(packageData.getIsCustom())) {
throw new InputValidationException(INVALID_PACKAGE_TITLE, INVALID_PACKAGE_DETAILS);
}
- return context.getPackagesService().deletePackage(parsedPackageId)
+ return context.getPackagesService().deletePackage(packageIdPart)
.thenCompose(v -> deleteAssignedResources(parsedPackageId, context));
}))
.execute();
}
+ @SuppressWarnings("checkstyle:MethodLength")
@Override
@Validate
@HandleValidationErrors
@@ -275,17 +295,29 @@ public void getEholdingsPackagesResourcesByPackageId(String packageId, List> asyncResultHandler,
Context vertxContext) {
- var filter = Filter.getSortableFilter(prepareFilterForResource(packageId, filterTags, filterAccessType,
- filterSelected, filterType, filterName, filterIsxn, filterSubject, filterPublisher), sort, page, count);
+ var filter = ResourceFilter.builder()
+ .packageId(packageId)
+ .filterTags(filterTags)
+ .filterAccessType(filterAccessType)
+ .filterSelected(filterSelected)
+ .filterType(filterType)
+ .filterName(filterName)
+ .filterIsxn(filterIsxn)
+ .filterSubject(filterSubject)
+ .filterPublisher(filterPublisher)
+ .sort(sort)
+ .page(page)
+ .count(count)
+ .build();
RmApiTemplate template = templateFactory.createTemplate(okapiHeaders, asyncResultHandler);
if (filter.isTagsFilter()) {
template.requestAction(
- context -> filteredEntitiesLoader.fetchResourcesByTagFilter(filter.createTagFilter(), context));
+ context -> filteredEntitiesLoader.fetchResourcesByTagFilter(TagFilter.from(filter), context));
} else if (filter.isAccessTypeFilter()) {
template.requestAction(
- context -> filteredEntitiesLoader.fetchResourcesByAccessTypeFilter(filter.createAccessTypeFilter(), context));
+ context -> filteredEntitiesLoader.fetchResourcesByAccessTypeFilter(AccessTypeFilter.from(filter), context));
} else {
template.requestAction(retrievePackageTitles(filter));
}
@@ -298,17 +330,17 @@ public void getEholdingsPackagesResourcesByPackageId(String packageId, List okapiHeaders,
+ Map headers,
Handler> asyncResultHandler,
Context vertxContext) {
- userKbCredentialsService.findByUser(okapiHeaders)
+ userKbCredentialsService.findByUser(headers)
.thenCompose(creds -> {
packageTagsPutBodyValidator.validate(entity);
PackageTagsDataAttributes attributes = entity.getData().getAttributes();
return updateTags(attributes.getTags(), createDbPackage(packageId, UUID.fromString(creds.getId()), attributes),
- new OkapiData(okapiHeaders).getTenant())
+ new RequestContext(headers).getTenant())
.thenAccept(o2 ->
asyncResultHandler
.handle(
@@ -334,53 +366,25 @@ public void postEholdingsPackagesBulkFetch(String contentType, PackagePostBulkFe
.executeWithResult(PackageBulkFetchCollection.class);
}
- private Function> retrievePackageTitles(Filter filter) {
+ private Function> retrievePackageTitles(ResourceFilter filter) {
return context -> {
- var pkgId = filter.getPackageId();
+ var pkgId = filter.parsePackageId();
return context.getTitlesService()
- .retrieveTitles(pkgId.getProviderIdPart(), pkgId.getPackageIdPart(), filter.createFilterQuery(),
- searchProperties.titlesSearchType(), filter.getSort(), filter.getPage(), filter.getCount())
+ .retrieveTitles(pkgId.providerIdPart(), pkgId.packageIdPart(), filter.createFilterQuery(),
+ searchProperties.titlesSearchType(), filter.resolveSort(), filter.getPage(), filter.getCount())
.thenApply(titles -> titleCollectionConverter.convert(titles))
.thenCompose(loadResourceTags(context))
.thenCompose(loadResourceAccessTypes(context));
};
}
- private CompletableFuture retrievePackages(Long providerId, Filter filter, RmApiTemplateContext context) {
- return context.getPackagesService()
- .retrievePackages(filter.getFilterSelected(), filter.getFilterType(), searchProperties.packagesSearchType(),
- providerId, filter.getQuery(), filter.getPage(), filter.getCount(), filter.getSort());
- }
-
- @SuppressWarnings("java:S107")
- private Filter.FilterBuilder prepareFilterForResource(String packageId, List filterTags,
- List filterAccessType, String filterSelected,
- String filterType, String filterName, String filterIsxn,
- String filterSubject, String filterPublisher) {
- return Filter.builder()
- .recordType(RecordType.RESOURCE)
- .filterTags(filterTags)
- .packageId(packageId)
- .filterAccessType(filterAccessType)
- .filterSelected(filterSelected)
- .filterType(filterType)
- .filterName(filterName)
- .filterIsxn(filterIsxn)
- .filterSubject(filterSubject)
- .filterPublisher(filterPublisher);
- }
-
- private Filter.FilterBuilder prepareFilterForPackage(String filterCustom, String q, String filterSelected,
- String filterType, List filterTags,
- List filterAccessType) {
- return Filter.builder()
- .recordType(RecordType.PACKAGE)
- .query(q)
- .filterTags(filterTags)
- .filterCustom(filterCustom)
- .filterAccessType(filterAccessType)
- .filterSelected(filterSelected)
- .filterType(filterType);
+ private CompletableFuture retrievePackages(Integer providerId, PackageRecordFilter filter,
+ RmApiTemplateContext context) {
+ var packageFilter = filter.toClientFilter(searchProperties);
+ var pageable = filter.toPageable();
+ return providerId == null
+ ? context.getPackagesService().retrievePackages(packageFilter, pageable)
+ : context.getPackagesService().retrievePackages(providerId, packageFilter, pageable);
}
private CompletableFuture updateAccessTypeMapping(AccessType accessType,
@@ -397,17 +401,17 @@ private CompletableFuture updateAccessTypeMapping(AccessType acce
private CompletableFuture updateRecordMapping(AccessType accessType, String recordId,
RmApiTemplateContext context) {
return accessTypeMappingsService.update(accessType, recordId, RecordType.PACKAGE, context.getCredentialsId(),
- context.getOkapiData().getHeaders());
+ context.getRequestContext().getHeaders());
}
private CompletableFuture fetchAccessType(PackagePutRequest entity,
RmApiTemplateContext context) {
String accessTypeId = entity.getData().getAttributes().getAccessTypeId();
if (accessTypeId == null) {
- return CompletableFuture.completedFuture(null);
+ return completedFuture(null);
} else {
return accessTypesService.findByCredentialsAndAccessTypeId(context.getCredentialsId(), accessTypeId, false,
- context.getOkapiData().getHeaders());
+ context.getRequestContext().getHeaders());
}
}
@@ -439,7 +443,7 @@ private Function>
return titleCollection -> {
Map resourceIdToTitle = mapResourceIdToTitleResult(titleCollection);
- return tagRepository.findPerRecord(context.getOkapiData().getTenant(),
+ return tagRepository.findPerRecord(context.getRequestContext().getTenant(),
new ArrayList<>(resourceIdToTitle.keySet()),
RecordType.RESOURCE)
.thenApply(tagMap -> {
@@ -461,7 +465,7 @@ private Function>
return titleCollection -> {
Map resourceIdToAccessType = mapResourceIdToTitleResult(titleCollection);
String credentialsId = context.getCredentialsId();
- String tenant = context.getOkapiData().getTenant();
+ String tenant = context.getRequestContext().getTenant();
return accessTypesService.findPerRecord(credentialsId, new ArrayList<>(resourceIdToAccessType.keySet()),
RecordType.RESOURCE, tenant)
.thenApply(accessTypeMap -> {
@@ -490,12 +494,12 @@ private String getResourceId(TitleResult titleResult) {
return resource.getVendorId() + "-" + resource.getPackageId() + "-" + resource.getTitleId();
}
- private CompletableFuture getCustomProviderId(RmApiTemplateContext context) {
+ private CompletableFuture getCustomProviderId(RmApiTemplateContext context) {
VendorIdCacheKey cacheKey = VendorIdCacheKey.builder()
- .tenant(context.getOkapiData().getTenant())
+ .tenant(context.getRequestContext().getTenant())
.rmapiConfiguration(context.getConfiguration())
.build();
- Long cachedId = vendorIdCache.getValue(cacheKey);
+ var cachedId = vendorIdCache.getValue(cacheKey);
if (cachedId != null) {
return completedFuture(cachedId);
} else {
@@ -509,7 +513,7 @@ private CompletableFuture getCustomProviderId(RmApiTemplateContext context
private CompletableFuture deleteTags(PackageId packageId, RmApiTemplateContext context) {
UUID credentialsId = toUUID(context.getCredentialsId());
- String tenant = context.getOkapiData().getTenant();
+ String tenant = context.getRequestContext().getTenant();
return packageRepository.delete(packageId, credentialsId, tenant)
.thenCompose(o -> tagRepository.deleteRecordTags(tenant, packageIdToString(packageId), RecordType.PACKAGE))
@@ -535,7 +539,7 @@ private CompletableFuture updateStoredPackage(Tags tags, DbPackage pkg, St
return packageRepository.delete(pkg.getId(), pkg.getCredentialsId(), tenant);
}
- private CompletableFuture processUpdateRequest(PackagePutRequest entity, PackageByIdData originalPackage,
+ private CompletableFuture processUpdateRequest(PackagePutRequest entity, PackageData originalPackage,
RmApiTemplateContext context) {
Boolean isEntityCustom = entity.getData().getAttributes().getIsCustom();
validateIsCustomMatch(originalPackage.getIsCustom(), isEntityCustom);
@@ -549,7 +553,7 @@ private CompletableFuture processUpdateRequest(PackagePutRequest entity, P
packagePutBody = converter.convertToRmApiPackagePutRequest(entity);
}
return context.getPackagesService()
- .updatePackage(parsePackageId(originalPackage.getFullPackageId()), packagePutBody);
+ .updatePackage(originalPackage.getPackageId(), packagePutBody);
}
private void validateIsCustomMatch(Boolean isOriginalCustom, Boolean isUpdatableCustom) {
@@ -565,8 +569,8 @@ private void validateIsCustomMatch(Boolean isOriginalCustom, Boolean isUpdatable
*
* @return future with initial result, or exceptionally completed future if deletion of tags failed
*/
- private CompletableFuture handleDeletedPackage(CompletableFuture future,
- PackageId packageId, RmApiTemplateContext context) {
+ private CompletableFuture handleDeletedPackage(CompletableFuture future,
+ PackageId packageId, RmApiTemplateContext context) {
CompletableFuture deleteFuture = new CompletableFuture<>();
return future.whenComplete((packageById, e) -> {
if (e instanceof ResourceNotFoundException) {
diff --git a/src/main/java/org/folio/rest/impl/EholdingsProvidersImpl.java b/src/main/java/org/folio/rest/impl/EholdingsProvidersImpl.java
index 0a0e02a83..b6538ee62 100644
--- a/src/main/java/org/folio/rest/impl/EholdingsProvidersImpl.java
+++ b/src/main/java/org/folio/rest/impl/EholdingsProvidersImpl.java
@@ -21,11 +21,12 @@
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import javax.ws.rs.core.Response;
-import org.folio.holdingsiq.model.OkapiData;
import org.folio.holdingsiq.model.Packages;
+import org.folio.holdingsiq.model.RequestContext;
import org.folio.holdingsiq.model.VendorById;
import org.folio.holdingsiq.model.VendorPut;
import org.folio.holdingsiq.service.exception.ResourceNotFoundException;
+import org.folio.properties.common.SearchProperties;
import org.folio.repository.RecordKey;
import org.folio.repository.RecordType;
import org.folio.repository.packages.PackageRepository;
@@ -46,7 +47,10 @@
import org.folio.rest.jaxrs.model.ProviderTagsPutRequest;
import org.folio.rest.jaxrs.model.Tags;
import org.folio.rest.jaxrs.resource.EholdingsProviders;
-import org.folio.rest.model.filter.Filter;
+import org.folio.rest.model.filter.AccessTypeFilter;
+import org.folio.rest.model.filter.PackageRecordFilter;
+import org.folio.rest.model.filter.ProviderFilter;
+import org.folio.rest.model.filter.TagFilter;
import org.folio.rest.util.ErrorHandler;
import org.folio.rest.util.ErrorUtil;
import org.folio.rest.util.template.RmApiTemplate;
@@ -62,7 +66,6 @@
import org.folio.spring.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.converter.Converter;
@SuppressWarnings("java:S6813")
@@ -91,8 +94,8 @@ public class EholdingsProvidersImpl implements EholdingsProviders {
@Autowired
@Qualifier("securedUserCredentialsService")
private UserKbCredentialsService userKbCredentialsService;
- @Value("${kb.ebsco.search-type.packages}")
- private String packagesSearchType;
+ @Autowired
+ private SearchProperties searchProperties;
public EholdingsProvidersImpl() {
SpringContextUtil.autowireDependencies(this, Vertx.currentContext());
@@ -106,8 +109,7 @@ public void getEholdingsProviders(String q, List filterTags, String sort
Handler> asyncResultHandler, Context vertxContext) {
RmApiTemplate template = templateFactory.createTemplate(okapiHeaders, asyncResultHandler);
- Filter filter = Filter.builder()
- .recordType(RecordType.PROVIDER)
+ var filter = ProviderFilter.builder()
.query(q)
.filterTags(filterTags)
.sort(sort)
@@ -116,25 +118,25 @@ public void getEholdingsProviders(String q, List filterTags, String sort
.build();
if (filter.isTagsFilter()) {
template.requestAction(
- context -> filteredEntitiesLoader.fetchProvidersByTagFilter(filter.createTagFilter(), context));
+ context -> filteredEntitiesLoader.fetchProvidersByTagFilter(TagFilter.from(filter), context));
} else {
template
.requestAction(context ->
- context.getProvidersService().retrieveProviders(q, page, count, filter.getSort()));
+ context.getProvidersService().retrieveProviders(q, page, count, filter.resolveSort()));
}
template.executeWithResult(ProviderCollection.class);
}
@Override
+ @Validate
@HandleValidationErrors
public void getEholdingsProvidersByProviderId(String providerId, String include, Map okapiHeaders,
Handler> asyncResultHandler,
Context vertxContext) {
- long providerIdLong = parseProviderId(providerId);
-
+ var parseProviderId = parseProviderId(providerId);
templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
.requestAction(context ->
- context.getProvidersService().retrieveProvider(providerIdLong, include)
+ context.getProvidersService().retrieveProvider(parseProviderId, include)
.thenCompose(result -> loadTags(result, context))
)
.addErrorMapper(ResourceNotFoundException.class, exception ->
@@ -144,38 +146,40 @@ public void getEholdingsProvidersByProviderId(String providerId, String include,
}
@Override
+ @Validate
@HandleValidationErrors
public void putEholdingsProvidersByProviderId(String providerId, String contentType, ProviderPutRequest entity,
Map okapiHeaders,
Handler> asyncResultHandler,
Context vertxContext) {
- long providerIdLong = parseProviderId(providerId);
bodyValidator.validate(entity);
templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
.requestAction(context ->
- processUpdateRequest(entity, providerIdLong, context)
+ processUpdateRequest(entity, parseProviderId(providerId), context)
.thenApply(result -> new VendorResult(result, null)))
.addErrorMapper(InputValidationException.class, error422InputValidationMapper())
.executeWithResult(Provider.class);
}
@Override
+ @Validate
+ @HandleValidationErrors
public void putEholdingsProvidersTagsByProviderId(String providerId, String contentType,
ProviderTagsPutRequest entity,
- Map okapiHeaders,
+ Map headers,
Handler> asyncResultHandler,
Context vertxContext) {
final Tags tags = entity.getData().getAttributes().getTags();
- userKbCredentialsService.findByUser(okapiHeaders)
+ userKbCredentialsService.findByUser(headers)
.thenCompose(creds -> {
ProviderTagsDataAttributes attributes = entity.getData().getAttributes();
providerTagsPutBodyValidator.validate(entity, attributes);
return updateTags(
createDbProvider(providerId, UUID.fromString(creds.getId()), entity.getData().getAttributes()),
- tags, new OkapiData(okapiHeaders).getTenant())
+ tags, new RequestContext(headers).getTenant())
.thenAccept(ob -> asyncResultHandler.handle(
Future.succeededFuture(PutEholdingsProvidersTagsByProviderIdResponse.respond200WithApplicationVndApiJson(
convertToProviderTags(attributes)
@@ -189,28 +193,45 @@ tags, new OkapiData(okapiHeaders).getTenant())
});
}
+ @SuppressWarnings("checkstyle:MethodLength")
@Override
@Validate
@HandleValidationErrors
- public void getEholdingsProvidersPackagesByProviderId(String providerId, String q, List filterTags,
+ public void getEholdingsProvidersPackagesByProviderId(String providerId, String q, String queryField,
+ String queryType, boolean highlight, List filterTags,
List filterAccessType, String filterSelected,
- String filterType, String sort, int page, int count,
+ String filterType, String filterVisibility, String filterAccess,
+ String sort, int page, int count,
Map okapiHeaders,
Handler> asyncResultHandler,
Context vertxContext) {
- var filter = Filter.getSortableFilter(getPackageFilter(providerId, q, filterTags, filterAccessType, filterSelected,
- filterType), sort, page, count);
+ var parsedProviderId = parseProviderId(providerId);
+ var filter = PackageRecordFilter.builder()
+ .query(q)
+ .queryField(queryField)
+ .queryType(queryType)
+ .filterTags(filterTags)
+ .providerId(providerId)
+ .filterAccessType(filterAccessType)
+ .filterSelected(filterSelected)
+ .filterType(filterType)
+ .filterVisibility(filterVisibility)
+ .filterFreeAccess(filterAccess)
+ .sort(sort)
+ .page(page)
+ .count(count)
+ .build();
RmApiTemplate template = templateFactory.createTemplate(okapiHeaders, asyncResultHandler);
if (filter.isTagsFilter()) {
template.requestAction(
- context -> filteredEntitiesLoader.fetchPackagesByTagFilter(filter.createTagFilter(), context));
+ context -> filteredEntitiesLoader.fetchPackagesByTagFilter(TagFilter.from(filter), context));
} else if (filter.isAccessTypeFilter()) {
template.requestAction(context -> filteredEntitiesLoader
- .fetchPackagesByAccessTypeFilter(filter.createAccessTypeFilter(), context)
+ .fetchPackagesByAccessTypeFilter(AccessTypeFilter.from(filter), context)
.thenApply(packages -> new PackageCollectionResult(packages, emptyList())));
} else {
- template.requestAction(retrieveFilteredPackages(filter));
+ template.requestAction(retrieveFilteredPackages(parsedProviderId, filter));
}
template
.addErrorMapper(ResourceNotFoundException.class, exception ->
@@ -219,24 +240,13 @@ public void getEholdingsProvidersPackagesByProviderId(String providerId, String
.executeWithResult(PackageCollection.class);
}
- private Filter.FilterBuilder getPackageFilter(String providerId, String q, List filterTags,
- List filterAccessType,
- String filterSelected, String filterType) {
- return Filter.builder()
- .recordType(RecordType.PACKAGE)
- .query(q)
- .filterTags(filterTags)
- .providerId(providerId)
- .filterAccessType(filterAccessType)
- .filterSelected(filterSelected)
- .filterType(filterType);
- }
-
- private Function> retrieveFilteredPackages(Filter filter) {
+ private Function> retrieveFilteredPackages(int providerId,
+ PackageRecordFilter filter) {
+ var packageFilter = filter.toClientFilter(searchProperties);
+ var pageable = filter.toPageable();
return context ->
context.getPackagesService()
- .retrievePackages(filter.getFilterSelected(), filter.getFilterType(), packagesSearchType,
- filter.getProviderId(), filter.getQuery(), filter.getPage(), filter.getCount(), filter.getSort())
+ .retrievePackages(providerId, packageFilter, pageable)
.thenCompose(packages -> loadTags(packages, context));
}
@@ -250,7 +260,7 @@ private CompletableFuture loadTags(VendorResult result, RmApiTempl
private CompletableFuture loadTags(Packages packages, RmApiTemplateContext context) {
UUID credentialsId = toUUID(context.getCredentialsId());
- String tenant = context.getOkapiData().getTenant();
+ String tenant = context.getRequestContext().getTenant();
return packageRepository.findByIds(getPackageIds(packages), credentialsId, tenant)
.thenApply(dbPackages -> new PackageCollectionResult(packages, dbPackages));
}
@@ -290,13 +300,13 @@ private CompletableFuture updateStoredProvider(DbProvider provider, Tags t
return providerRepository.delete(provider.getId(), provider.getCredentialsId(), tenant);
}
- private CompletableFuture processUpdateRequest(ProviderPutRequest request, long providerIdLong,
+ private CompletableFuture processUpdateRequest(ProviderPutRequest request, int providerId,
RmApiTemplateContext context) {
if (!providerCanBeUpdated(request)) {
//Return current state of provider without updating it
- return context.getProvidersService().retrieveProvider(providerIdLong);
+ return context.getProvidersService().retrieveProvider(providerId);
}
- return context.getProvidersService().updateProvider(providerIdLong, putRequestConverter.convert(request));
+ return context.getProvidersService().updateProvider(providerId, putRequestConverter.convert(request));
}
private boolean providerCanBeUpdated(ProviderPutRequest request) {
diff --git a/src/main/java/org/folio/rest/impl/EholdingsResourcesImpl.java b/src/main/java/org/folio/rest/impl/EholdingsResourcesImpl.java
index bd9be22f8..16fa2cd8c 100644
--- a/src/main/java/org/folio/rest/impl/EholdingsResourcesImpl.java
+++ b/src/main/java/org/folio/rest/impl/EholdingsResourcesImpl.java
@@ -1,8 +1,7 @@
package org.folio.rest.impl;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
-import static org.apache.http.HttpStatus.SC_NOT_FOUND;
-import static org.apache.http.protocol.HTTP.CONTENT_TYPE;
+import static org.folio.HttpStatus.SC_NOT_FOUND;
import static org.folio.common.ListUtils.parseByComma;
import static org.folio.rest.util.ErrorUtil.createError;
import static org.folio.rest.util.ExceptionMappers.error400NotFoundMapper;
@@ -32,12 +31,13 @@
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.BooleanUtils;
+import org.folio.HttpHeaders;
import org.folio.db.RowSetUtils;
import org.folio.holdingsiq.model.CustomerResources;
import org.folio.holdingsiq.model.FilterQuery;
-import org.folio.holdingsiq.model.OkapiData;
-import org.folio.holdingsiq.model.PackageByIdData;
+import org.folio.holdingsiq.model.PackageData;
import org.folio.holdingsiq.model.PackageId;
+import org.folio.holdingsiq.model.RequestContext;
import org.folio.holdingsiq.model.ResourceId;
import org.folio.holdingsiq.model.ResourcePut;
import org.folio.holdingsiq.model.ResourceSelectedPayload;
@@ -201,15 +201,15 @@ public void deleteEholdingsResourcesByResourceId(String resourceId, Map okapiHeaders,
+ Map headers,
Handler> asyncResultHandler,
Context vertxContext) {
- userKbCredentialsService.findByUser(okapiHeaders)
+ userKbCredentialsService.findByUser(headers)
.thenCompose(creds -> {
ResourceTagsDataAttributes attributes = entity.getData().getAttributes();
resourceTagsPutBodyValidator.validate(entity, attributes);
return updateResourceTags(createDbResource(resourceId, creds.getId(), attributes),
- new OkapiData(okapiHeaders).getTenant())
+ new RequestContext(headers).getTenant())
.thenAccept(ob -> asyncResultHandler.handle(
Future.succeededFuture(PutEholdingsResourcesTagsByResourceIdResponse.respond200WithApplicationVndApiJson(
convertToResourceTags(attributes)))));
@@ -235,7 +235,7 @@ public void postEholdingsResourcesBulkFetch(String contentType, ResourcePostBulk
private Function> processResourcePost(
ResourcePostDataAttributes attributes) {
- long titleId = parseTitleId(attributes.getTitleId());
+ var titleId = parseTitleId(attributes.getTitleId());
PackageId packageId = parsePackageId(attributes.getPackageId());
return context -> (CompletableFuture>) getObjectsForPostResource(titleId, packageId, context.getTitlesService(),
context.getPackagesService())
@@ -244,11 +244,7 @@ private Function> processResourcePost
postValidator.validateRelatedObjects(result.packageData(), title, result.titles());
ResourceSelectedPayload postRequest =
new ResourceSelectedPayload(true, title.getTitleName(), title.getPubType(), attributes.getUrl());
- ResourceId resourceId = ResourceId.builder()
- .providerIdPart(packageId.getProviderIdPart())
- .packageIdPart(packageId.getPackageIdPart())
- .titleIdPart(titleId)
- .build();
+ ResourceId resourceId = new ResourceId(packageId.providerIdPart(), packageId.packageIdPart(), titleId);
return context.getResourcesService().postResource(postRequest, resourceId);
})
.thenCompose(title -> CompletableFuture.completedFuture(
@@ -262,7 +258,7 @@ private CompletableFuture fetchAccessType(ResourcePutRequest entity,
return CompletableFuture.completedFuture(null);
} else {
return accessTypesService.findByCredentialsAndAccessTypeId(context.getCredentialsId(), accessTypeId, false,
- context.getOkapiData().getHeaders());
+ context.getRequestContext().getHeaders());
}
}
@@ -280,7 +276,7 @@ private CompletableFuture updateAccessType(String recordId, Titl
private CompletableFuture updateRecordMapping(AccessType accessType, String recordId,
RmApiTemplateContext context) {
return accessTypeMappingsService.update(accessType, recordId, RecordType.RESOURCE, context.getCredentialsId(),
- context.getOkapiData().getHeaders());
+ context.getRequestContext().getHeaders());
}
private CompletableFuture loadRelatedEntities(ResourceResult result, RmApiTemplateContext context) {
@@ -308,7 +304,7 @@ private CompletableFuture deleteAssignedResources(String resourceId, RmApi
}
private CompletableFuture deleteTags(String resourceId, RmApiTemplateContext context) {
- String tenant = context.getOkapiData().getTenant();
+ String tenant = context.getRequestContext().getTenant();
UUID credentialsId = RowSetUtils.toUUID(context.getCredentialsId());
return resourceRepository.delete(resourceId, credentialsId, tenant)
@@ -317,17 +313,17 @@ private CompletableFuture deleteTags(String resourceId, RmApiTemplateConte
}
private CompletionStage getObjectsForPostResource(
- Long titleId, PackageId packageId,
+ int titleId, PackageId packageId,
TitlesHoldingsIQService titlesService,
PackagesHoldingsIQService packagesService) {
CompletableFuture titleFuture = titlesService.retrieveTitle(titleId);
- CompletableFuture packageFuture = packagesService.retrievePackage(packageId);
+ CompletableFuture packageFuture = packagesService.retrievePackage(packageId.packageIdPart());
return CompletableFuture.allOf(titleFuture, packageFuture)
.thenCompose(o -> {
FilterQuery filterByName = FilterQuery.builder()
.name(titleFuture.join().getTitleName())
.build();
- return titlesService.retrieveTitles(packageId.getProviderIdPart(), packageId.getPackageIdPart(),
+ return titlesService.retrieveTitles(packageId.providerIdPart(), packageId.packageIdPart(),
filterByName, searchProperties.titlesSearchType(), Sort.RELEVANCE, 1, MAX_TITLE_COUNT);
})
.thenCompose(titles -> CompletableFuture.completedFuture(
@@ -372,7 +368,7 @@ private CompletableFuture processResourceUpdate(ResourcePutRequest entity
private Function error404ResourceNotFoundMapper() {
return exception ->
Response.status(SC_NOT_FOUND)
- .header(CONTENT_TYPE, JSON_API_TYPE)
+ .header(HttpHeaders.CONTENT_TYPE, JSON_API_TYPE)
.entity(createError(RESOURCE_NOT_FOUND_MESSAGE))
.build();
}
diff --git a/src/main/java/org/folio/rest/impl/EholdingsStatusImpl.java b/src/main/java/org/folio/rest/impl/EholdingsStatusImpl.java
index daa74a10f..6dbfd51d5 100644
--- a/src/main/java/org/folio/rest/impl/EholdingsStatusImpl.java
+++ b/src/main/java/org/folio/rest/impl/EholdingsStatusImpl.java
@@ -20,7 +20,7 @@
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.mutable.MutableObject;
import org.folio.holdingsiq.model.ConfigurationError;
-import org.folio.holdingsiq.model.OkapiData;
+import org.folio.holdingsiq.model.RequestContext;
import org.folio.holdingsiq.service.ConfigurationService;
import org.folio.rest.aspect.HandleValidationErrors;
import org.folio.rest.converter.configuration.StatusConverter;
@@ -46,17 +46,17 @@ public EholdingsStatusImpl() {
@Override
@HandleValidationErrors
- public void getEholdingsStatus(Map okapiHeaders, Handler> asyncResultHandler,
+ public void getEholdingsStatus(Map headers, Handler> asyncResultHandler,
Context vertxContext) {
- headerValidator.validate(okapiHeaders);
- MutableObject okapiData = new MutableObject<>();
+ headerValidator.validate(headers);
+ MutableObject requestContext = new MutableObject<>();
CompletableFuture.completedFuture(null)
.thenCompose(o -> {
- okapiData.setValue(new OkapiData(okapiHeaders));
- return configurationService.retrieveConfiguration(okapiData.get());
+ requestContext.setValue(new RequestContext(headers));
+ return configurationService.retrieveConfiguration(requestContext.get());
})
.thenCompose(configuration -> configurationService.verifyCredentials(configuration, vertxContext,
- okapiData.get())
+ requestContext.get())
)
.thenAccept(verificationErrorsToResponse(asyncResultHandler))
.exceptionally(handleStatusException(asyncResultHandler));
diff --git a/src/main/java/org/folio/rest/impl/EholdingsTitlesImpl.java b/src/main/java/org/folio/rest/impl/EholdingsTitlesImpl.java
index 53ce60458..e7e7a303f 100644
--- a/src/main/java/org/folio/rest/impl/EholdingsTitlesImpl.java
+++ b/src/main/java/org/folio/rest/impl/EholdingsTitlesImpl.java
@@ -39,7 +39,9 @@
import org.folio.rest.jaxrs.model.TitlePostRequest;
import org.folio.rest.jaxrs.model.TitlePutRequest;
import org.folio.rest.jaxrs.resource.EholdingsTitles;
-import org.folio.rest.model.filter.Filter;
+import org.folio.rest.model.filter.AccessTypeFilter;
+import org.folio.rest.model.filter.TagFilter;
+import org.folio.rest.model.filter.TitleFilter;
import org.folio.rest.util.ErrorUtil;
import org.folio.rest.util.IdParser;
import org.folio.rest.util.template.RmApiTemplateContext;
@@ -94,8 +96,7 @@ public void getEholdingsTitles(List filterTags, List filterAcces
List filterPackageIds, String include, String sort, int page, int count,
Map okapiHeaders, Handler> asyncResultHandler,
Context vertxContext) {
- Filter filter = Filter.builder()
- .recordType(RecordType.TITLE)
+ var filter = TitleFilter.builder()
.filterTags(filterTags)
.filterAccessType(filterAccessType)
.filterSelected(filterSelected)
@@ -139,7 +140,7 @@ public void postEholdingsTitles(String contentType, TitlePostRequest entity, Map
@HandleValidationErrors
public void getEholdingsTitlesByTitleId(String titleId, String include, Map okapiHeaders,
Handler> asyncResultHandler, Context vertxContext) {
- long titleIdLong = parseTitleId(titleId);
+ var titleIdLong = parseTitleId(titleId);
templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
.requestAction(context ->
@@ -161,7 +162,7 @@ public void putEholdingsTitlesByTitleId(String titleId, String contentType, Titl
Handler> asyncResultHandler, Context vertxContext) {
titleCommonRequestAttributesValidator.validate(entity.getData().getAttributes());
- Long parsedTitleId = parseTitleId(titleId);
+ var parsedTitleId = parseTitleId(titleId);
templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
.requestAction(context ->
context.getTitlesService().retrieveTitle(parsedTitleId)
@@ -182,15 +183,15 @@ public void putEholdingsTitlesByTitleId(String titleId, String contentType, Titl
.executeWithResult(Title.class);
}
- private CompletableFuture fetchTitlesByFilter(Filter filter, RmApiTemplateContext context) {
+ private CompletableFuture fetchTitlesByFilter(TitleFilter filter, RmApiTemplateContext context) {
if (filter.isTagsFilter()) {
- return filteredEntitiesLoader.fetchTitlesByTagFilter(filter.createTagFilter(), context);
+ return filteredEntitiesLoader.fetchTitlesByTagFilter(TagFilter.from(filter), context);
} else if (filter.isAccessTypeFilter()) {
- return filteredEntitiesLoader.fetchTitlesByAccessTypeFilter(filter.createAccessTypeFilter(), context);
+ return filteredEntitiesLoader.fetchTitlesByAccessTypeFilter(AccessTypeFilter.from(filter), context);
} else {
return context.getTitlesService()
.retrieveTitles(filter.createFilterQuery(), searchProperties.titlesSearchType(),
- filter.getSort(), filter.getPage(), filter.getCount()
+ filter.resolveSort(), filter.getPage(), filter.getCount()
);
}
}
@@ -223,7 +224,7 @@ private CompletableFuture loadTags(TitleResult result,
.stream()
.map(IdParser::getResourceId)
.toList();
- return tagRepository.findByRecordByIds(context.getOkapiData().getTenant(), resourceIds, RecordType.RESOURCE)
+ return tagRepository.findByRecordByIds(context.getRequestContext().getTenant(), resourceIds, RecordType.RESOURCE)
.thenApply(tags -> {
result.setResourceTagList(tags);
return result;
@@ -240,7 +241,7 @@ private CompletableFuture updateTags(TitleResult result, RmApiTempl
if (Objects.isNull(tags)) {
return completedFuture(result);
} else {
- String tenant = context.getOkapiData().getTenant();
+ String tenant = context.getRequestContext().getTenant();
UUID credentialsId = toUUID(context.getCredentialsId());
return updateStoredTitles(createDbTitle(result, credentialsId), tags, tenant)
diff --git a/src/main/java/org/folio/rest/impl/LoadHoldingsImpl.java b/src/main/java/org/folio/rest/impl/LoadHoldingsImpl.java
index 5e756b14e..d73885bd8 100644
--- a/src/main/java/org/folio/rest/impl/LoadHoldingsImpl.java
+++ b/src/main/java/org/folio/rest/impl/LoadHoldingsImpl.java
@@ -15,7 +15,7 @@
import javax.ws.rs.core.Response;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.folio.holdingsiq.model.OkapiData;
+import org.folio.holdingsiq.model.RequestContext;
import org.folio.repository.holdings.status.HoldingsStatusRepository;
import org.folio.rest.annotations.Validate;
import org.folio.rest.aspect.HandleValidationErrors;
@@ -62,14 +62,14 @@ public LoadHoldingsImpl() {
@Override
@Validate
@HandleValidationErrors
- public void postEholdingsLoadingKbCredentials(String contentType, Map okapiHeaders,
+ public void postEholdingsLoadingKbCredentials(String contentType, Map headers,
Handler> asyncResultHandler,
Context vertxContext) {
LOG.info("Received signal to start scheduled loading of holdings");
- validateAllCredentialsStatus(okapiHeaders)
- .thenCompose(o -> credentialsService.findAll(okapiHeaders))
+ validateAllCredentialsStatus(headers)
+ .thenCompose(o -> credentialsService.findAll(headers))
.thenApply(KbCredentialsCollection::getData)
- .thenApply(credentialsList -> iterateOverCredentials(credentialsList, okapiHeaders))
+ .thenApply(credentialsList -> iterateOverCredentials(credentialsList, headers))
.thenAccept(anyObj -> asyncResultHandler.handle(
succeededFuture(PostEholdingsLoadingKbCredentialsResponse.respond204())))
.exceptionally(loadHoldingsErrorHandler.handle(asyncResultHandler));
@@ -78,11 +78,11 @@ public void postEholdingsLoadingKbCredentials(String contentType, Map okapiHeaders,
+ public void postEholdingsLoadingKbCredentialsById(String id, String contentType, Map headers,
Handler> asyncResultHandler,
Context vertxContext) {
LOG.info("Start loading of holdings for credentials: {}", id);
- templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
+ templateFactory.createTemplate(headers, asyncResultHandler)
.withErrorHandler(loadHoldingsErrorHandler)
.requestAction(this::validateAndStartLoading)
.execute();
@@ -92,54 +92,54 @@ public void postEholdingsLoadingKbCredentialsById(String id, String contentType,
@Validate
@HandleValidationErrors
public void getEholdingsLoadingKbCredentialsStatusById(String id, String contentType,
- Map okapiHeaders,
+ Map headers,
Handler> asyncResultHandler,
Context vertxContext) {
LOG.info("Getting holdings loading status");
- templateFactory.createTemplate(okapiHeaders, asyncResultHandler)
+ templateFactory.createTemplate(headers, asyncResultHandler)
.withErrorHandler(loadHoldingsErrorHandler)
.requestAction(context ->
holdingsStatusRepository.findByCredentialsId(toUUID(context.getCredentialsId()),
- context.getOkapiData().getTenant()))
+ context.getRequestContext().getTenant()))
.executeWithResult(HoldingsLoadingStatus.class);
}
private CompletableFuture validateAndStartLoading(RmApiTemplateContext context) {
- return holdingsService.canStartLoading(context.getCredentialsId(), context.getOkapiData().getTenant())
+ return holdingsService.canStartLoading(context.getCredentialsId(), context.getRequestContext().getTenant())
.thenCompose(canStartLoading -> Boolean.TRUE.equals(canStartLoading)
? startLoading(context)
: failedFuture(new ProcessInProgressException(LOADING_IN_PROGRESS_MESSAGE)));
}
- private CompletableFuture validateAllCredentialsStatus(Map okapiHeaders) {
- return holdingsService.canStartLoading(tenantId(okapiHeaders))
+ private CompletableFuture validateAllCredentialsStatus(Map headers) {
+ return holdingsService.canStartLoading(tenantId(headers))
.thenCompose(allCanStartLoading -> Boolean.TRUE.equals(allCanStartLoading)
? CompletableFuture.completedFuture(null)
: failedFuture(new ProcessInProgressException(LOADING_IN_PROGRESS_MESSAGE)));
}
private CompletableFuture iterateOverCredentials(List credentialsList,
- Map okapiHeaders) {
+ Map headers) {
return CompletableFuture.allOf(
credentialsList.stream()
- .map(credentials -> buildContextAndRun(credentials, okapiHeaders))
+ .map(credentials -> buildContextAndRun(credentials, headers))
.toArray(CompletableFuture[]::new)
);
}
- private CompletableFuture buildContextAndRun(KbCredentials credentials, Map okapiHeaders) {
- return startLoading(buildLoadingContext(credentials, okapiHeaders));
+ private CompletableFuture buildContextAndRun(KbCredentials credentials, Map headers) {
+ return startLoading(buildLoadingContext(credentials, headers));
}
private CompletableFuture startLoading(RmApiTemplateContext context) {
return holdingsStatusAuditService.clearExpiredRecords(context.getCredentialsId(),
- context.getOkapiData().getTenant())
+ context.getRequestContext().getTenant())
.thenCompose(o -> holdingsService.loadSingleHoldings(context));
}
- private RmApiTemplateContext buildLoadingContext(KbCredentials credentials, Map okapiHeaders) {
+ private RmApiTemplateContext buildLoadingContext(KbCredentials credentials, Map headers) {
return templateFactory.lookupContextBuilder()
- .okapiData(new OkapiData(okapiHeaders))
+ .requestContext(new RequestContext(headers))
.kbCredentials(credentials)
.build();
}
diff --git a/src/main/java/org/folio/rest/model/filter/AccessTypeFilter.java b/src/main/java/org/folio/rest/model/filter/AccessTypeFilter.java
index 84db86e75..ffc58be05 100644
--- a/src/main/java/org/folio/rest/model/filter/AccessTypeFilter.java
+++ b/src/main/java/org/folio/rest/model/filter/AccessTypeFilter.java
@@ -14,4 +14,25 @@ public class AccessTypeFilter {
private RecordType recordType;
private int page;
private int count;
+
+ public static AccessTypeFilter from(Filter filter) {
+ AccessTypeFilter accessTypeFilter = new AccessTypeFilter();
+ accessTypeFilter.setAccessTypeNames(filter.getFilterAccessType());
+ accessTypeFilter.setCount(filter.getCount());
+ accessTypeFilter.setPage(filter.getPage());
+
+ switch (filter) {
+ case PackageRecordFilter pf -> {
+ accessTypeFilter.setRecordIdPrefix(pf.getProviderId());
+ accessTypeFilter.setRecordType(RecordType.PACKAGE);
+ }
+ case ResourceFilter rf -> {
+ accessTypeFilter.setRecordIdPrefix(rf.getPackageId());
+ accessTypeFilter.setRecordType(RecordType.RESOURCE);
+ }
+ case TitleFilter tf -> accessTypeFilter.setRecordType(RecordType.RESOURCE);
+ case ProviderFilter prov -> { }
+ }
+ return accessTypeFilter;
+ }
}
diff --git a/src/main/java/org/folio/rest/model/filter/Filter.java b/src/main/java/org/folio/rest/model/filter/Filter.java
index 42323773b..9a4770265 100644
--- a/src/main/java/org/folio/rest/model/filter/Filter.java
+++ b/src/main/java/org/folio/rest/model/filter/Filter.java
@@ -1,291 +1,60 @@
package org.folio.rest.model.filter;
-import static java.util.Arrays.asList;
-import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
-import static org.apache.commons.collections4.IterableUtils.countMatches;
import static org.apache.commons.collections4.IterableUtils.matchesAll;
-import static org.apache.commons.collections4.IterableUtils.matchesAny;
-import static org.apache.commons.lang3.StringUtils.isBlank;
-import static org.apache.commons.lang3.StringUtils.isNumeric;
import static org.folio.rest.util.RestConstants.FILTER_SELECTED_MAPPING;
-import static org.folio.rest.util.RestConstants.SUPPORTED_PACKAGE_FILTER_TYPE_VALUES;
-import static org.folio.rest.util.RestConstants.SUPPORTED_TITLE_FILTER_TYPE_VALUES;
-import jakarta.validation.ValidationException;
import java.util.List;
-import java.util.Objects;
-import lombok.Builder;
-import lombok.Value;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Strings;
-import org.folio.holdingsiq.model.FilterQuery;
-import org.folio.holdingsiq.model.PackageId;
+import org.folio.holdingsiq.model.Pageable;
import org.folio.holdingsiq.model.Sort;
import org.folio.repository.RecordType;
-import org.folio.rest.util.IdParser;
-@Value
-@Builder
-public class Filter {
+public sealed interface Filter permits ProviderFilter, PackageRecordFilter, ResourceFilter, TitleFilter {
- String query;
- String filterIsxn;
- String filterName;
- String filterPublisher;
- String filterSubject;
- String filterType;
- String filterCustom;
- String filterSelected;
- List filterPackageIds;
+ String getSort();
- String packageId;
- String providerId;
+ int getPage();
- List filterTags;
- List filterAccessType;
+ int getCount();
- String sort;
- int page;
- int count;
+ List getFilterTags();
- RecordType recordType;
+ List getFilterAccessType();
- public static FilterBuilder builder() {
- return new FilterBuilder() {
+ RecordType getRecordType();
- @Override
- public Filter build() {
- validate();
- return super.build();
- }
- };
- }
-
- public static Filter getSortableFilter(Filter.FilterBuilder filterBuilder, String sort, int page, int count) {
- return filterBuilder
- .sort(sort)
- .page(page)
- .count(count)
- .build();
- }
-
- public boolean isTagsFilter() {
- return isCheckedFilter(filterTags, filterAccessType);
- }
+ // Each impl returns its own search-criteria field values (used by isTagsFilter/isAccessTypeFilter)
+ List searchCriteriaValues();
- public boolean isAccessTypeFilter() {
- return isCheckedFilter(filterAccessType, filterTags);
+ // Default methods
+ default Sort resolveSort() {
+ return Sort.valueOf(getSort().toUpperCase());
}
- public AccessTypeFilter createAccessTypeFilter() {
- AccessTypeFilter accessTypeFilter = new AccessTypeFilter();
- accessTypeFilter.setAccessTypeNames(filterAccessType);
- accessTypeFilter.setCount(count);
- accessTypeFilter.setPage(page);
-
- if (recordType == RecordType.PACKAGE) {
- accessTypeFilter.setRecordIdPrefix(providerId);
- accessTypeFilter.setRecordType(RecordType.PACKAGE);
- } else if (recordType == RecordType.RESOURCE) {
- accessTypeFilter.setRecordIdPrefix(packageId);
- accessTypeFilter.setRecordType(RecordType.RESOURCE);
- } else if (recordType == RecordType.TITLE) {
- accessTypeFilter.setRecordType(RecordType.RESOURCE);
- }
- return accessTypeFilter;
- }
-
- public TagFilter createTagFilter() {
- TagFilter.TagFilterBuilder builder = TagFilter.builder()
- .tags(filterTags)
- .recordType(recordType)
- .offset((page - 1) * count)
- .count(count);
-
- switch (recordType) {
- case PACKAGE -> builder.recordIdPrefix(createRecordIdPrefix(providerId));
- case RESOURCE -> builder.recordIdPrefix(createRecordIdPrefix(packageId));
- case TITLE -> {
- builder.recordType(RecordType.RESOURCE);
- builder.recordIdPrefix("");
- }
- case null, default -> builder.recordIdPrefix("");
- }
-
- return builder.build();
+ default boolean isTagsFilter() {
+ return isFilteredBy(getFilterTags(), getFilterAccessType());
}
- public FilterQuery createFilterQuery() {
- return FilterQuery.builder()
- .type(filterType)
- .name(filterName)
- .isxn(filterIsxn)
- .subject(filterSubject)
- .publisher(filterPublisher)
- .selected(getFilterSelected())
- .packageIds(getFilterPackageIds())
- .build();
+ default boolean isAccessTypeFilter() {
+ return isFilteredBy(getFilterAccessType(), getFilterTags());
}
- public Long getProviderId() {
- return IdParser.parseProviderId(providerId);
+ default Pageable toPageable() {
+ return new Pageable(getPage(), getCount(), resolveSort());
}
- public PackageId getPackageId() {
- return IdParser.parsePackageId(packageId);
+ // Private helper
+ private boolean isFilteredBy(List primary, List other) {
+ return isNotEmpty(primary)
+ && matchesAll(primary, StringUtils::isNotBlank)
+ && CollectionUtils.isEmpty(other)
+ && matchesAll(searchCriteriaValues(), StringUtils::isBlank);
}
- public String getFilterSelected() {
- return filterSelected == null ? null : FILTER_SELECTED_MAPPING.get(filterSelected);
- }
-
- public List getFilterPackageIds() {
- return filterPackageIds == null ? null : filterPackageIds.stream()
- .map(Integer::parseInt)
- .toList();
- }
-
- public Boolean getFilterCustom() {
- return filterCustom == null ? null : Boolean.parseBoolean(filterCustom);
- }
-
- public Sort getSort() {
- return Sort.valueOf(sort.toUpperCase());
- }
-
- private String createRecordIdPrefix(String providerId) {
- return isBlank(providerId) ? "" : Strings.CS.appendIfMissing(providerId, "-");
- }
-
- private boolean isCheckedFilter(List checkedFilter, List otherFilter) {
- return isNotEmpty(checkedFilter)
- && matchesAll(checkedFilter, StringUtils::isNotBlank)
- && CollectionUtils.isEmpty(otherFilter)
- && matchesAll(
- asList(query, filterIsxn, filterName, filterPublisher, filterSubject, filterCustom, filterSelected),
- StringUtils::isBlank);
- }
-
- public static class FilterBuilder {
-
- private static final String INVALID_QUERY_PARAMETER_MESSAGE = "Search parameter cannot be empty";
- private static final String INVALID_SORT_PARAMETER_MESSAGE = "Invalid Query Parameter for sort";
- private static final String INVALID_FILTER_CUSTOM_PARAMETER_MESSAGE =
- "Invalid Query Parameter for filter[custom]: only 'true' is supported";
- private static final String INVALID_FILTER_TYPE_PARAMETER_MESSAGE = "Invalid Query Parameter for filter[type]";
- private static final String INVALID_FILTER_SELECTED_PARAMETER_MESSAGE =
- "Invalid Query Parameter for filter[selected]";
- private static final String INVALID_FILTER_PACKAGE_IDS_PARAMETER_MESSAGE =
- "Invalid Query Parameter for filter[packageIds]";
- private static final String CONFLICTING_KEYWORD_SEARCH_PARAMETERS_MESSAGE = "Conflicting filter parameters";
- private static final String MISSING_KEYWORD_SEARCH_PARAMETERS_MESSAGE =
- "All of filter[name], filter[isxn], filter[subject] and filter[publisher] cannot be missing.";
- private static final String INVALID_KEYWORD_SEARCH_PARAMETERS_MESSAGE =
- "Value of required parameter filter[name], filter[isxn], filter[subject] or filter[publisher] is missing.";
-
- void validate() {
- if (isEmpty(this.filterTags) && isEmpty(this.filterAccessType)) {
- validateSort();
-
- if (this.recordType == RecordType.PROVIDER) {
- validateQuery();
- } else if (this.recordType == RecordType.PACKAGE) {
- validateQuery();
- validatePackageFilterType();
- validateFilterCustom();
- validateFilterSelected();
- validateProviderId();
- } else if (this.recordType == RecordType.RESOURCE) {
- validatePackageId();
- validateTitleFilterType();
- validateFilterSelected();
- validateKeywordSearch(true);
- } else if (this.recordType == RecordType.TITLE) {
- validateTitleFilterType();
- validateFilterSelected();
- validateFilterPackageIds();
- validateKeywordSearch(false);
- }
- }
- }
-
- private void validateKeywordSearch(boolean allowNullFilters) {
- List searchParameters =
- asList(this.filterName, this.filterIsxn, this.filterSubject, this.filterPublisher);
-
- long nonNullFilters = countMatches(searchParameters, Objects::nonNull);
-
- if (nonNullFilters > 1) {
- throw new ValidationException(CONFLICTING_KEYWORD_SEARCH_PARAMETERS_MESSAGE);
- }
- if (nonNullFilters < 1 && !allowNullFilters) {
- throw new ValidationException(MISSING_KEYWORD_SEARCH_PARAMETERS_MESSAGE);
- }
- if (matchesAny(searchParameters, StringUtils.EMPTY::equals)) {
- throw new ValidationException(INVALID_KEYWORD_SEARCH_PARAMETERS_MESSAGE);
- }
- }
-
- private void validateFilterSelected() {
- if (this.filterSelected != null && !FILTER_SELECTED_MAPPING.containsKey(this.filterSelected)) {
- throw new ValidationException(INVALID_FILTER_SELECTED_PARAMETER_MESSAGE);
- }
- }
-
- private void validateFilterPackageIds() {
- if (this.filterPackageIds != null && !this.filterPackageIds.isEmpty()) {
- this.filterPackageIds.forEach(filterPackageId -> {
- if (!isNumeric(filterPackageId)) {
- throw new ValidationException(INVALID_FILTER_PACKAGE_IDS_PARAMETER_MESSAGE);
- }
- });
- }
- }
-
- private void validatePackageFilterType() {
- if (this.filterType != null
- && !SUPPORTED_PACKAGE_FILTER_TYPE_VALUES.contains(this.filterType)) {
- throw new ValidationException(INVALID_FILTER_TYPE_PARAMETER_MESSAGE);
- }
- }
-
- private void validateTitleFilterType() {
- if (this.filterType != null
- && !SUPPORTED_TITLE_FILTER_TYPE_VALUES.contains(this.filterType)) {
- throw new ValidationException(INVALID_FILTER_TYPE_PARAMETER_MESSAGE);
- }
- }
-
- private void validateFilterCustom() {
- if (this.filterCustom != null && !Boolean.parseBoolean(this.filterCustom)) {
- throw new ValidationException(INVALID_FILTER_CUSTOM_PARAMETER_MESSAGE);
- }
- }
-
- private void validateSort() {
- if (!Sort.contains(this.sort.toUpperCase())) {
- throw new ValidationException(INVALID_SORT_PARAMETER_MESSAGE);
- }
- }
-
- private void validateQuery() {
- if ("".equals(this.query)) {
- throw new ValidationException(INVALID_QUERY_PARAMETER_MESSAGE);
- }
- }
-
- private void validateProviderId() {
- if (this.providerId != null) {
- IdParser.parseProviderId(this.providerId);
- }
- }
-
- private void validatePackageId() {
- if (this.packageId != null) {
- IdParser.parsePackageId(this.packageId);
- }
- }
+ // Shared utility for resolveFilterSelected (used by 3 implementations)
+ static String mapFilterSelected(String filterSelected) {
+ return filterSelected == null ? null : FILTER_SELECTED_MAPPING.get(filterSelected).getValue();
}
}
diff --git a/src/main/java/org/folio/rest/model/filter/FilterValidator.java b/src/main/java/org/folio/rest/model/filter/FilterValidator.java
new file mode 100644
index 000000000..45dd20de4
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/FilterValidator.java
@@ -0,0 +1,32 @@
+package org.folio.rest.model.filter;
+
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.jspecify.annotations.NonNull;
+
+public sealed interface FilterValidator permits PackageFilterValidator {
+
+ void validate(@NonNull F filter);
+
+ default Optional validate(@NonNull F filter,
+ @NonNull Function paramExtractor,
+ @NonNull Predicate validator,
+ @NonNull String errorMessage) {
+ var param = paramExtractor.apply(filter);
+ if (validator.test(param)) {
+ return Optional.empty();
+ } else {
+ return Optional.of(errorMessage);
+ }
+ }
+
+ default Optional validate(@NonNull F filter, @NonNull FilterValidatorLogic logic) {
+ var param = logic.extractor().apply(filter);
+ if (logic.validator().test(param)) {
+ return Optional.empty();
+ } else {
+ return Optional.of(logic.errorMessage());
+ }
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/filter/FilterValidatorLogic.java b/src/main/java/org/folio/rest/model/filter/FilterValidatorLogic.java
new file mode 100644
index 000000000..40b1d7f14
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/FilterValidatorLogic.java
@@ -0,0 +1,18 @@
+package org.folio.rest.model.filter;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.jspecify.annotations.NonNull;
+
+public record FilterValidatorLogic(
+ Function extractor,
+ Predicate validator,
+ String errorMessage
+) {
+
+ public static FilterValidatorLogic of(@NonNull Function extractor,
+ @NonNull Predicate validator,
+ @NonNull String errorMessage) {
+ return new FilterValidatorLogic<>(extractor, validator, errorMessage);
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/filter/FilterValidators.java b/src/main/java/org/folio/rest/model/filter/FilterValidators.java
new file mode 100644
index 000000000..86f920126
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/FilterValidators.java
@@ -0,0 +1,175 @@
+package org.folio.rest.model.filter;
+
+import static java.util.Arrays.asList;
+import static org.apache.commons.collections4.IterableUtils.countMatches;
+import static org.apache.commons.collections4.IterableUtils.matchesAny;
+import static org.apache.commons.lang3.StringUtils.isNumeric;
+import static org.folio.rest.util.RestConstants.FILTER_SELECTED_MAPPING;
+import static org.folio.rest.util.RestConstants.SUPPORTED_PACKAGE_FILTER_TYPE_VALUES;
+import static org.folio.rest.util.RestConstants.SUPPORTED_TITLE_FILTER_TYPE_VALUES;
+
+import jakarta.validation.ValidationException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.folio.holdingsiq.model.PackageSearchField;
+import org.folio.holdingsiq.model.SearchType;
+import org.folio.holdingsiq.model.Sort;
+import org.folio.rest.util.IdParser;
+
+public final class FilterValidators {
+
+ static final String INVALID_QUERY_PARAMETER_MESSAGE = "Search parameter cannot be empty";
+ static final String INVALID_SORT_PARAMETER_MESSAGE = "Invalid Query Parameter for sort";
+ static final String INVALID_QUERY_TYPE_PARAMETER_MESSAGE = "Invalid Query Parameter for qType";
+ static final String INVALID_QUERY_FIELD_PARAMETER_MESSAGE = "Invalid Query Parameter for qField";
+ static final String INVALID_FILTER_CUSTOM_PARAMETER_MESSAGE =
+ "Invalid Query Parameter for filter[custom]: only 'true' is supported";
+ static final String INVALID_FILTER_TYPE_PARAMETER_MESSAGE = "Invalid Query Parameter for filter[type]";
+ static final String INVALID_FILTER_SELECTED_PARAMETER_MESSAGE =
+ "Invalid Query Parameter for filter[selected]";
+ static final String INVALID_FILTER_PACKAGE_IDS_PARAMETER_MESSAGE =
+ "Invalid Query Parameter for filter[packageIds]";
+ static final String CONFLICTING_KEYWORD_SEARCH_PARAMETERS_MESSAGE = "Conflicting filter parameters";
+ static final String MISSING_KEYWORD_SEARCH_PARAMETERS_MESSAGE =
+ "All of filter[name], filter[isxn], filter[subject] and filter[publisher] cannot be missing.";
+ static final String INVALID_KEYWORD_SEARCH_PARAMETERS_MESSAGE =
+ "Value of required parameter filter[name], filter[isxn], filter[subject] or filter[publisher] is missing.";
+
+ private FilterValidators() {
+ }
+
+ public static void validateProvider(ProviderFilter filter) {
+ if (CollectionUtils.isNotEmpty(filter.getFilterTags())
+ || CollectionUtils.isNotEmpty(filter.getFilterAccessType())) {
+ return;
+ }
+ validateSort(filter.getSort());
+ validateQuery(filter.getQuery());
+ }
+
+ public static void validatePackage(PackageRecordFilter filter) {
+ if (CollectionUtils.isNotEmpty(filter.getFilterTags())
+ || CollectionUtils.isNotEmpty(filter.getFilterAccessType())) {
+ return;
+ }
+ validateSort(filter.getSort());
+ validateQuery(filter.getQuery());
+ validateQueryType(filter.getQueryType());
+ validateQueryField(filter.getQueryField());
+ validateFilterType(filter.getFilterType(), SUPPORTED_PACKAGE_FILTER_TYPE_VALUES);
+ validateFilterCustom(filter.getFilterCustom());
+ validateFilterSelected(filter.getFilterSelected());
+ validateProviderId(filter.getProviderId());
+ }
+
+ public static void validateResource(ResourceFilter filter) {
+ if (CollectionUtils.isNotEmpty(filter.getFilterTags())
+ || CollectionUtils.isNotEmpty(filter.getFilterAccessType())) {
+ return;
+ }
+ validateSort(filter.getSort());
+ validatePackageId(filter.getPackageId());
+ validateFilterType(filter.getFilterType(), SUPPORTED_TITLE_FILTER_TYPE_VALUES);
+ validateFilterSelected(filter.getFilterSelected());
+ validateKeywordSearch(filter.getFilterName(), filter.getFilterIsxn(),
+ filter.getFilterSubject(), filter.getFilterPublisher(), true);
+ }
+
+ public static void validateTitle(TitleFilter filter) {
+ if (CollectionUtils.isNotEmpty(filter.getFilterTags())
+ || CollectionUtils.isNotEmpty(filter.getFilterAccessType())) {
+ return;
+ }
+ validateSort(filter.getSort());
+ validateFilterType(filter.getFilterType(), SUPPORTED_TITLE_FILTER_TYPE_VALUES);
+ validateFilterSelected(filter.getFilterSelected());
+ validateFilterPackageIds(filter.getFilterPackageIds());
+ validateKeywordSearch(filter.getFilterName(), filter.getFilterIsxn(),
+ filter.getFilterSubject(), filter.getFilterPublisher(), false);
+ }
+
+ private static void validateSort(String sort) {
+ if (!Sort.contains(sort.toUpperCase())) {
+ throw new ValidationException(INVALID_SORT_PARAMETER_MESSAGE);
+ }
+ }
+
+ private static void validateQuery(String query) {
+ if ("".equals(query)) {
+ throw new ValidationException(INVALID_QUERY_PARAMETER_MESSAGE);
+ }
+ }
+
+ private static void validateQueryType(String queryType) {
+ if (!SearchType.contains(queryType.toUpperCase())) {
+ throw new ValidationException(INVALID_QUERY_TYPE_PARAMETER_MESSAGE);
+ }
+ }
+
+ private static void validateQueryField(String queryField) {
+ if (!PackageSearchField.contains(queryField.toUpperCase())) {
+ throw new ValidationException(INVALID_QUERY_FIELD_PARAMETER_MESSAGE);
+ }
+ }
+
+ private static void validateFilterType(String filterType, Collection supportedValues) {
+ if (filterType != null && !supportedValues.contains(filterType)) {
+ throw new ValidationException(INVALID_FILTER_TYPE_PARAMETER_MESSAGE);
+ }
+ }
+
+ private static void validateFilterCustom(String filterCustom) {
+ if (filterCustom != null && !Boolean.parseBoolean(filterCustom)) {
+ throw new ValidationException(INVALID_FILTER_CUSTOM_PARAMETER_MESSAGE);
+ }
+ }
+
+ private static void validateFilterSelected(String filterSelected) {
+ if (filterSelected != null && !FILTER_SELECTED_MAPPING.containsKey(filterSelected)) {
+ throw new ValidationException(INVALID_FILTER_SELECTED_PARAMETER_MESSAGE);
+ }
+ }
+
+ private static void validateFilterPackageIds(List packageIds) {
+ if (packageIds != null && !packageIds.isEmpty()) {
+ packageIds.forEach(id -> {
+ if (!isNumeric(id)) {
+ throw new ValidationException(INVALID_FILTER_PACKAGE_IDS_PARAMETER_MESSAGE);
+ }
+ });
+ }
+ }
+
+ private static void validateKeywordSearch(String filterName, String filterIsxn,
+ String filterSubject, String filterPublisher,
+ boolean allowNullFilters) {
+ List searchParameters = asList(filterName, filterIsxn, filterSubject, filterPublisher);
+
+ long nonNullFilters = countMatches(searchParameters, Objects::nonNull);
+
+ if (nonNullFilters > 1) {
+ throw new ValidationException(CONFLICTING_KEYWORD_SEARCH_PARAMETERS_MESSAGE);
+ }
+ if (nonNullFilters < 1 && !allowNullFilters) {
+ throw new ValidationException(MISSING_KEYWORD_SEARCH_PARAMETERS_MESSAGE);
+ }
+ if (matchesAny(searchParameters, StringUtils.EMPTY::equals)) {
+ throw new ValidationException(INVALID_KEYWORD_SEARCH_PARAMETERS_MESSAGE);
+ }
+ }
+
+ private static void validateProviderId(String providerId) {
+ if (providerId != null) {
+ IdParser.parseProviderId(providerId);
+ }
+ }
+
+ private static void validatePackageId(String packageId) {
+ if (packageId != null) {
+ IdParser.parsePackageId(packageId);
+ }
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/filter/PackageFilterParams.java b/src/main/java/org/folio/rest/model/filter/PackageFilterParams.java
new file mode 100644
index 000000000..03eb014ac
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/PackageFilterParams.java
@@ -0,0 +1,13 @@
+package org.folio.rest.model.filter;
+
+import java.util.List;
+
+public record PackageFilterParams(
+ String filterCustom,
+ String filterSelected,
+ String filterType,
+ String filterVisibility,
+ String filterFreeAccess,
+ List filterTags,
+ List filterAccessType
+) { }
diff --git a/src/main/java/org/folio/rest/model/filter/PackageFilterValidator.java b/src/main/java/org/folio/rest/model/filter/PackageFilterValidator.java
new file mode 100644
index 000000000..d173b981d
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/PackageFilterValidator.java
@@ -0,0 +1,123 @@
+package org.folio.rest.model.filter;
+
+import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
+import static org.folio.rest.model.filter.FilterValidatorLogic.of;
+import static org.folio.rest.util.RestConstants.FILTER_FREE_ACCESS_MAPPING;
+import static org.folio.rest.util.RestConstants.FILTER_SELECTED_MAPPING;
+import static org.folio.rest.util.RestConstants.FILTER_VISIBILITY_MAPPING;
+import static org.folio.rest.util.RestConstants.SUPPORTED_PACKAGE_FILTER_TYPE_VALUES;
+
+import java.util.List;
+import java.util.Optional;
+import org.folio.holdingsiq.model.PackageSearchField;
+import org.folio.holdingsiq.model.SearchType;
+import org.folio.holdingsiq.model.Sort;
+import org.folio.rest.exception.QueryParamsValidationException;
+import org.jspecify.annotations.NonNull;
+
+public final class PackageFilterValidator implements FilterValidator {
+
+ static final String INVALID_QUERY_PARAMETER_MESSAGE = "Invalid Query Parameter for q: cannot be empty";
+ static final String INVALID_SORT_PARAMETER_MESSAGE = "Invalid Query Parameter for sort";
+ static final String INVALID_QUERY_TYPE_PARAMETER_MESSAGE = "Invalid Query Parameter for qType";
+ static final String INVALID_QUERY_FIELD_PARAMETER_MESSAGE = "Invalid Query Parameter for qField";
+ static final String INVALID_FILTER_CUSTOM_PARAMETER_MESSAGE =
+ "Invalid Query Parameter for filter[custom]: only 'true' is supported";
+ static final String INVALID_FILTER_TYPE_PARAMETER_MESSAGE = "Invalid Query Parameter for filter[type]";
+ static final String INVALID_FILTER_FREE_ACCESS_PARAMETER_MESSAGE = "Invalid Query Parameter for filter[free-access]";
+ static final String INVALID_FILTER_VISIBILITY_PARAMETER_MESSAGE = "Invalid Query Parameter for filter[visibility]";
+ static final String INVALID_FILTER_SELECTED_PARAMETER_MESSAGE =
+ "Invalid Query Parameter for filter[selected]";
+
+ private static final List> VALIDATORS = List.of(
+ of(PackageRecordFilter::getSort, PackageFilterValidator::isSortValid, INVALID_SORT_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getQueryType, PackageFilterValidator::isQueryTypeValid,
+ INVALID_QUERY_TYPE_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getQueryField, PackageFilterValidator::isQueryFieldValid,
+ INVALID_QUERY_FIELD_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getQuery, PackageFilterValidator::isQueryValid, INVALID_QUERY_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getFilterType, PackageFilterValidator::isFilterTypeValid,
+ INVALID_FILTER_TYPE_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getFilterCustom, PackageFilterValidator::isFilterCustomValid,
+ INVALID_FILTER_CUSTOM_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getFilterSelected, PackageFilterValidator::isFilterSelectedValid,
+ INVALID_FILTER_SELECTED_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getFilterVisibility, PackageFilterValidator::isFilterVisibilityValid,
+ INVALID_FILTER_VISIBILITY_PARAMETER_MESSAGE),
+ of(PackageRecordFilter::getFilterFreeAccess, PackageFilterValidator::isFilterFreeAccessValid,
+ INVALID_FILTER_FREE_ACCESS_PARAMETER_MESSAGE)
+ );
+
+ @Override
+ public void validate(@NonNull PackageRecordFilter filter) {
+ if (isNotEmpty(filter.getFilterTags())
+ || isNotEmpty(filter.getFilterAccessType())) {
+ return;
+ }
+ var failMessages = VALIDATORS.stream()
+ .map(logic -> validate(filter, logic))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .toList();
+ if (!failMessages.isEmpty()) {
+ throw new QueryParamsValidationException(failMessages);
+ }
+ }
+
+ private static boolean isFilterFreeAccessValid(String filterFreeAccess) {
+ if (filterFreeAccess == null) {
+ return true;
+ }
+ return FILTER_FREE_ACCESS_MAPPING.containsKey(filterFreeAccess);
+ }
+
+ private static boolean isFilterVisibilityValid(String filterVisibility) {
+ if (filterVisibility == null) {
+ return true;
+ }
+ return FILTER_VISIBILITY_MAPPING.containsKey(filterVisibility);
+ }
+
+ private static boolean isFilterSelectedValid(String filterSelected) {
+ if (filterSelected == null) {
+ return true;
+ }
+ return FILTER_SELECTED_MAPPING.containsKey(filterSelected);
+ }
+
+ private static boolean isFilterCustomValid(String filterCustom) {
+ if (filterCustom == null) {
+ return true;
+ }
+ return Boolean.parseBoolean(filterCustom);
+ }
+
+ private static boolean isFilterTypeValid(String filterType) {
+ if (filterType == null) {
+ return true;
+ }
+ return SUPPORTED_PACKAGE_FILTER_TYPE_VALUES.contains(filterType);
+ }
+
+ private static boolean isQueryValid(String anObject) {
+ return !"".equals(anObject);
+ }
+
+ private static boolean isQueryFieldValid(String queryField) {
+ if (queryField == null) {
+ return true;
+ }
+ return PackageSearchField.contains(queryField);
+ }
+
+ private static boolean isQueryTypeValid(String queryType) {
+ if (queryType == null) {
+ return true;
+ }
+ return SearchType.contains(queryType);
+ }
+
+ private static boolean isSortValid(String sort) {
+ return Sort.contains(sort.toUpperCase());
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/filter/PackageRecordFilter.java b/src/main/java/org/folio/rest/model/filter/PackageRecordFilter.java
new file mode 100644
index 000000000..111d56e72
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/PackageRecordFilter.java
@@ -0,0 +1,109 @@
+package org.folio.rest.model.filter;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+import java.util.Optional;
+import lombok.Builder;
+import lombok.Value;
+import org.folio.holdingsiq.model.PackageFilter;
+import org.folio.holdingsiq.model.PackageFilterFreeAccess;
+import org.folio.holdingsiq.model.PackageFilterSelected;
+import org.folio.holdingsiq.model.PackageFilterType;
+import org.folio.holdingsiq.model.PackageFilterVisibility;
+import org.folio.holdingsiq.model.PackageSearchField;
+import org.folio.holdingsiq.model.SearchType;
+import org.folio.properties.common.SearchProperties;
+import org.folio.repository.RecordType;
+import org.folio.rest.util.IdParser;
+import org.folio.rest.util.RestConstants;
+
+@Value
+@Builder
+public class PackageRecordFilter implements Filter {
+
+ private static final FilterValidator VALIDATOR = new PackageFilterValidator();
+
+ String query;
+ String queryField;
+ String queryType;
+ boolean highlight;
+
+ String providerId;
+ String filterCustom;
+ String filterSelected;
+ String filterType;
+ String filterVisibility;
+ String filterFreeAccess;
+ String sort;
+ int page;
+ int count;
+ List filterTags;
+ List filterAccessType;
+
+ public static PackageRecordFilterBuilder builder() {
+ return new PackageRecordFilterBuilder() {
+ @Override
+ public PackageRecordFilter build() {
+ PackageRecordFilter filter = super.build();
+ VALIDATOR.validate(filter);
+ return filter;
+ }
+ };
+ }
+
+ public Optional getSearchType() {
+ return Optional.ofNullable(queryType).map(SearchType::fromValue);
+ }
+
+ public Optional getPackageSearchField() {
+ return Optional.ofNullable(queryField).map(PackageSearchField::fromValue);
+ }
+
+ public Optional getPackageFilterSelected() {
+ return Optional.ofNullable(filterSelected).map(RestConstants.FILTER_SELECTED_MAPPING::get);
+ }
+
+ public Optional getPackageFilterType() {
+ return Optional.ofNullable(filterType).map(PackageFilterType::fromValue);
+ }
+
+ public Optional getPackageFilterFreeAccess() {
+ return Optional.ofNullable(filterFreeAccess).map(RestConstants.FILTER_FREE_ACCESS_MAPPING::get);
+ }
+
+ public Optional getPackageFilterVisibility() {
+ return Optional.ofNullable(filterVisibility).map(RestConstants.FILTER_VISIBILITY_MAPPING::get);
+ }
+
+ @Override
+ public RecordType getRecordType() {
+ return RecordType.PACKAGE;
+ }
+
+ @Override
+ public List searchCriteriaValues() {
+ return asList(query, filterCustom, filterSelected);
+ }
+
+ public Integer parseProviderId() {
+ return IdParser.parseProviderId(providerId);
+ }
+
+ public Boolean resolveFilterCustom() {
+ return filterCustom == null ? null : Boolean.parseBoolean(filterCustom);
+ }
+
+ public PackageFilter toClientFilter(SearchProperties searchProperties) {
+ return PackageFilter.builder()
+ .query(query)
+ .searchType(getSearchType().orElse(SearchType.fromValue(searchProperties.packagesSearchType())))
+ .searchField(getPackageSearchField().orElse(null))
+ .highlightTag(highlight ? searchProperties.highlightTag() : null)
+ .filterSelected(getPackageFilterSelected().orElse(null))
+ .filterType(getPackageFilterType().orElse(null))
+ .filterPackageFreeAccess(getPackageFilterFreeAccess().orElse(null))
+ .filterVisibility(getPackageFilterVisibility().orElse(null))
+ .build();
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/filter/ProviderFilter.java b/src/main/java/org/folio/rest/model/filter/ProviderFilter.java
new file mode 100644
index 000000000..8d172ddca
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/ProviderFilter.java
@@ -0,0 +1,41 @@
+package org.folio.rest.model.filter;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+import lombok.Builder;
+import lombok.Value;
+import org.folio.repository.RecordType;
+
+@Value
+@Builder
+public class ProviderFilter implements Filter {
+
+ String query;
+ String sort;
+ int page;
+ int count;
+ List filterTags;
+ List filterAccessType;
+
+ @Override
+ public RecordType getRecordType() {
+ return RecordType.PROVIDER;
+ }
+
+ @Override
+ public List searchCriteriaValues() {
+ return asList(query);
+ }
+
+ public static ProviderFilterBuilder builder() {
+ return new ProviderFilterBuilder() {
+ @Override
+ public ProviderFilter build() {
+ ProviderFilter filter = super.build();
+ FilterValidators.validateProvider(filter);
+ return filter;
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/filter/ResourceFilter.java b/src/main/java/org/folio/rest/model/filter/ResourceFilter.java
new file mode 100644
index 000000000..086eda3bc
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/ResourceFilter.java
@@ -0,0 +1,69 @@
+package org.folio.rest.model.filter;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+import lombok.Builder;
+import lombok.Value;
+import org.folio.holdingsiq.model.FilterQuery;
+import org.folio.holdingsiq.model.PackageId;
+import org.folio.repository.RecordType;
+import org.folio.rest.util.IdParser;
+
+@Value
+@Builder
+public class ResourceFilter implements Filter {
+
+ String packageId;
+ String filterSelected;
+ String filterType;
+ String filterName;
+ String filterIsxn;
+ String filterSubject;
+ String filterPublisher;
+ String sort;
+ int page;
+ int count;
+ List filterTags;
+ List filterAccessType;
+
+ @Override
+ public RecordType getRecordType() {
+ return RecordType.RESOURCE;
+ }
+
+ @Override
+ public List searchCriteriaValues() {
+ return asList(filterIsxn, filterName, filterPublisher, filterSubject, filterSelected);
+ }
+
+ public PackageId parsePackageId() {
+ return IdParser.parsePackageId(packageId);
+ }
+
+ public String resolveFilterSelected() {
+ return Filter.mapFilterSelected(filterSelected);
+ }
+
+ public FilterQuery createFilterQuery() {
+ return FilterQuery.builder()
+ .type(filterType)
+ .name(filterName)
+ .isxn(filterIsxn)
+ .subject(filterSubject)
+ .publisher(filterPublisher)
+ .selected(resolveFilterSelected())
+ .build();
+ }
+
+ public static ResourceFilterBuilder builder() {
+ return new ResourceFilterBuilder() {
+ @Override
+ public ResourceFilter build() {
+ ResourceFilter filter = super.build();
+ FilterValidators.validateResource(filter);
+ return filter;
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/filter/TagFilter.java b/src/main/java/org/folio/rest/model/filter/TagFilter.java
index f3a26191e..dbc176c02 100644
--- a/src/main/java/org/folio/rest/model/filter/TagFilter.java
+++ b/src/main/java/org/folio/rest/model/filter/TagFilter.java
@@ -1,8 +1,11 @@
package org.folio.rest.model.filter;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
import java.util.List;
import lombok.Builder;
import lombok.Value;
+import org.apache.commons.lang3.Strings;
import org.folio.repository.RecordType;
@Value
@@ -14,4 +17,28 @@ public class TagFilter {
RecordType recordType;
int offset;
int count;
+
+ public static TagFilter from(Filter filter) {
+ TagFilterBuilder builder = TagFilter.builder()
+ .tags(filter.getFilterTags())
+ .recordType(filter.getRecordType())
+ .offset((filter.getPage() - 1) * filter.getCount())
+ .count(filter.getCount());
+
+ switch (filter) {
+ case PackageRecordFilter pf -> builder.recordIdPrefix(ensureTrailingDash(pf.getProviderId()));
+ case ResourceFilter rf -> builder.recordIdPrefix(ensureTrailingDash(rf.getPackageId()));
+ case TitleFilter tf -> {
+ builder.recordType(RecordType.RESOURCE);
+ builder.recordIdPrefix("");
+ }
+ case ProviderFilter prov -> builder.recordIdPrefix("");
+ }
+
+ return builder.build();
+ }
+
+ private static String ensureTrailingDash(String id) {
+ return isBlank(id) ? "" : Strings.CS.appendIfMissing(id, "-");
+ }
}
diff --git a/src/main/java/org/folio/rest/model/filter/TitleFilter.java b/src/main/java/org/folio/rest/model/filter/TitleFilter.java
new file mode 100644
index 000000000..70c3c2592
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/filter/TitleFilter.java
@@ -0,0 +1,70 @@
+package org.folio.rest.model.filter;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+import lombok.Builder;
+import lombok.Value;
+import org.folio.holdingsiq.model.FilterQuery;
+import org.folio.repository.RecordType;
+
+@Value
+@Builder
+public class TitleFilter implements Filter {
+
+ String filterSelected;
+ String filterType;
+ String filterName;
+ String filterIsxn;
+ String filterSubject;
+ String filterPublisher;
+ List filterPackageIds;
+ String sort;
+ int page;
+ int count;
+ List filterTags;
+ List filterAccessType;
+
+ @Override
+ public RecordType getRecordType() {
+ return RecordType.TITLE;
+ }
+
+ @Override
+ public List searchCriteriaValues() {
+ return asList(filterIsxn, filterName, filterPublisher, filterSubject, filterSelected);
+ }
+
+ public String resolveFilterSelected() {
+ return Filter.mapFilterSelected(filterSelected);
+ }
+
+ public List resolveFilterPackageIds() {
+ return filterPackageIds == null ? null : filterPackageIds.stream()
+ .map(Integer::parseInt)
+ .toList();
+ }
+
+ public FilterQuery createFilterQuery() {
+ return FilterQuery.builder()
+ .type(filterType)
+ .name(filterName)
+ .isxn(filterIsxn)
+ .subject(filterSubject)
+ .publisher(filterPublisher)
+ .selected(resolveFilterSelected())
+ .packageIds(resolveFilterPackageIds())
+ .build();
+ }
+
+ public static TitleFilterBuilder builder() {
+ return new TitleFilterBuilder() {
+ @Override
+ public TitleFilter build() {
+ TitleFilter filter = super.build();
+ FilterValidators.validateTitle(filter);
+ return filter;
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/folio/rest/model/query/PackageSearchParams.java b/src/main/java/org/folio/rest/model/query/PackageSearchParams.java
new file mode 100644
index 000000000..5275795b0
--- /dev/null
+++ b/src/main/java/org/folio/rest/model/query/PackageSearchParams.java
@@ -0,0 +1,8 @@
+package org.folio.rest.model.query;
+
+public record PackageSearchParams(
+ String query,
+ String queryField,
+ String queryType,
+ boolean highlight
+) { }
diff --git a/src/main/java/org/folio/rest/util/ErrorUtil.java b/src/main/java/org/folio/rest/util/ErrorUtil.java
index cbbd2bcdc..ea5fa83f2 100644
--- a/src/main/java/org/folio/rest/util/ErrorUtil.java
+++ b/src/main/java/org/folio/rest/util/ErrorUtil.java
@@ -19,6 +19,15 @@ public final class ErrorUtil {
private ErrorUtil() {
}
+ public static JsonapiError createErrors(List errorMessage) {
+ var jsonapiErrorResponses = errorMessage.stream()
+ .map(msg -> createErrorResponse(msg, null))
+ .toList();
+ return new JsonapiError()
+ .withErrors(jsonapiErrorResponses)
+ .withJsonapi(RestConstants.JSONAPI);
+ }
+
public static JsonapiError createError(String errorMessage) {
return createError(errorMessage, null);
}
diff --git a/src/main/java/org/folio/rest/util/ExceptionMappers.java b/src/main/java/org/folio/rest/util/ExceptionMappers.java
index 23b92f211..200bb0659 100644
--- a/src/main/java/org/folio/rest/util/ExceptionMappers.java
+++ b/src/main/java/org/folio/rest/util/ExceptionMappers.java
@@ -1,14 +1,14 @@
package org.folio.rest.util;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
-import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
-import static org.apache.http.HttpStatus.SC_CONFLICT;
-import static org.apache.http.HttpStatus.SC_FORBIDDEN;
-import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
-import static org.apache.http.HttpStatus.SC_NOT_FOUND;
-import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
-import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY;
import static org.apache.http.protocol.HTTP.CONTENT_TYPE;
+import static org.folio.HttpStatus.SC_BAD_REQUEST;
+import static org.folio.HttpStatus.SC_CONFLICT;
+import static org.folio.HttpStatus.SC_FORBIDDEN;
+import static org.folio.HttpStatus.SC_INTERNAL_SERVER_ERROR;
+import static org.folio.HttpStatus.SC_NOT_FOUND;
+import static org.folio.HttpStatus.SC_UNAUTHORIZED;
+import static org.folio.HttpStatus.SC_UNPROCESSABLE_CONTENT;
import static org.folio.rest.util.ErrorUtil.createError;
import static org.folio.rest.util.ErrorUtil.createErrorFromRmApiResponse;
import static org.folio.rest.util.RestConstants.JSON_API_TYPE;
@@ -281,7 +281,7 @@ public static Function error409ProcessInPr
*/
public static Function error422InputValidationMapper() {
return exception ->
- Response.status(SC_UNPROCESSABLE_ENTITY)
+ Response.status(SC_UNPROCESSABLE_CONTENT)
.header(CONTENT_TYPE, JSON_API_TYPE)
.entity(createError(exception.getMessage(), exception.getMessageDetail()))
.build();
@@ -299,7 +299,7 @@ public static Function error422InputValidati
*/
public static Function error422ConfigurationInvalidMapper() {
return exception ->
- Response.status(SC_UNPROCESSABLE_ENTITY)
+ Response.status(SC_UNPROCESSABLE_CONTENT)
.header(CONTENT_TYPE, JSON_API_TYPE)
.entity(ErrorUtil.createError(exception.getErrors().getFirst().getMessage()))
.build();
@@ -317,7 +317,7 @@ public static Function error422Configur
*/
public static Function error422UcSettingsInvalidMapper() {
return exception ->
- Response.status(SC_UNPROCESSABLE_ENTITY)
+ Response.status(SC_UNPROCESSABLE_CONTENT)
.header(CONTENT_TYPE, JSON_API_TYPE)
.entity(ErrorUtil.createError(exception.getMessage()))
.build();
diff --git a/src/main/java/org/folio/rest/util/IdParser.java b/src/main/java/org/folio/rest/util/IdParser.java
index 3e76b2a38..1bacef21f 100644
--- a/src/main/java/org/folio/rest/util/IdParser.java
+++ b/src/main/java/org/folio/rest/util/IdParser.java
@@ -27,32 +27,29 @@ private IdParser() {
}
public static ResourceId parseResourceId(String id) {
- List parts = parseId(id, 3, RESOURCE_ID_INVALID_ERROR, RESOURCE_ID_INVALID_ERROR);
- return ResourceId.builder()
- .providerIdPart(parts.get(0))
- .packageIdPart(parts.get(1))
- .titleIdPart(parts.get(2)).build();
+ var parts = parseId(id, 3, RESOURCE_ID_INVALID_ERROR, RESOURCE_ID_INVALID_ERROR);
+ return new ResourceId(parts.get(0), parts.get(1), parts.get(2));
}
public static PackageId parsePackageId(String id) {
- List parts = parseId(id, 2, PACKAGE_ID_MISSING_ERROR, PACKAGE_ID_INVALID_ERROR);
- return PackageId.builder().providerIdPart(parts.get(0)).packageIdPart(parts.get(1)).build();
+ var parts = parseId(id, 2, PACKAGE_ID_MISSING_ERROR, PACKAGE_ID_INVALID_ERROR);
+ return new PackageId(parts.get(0), parts.get(1));
}
- public static Long parseTitleId(String id) {
+ public static Integer parseTitleId(String id) {
return parseId(id, 1, TITLE_ID_IS_INVALID_ERROR, TITLE_ID_IS_INVALID_ERROR).getFirst();
}
- public static Long parseProviderId(String id) {
+ public static Integer parseProviderId(String id) {
return parseId(id, 1, INVALID_PROVIDER_ID_ERROR, INVALID_PROVIDER_ID_ERROR).getFirst();
}
public static String packageIdToString(PackageId packageId) {
- return concat(packageId.getProviderIdPart(), packageId.getPackageIdPart());
+ return concat(packageId.providerIdPart(), packageId.packageIdPart());
}
public static String resourceIdToString(ResourceId resourceId) {
- return concat(resourceId.getProviderIdPart(), resourceId.getPackageIdPart(), resourceId.getTitleIdPart());
+ return concat(resourceId.providerIdPart(), resourceId.packageIdPart(), resourceId.titleIdPart());
}
public static List resourceIdsToStrings(List resourceIds) {
@@ -72,11 +69,7 @@ public static String getResourceId(HoldingsId holding) {
}
public static ResourceId getResourceId(DbHoldingInfo resource) {
- return ResourceId.builder()
- .providerIdPart(resource.getVendorId())
- .packageIdPart(resource.getPackageId())
- .titleIdPart(resource.getTitleId())
- .build();
+ return new ResourceId(resource.getVendorId(), resource.getPackageId(), resource.getTitleId());
}
public static List getPackageIds(List packageIds) {
@@ -85,10 +78,7 @@ public static List getPackageIds(List packageIds) {
public static List getPackageIds(Packages packages) {
return mapItems(packages.getPackagesList(), packageData ->
- PackageId.builder()
- .packageIdPart(packageData.getPackageId())
- .providerIdPart(packageData.getVendorId())
- .build());
+ new PackageId(packageData.getVendorId(), packageData.getPackageId()));
}
public static List getTitleIds(List resources) {
@@ -103,16 +93,16 @@ private static String concat(long... parts) {
return StringUtils.join(parts, '-');
}
- private static List parseId(String id, int partCount, String wrongCountErrorMessage,
+ private static List parseId(String id, int partCount, String wrongCountErrorMessage,
String numberFormatErrorMessage) {
- String[] parts = id.split("-");
+ var parts = id.split("-");
if (parts.length != partCount) {
throw new ValidationException(String.format(wrongCountErrorMessage, id));
}
- List parsedParts = new ArrayList<>();
+ var parsedParts = new ArrayList();
try {
for (String part : parts) {
- parsedParts.add(Long.parseLong(part));
+ parsedParts.add(Integer.parseInt(part));
}
return parsedParts;
} catch (NumberFormatException e) {
diff --git a/src/main/java/org/folio/rest/util/RestConstants.java b/src/main/java/org/folio/rest/util/RestConstants.java
index fedaca56a..4e61c9aae 100644
--- a/src/main/java/org/folio/rest/util/RestConstants.java
+++ b/src/main/java/org/folio/rest/util/RestConstants.java
@@ -2,6 +2,9 @@
import java.util.List;
import java.util.Map;
+import org.folio.holdingsiq.model.PackageFilterFreeAccess;
+import org.folio.holdingsiq.model.PackageFilterSelected;
+import org.folio.holdingsiq.model.PackageFilterVisibility;
import org.folio.rest.jaxrs.model.JsonAPI;
public final class RestConstants {
@@ -19,17 +22,29 @@ public final class RestConstants {
public static final String TITLE_RECTYPE = "title";
public static final String RESOURCE_RECTYPE = "resource";
- public static final Map