From 9794d266b93fe0109b6c3d59cd4d4e9828f9d55b Mon Sep 17 00:00:00 2001 From: Stig Norland Date: Wed, 8 Jan 2025 13:13:15 +0100 Subject: [PATCH] autoformat --- .../indexingclient/AggregationsValidator.java | 85 +- .../unit/nva/indexingclient/BatchIndexer.java | 12 +- .../no/unit/nva/indexingclient/Constants.java | 82 +- .../nva/indexingclient/EmitEventUtils.java | 5 +- .../EventBasedBatchIndexer.java | 9 +- .../ImportDataRequestEvent.java | 145 +- .../nva/indexingclient/IndexingResult.java | 6 +- .../indexingclient/IndexingResultRecord.java | 4 +- .../StartBatchIndexingHandler.java | 7 +- .../keybatch/GenerateKeyBatchesHandler.java | 238 +- .../keybatch/KeyBasedBatchIndexHandler.java | 32 +- .../keybatch/KeyBatchRequestEvent.java | 10 +- .../nva/indexingclient/BatchIndexTest.java | 10 +- .../EventBasedBatchIndexerTest.java | 20 +- .../ImportDataRequestEventTest.java | 65 +- .../StartBatchIndexingHandlerTest.java | 6 +- .../indexingclient/StubEventBridgeClient.java | 2 - .../nva/indexingclient/TestConstants.java | 6 +- .../GenerateKeyBatchesHandlerTest.java | 252 +-- .../KeyBasedBatchIndexHandlerTest.java | 563 +++-- ...DeleteImportCandidateFromIndexHandler.java | 3 - .../DeleteImportCandidateIndexHandler.java | 4 - .../handlers/DeleteIndicesHandler.java | 72 +- .../handlers/DeleteResourceEvent.java | 5 +- .../DeleteResourceFromIndexHandler.java | 67 +- .../handlers/ImportCandidateInitHandler.java | 9 +- .../handlers/IndexImportCandidateHandler.java | 2 - .../handlers/IndexResourceHandler.java | 4 - .../nva/indexing/handlers/InitHandler.java | 111 +- .../model/DeleteImportCandidateEvent.java | 21 +- .../unit/nva/indexing/model/IndexRequest.java | 4 +- .../nva/indexing/utils/RecoveryEntry.java | 9 +- ...teImportCandidateFromIndexHandlerTest.java | 15 +- ...DeleteImportCandidateIndexHandlerTest.java | 74 +- .../handlers/DeleteIndicesHandlerTest.java | 84 +- .../DeleteResourceFromIndexHandlerTest.java | 156 +- .../ImportCandidateInitHandlerTest.java | 8 +- .../IndexImportCandidateHandlerTest.java | 17 +- .../handlers/IndexResourceHandlerTest.java | 23 +- .../indexing/handlers/InitHandlerTest.java | 74 +- .../java/no/unit/nva/constants/Defaults.java | 30 +- .../no/unit/nva/constants/ErrorMessages.java | 4 +- .../java/no/unit/nva/constants/Words.java | 279 ++- .../nva/indexingclient/IndexQueueClient.java | 5 +- .../nva/indexingclient/IndexingClient.java | 287 ++- .../AuthenticatedOpenSearchClientWrapper.java | 5 +- .../indexingclient/models/IndexDocument.java | 9 +- .../models/IndicesClientWrapper.java | 76 +- .../indexingclient/models/QueueClient.java | 2 +- .../models/RestHighLevelClientWrapper.java | 143 +- .../nva/search/common/AggregationFormat.java | 215 +- .../no/unit/nva/search/common/AsType.java | 232 +- .../nva/search/common/ContentTypeUtils.java | 4 +- .../nva/search/common/OpenSearchClient.java | 26 +- .../nva/search/common/ParameterValidator.java | 21 +- .../java/no/unit/nva/search/common/Query.java | 7 +- .../unit/nva/search/common/QueryFilter.java | 7 +- .../no/unit/nva/search/common/QueryKeys.java | 8 +- .../unit/nva/search/common/SearchQuery.java | 29 +- .../common/builder/AbstractBuilder.java | 9 +- .../common/builder/AcrossFieldsQuery.java | 79 +- .../search/common/builder/ExistsQuery.java | 8 +- .../common/builder/FuzzyKeywordQuery.java | 127 +- .../search/common/builder/HasPartsQuery.java | 7 +- .../search/common/builder/KeywordQuery.java | 8 +- .../search/common/builder/PartOfQuery.java | 6 +- .../nva/search/common/builder/RangeQuery.java | 7 +- .../nva/search/common/builder/TextQuery.java | 9 +- .../nva/search/common/constant/Functions.java | 260 ++- .../nva/search/common/constant/Patterns.java | 132 +- .../search/common/csv/CsvBindByNameOrder.java | 2 +- .../unit/nva/search/common/csv/ExportCsv.java | 274 ++- ...aderColumnNameAndOrderMappingStrategy.java | 6 +- .../common/csv/ResourceCsvTransformer.java | 1 - .../search/common/enums/FieldOperator.java | 38 +- .../nva/search/common/enums/ParameterKey.java | 127 +- .../search/common/enums/ParameterKind.java | 44 +- .../common/enums/PublicationStatus.java | 14 +- .../unit/nva/search/common/enums/SortKey.java | 33 +- .../search/common/enums/ValueEncoding.java | 4 +- .../search/common/jwt/CachedJwtProvider.java | 45 +- .../common/jwt/CachedValueProvider.java | 16 +- .../common/jwt/CognitoAuthenticator.java | 196 +- .../no/unit/nva/search/common/jwt/Tools.java | 6 +- .../unit/nva/search/common/records/Facet.java | 4 +- .../search/common/records/FacetsBuilder.java | 9 +- .../search/common/records/FilterBuilder.java | 4 +- .../common/records/HttpResponseFormatter.java | 17 +- .../common/records/JsonNodeMutator.java | 2 +- .../search/common/records/PagedSearch.java | 43 +- .../common/records/PagedSearchBuilder.java | 9 +- .../common/records/ResponseLogInfo.java | 118 +- .../search/common/records/SwsResponse.java | 13 +- .../search/common/records/UserSettings.java | 1 - .../records/UsernamePasswordWrapper.java | 48 +- .../nva/search/importcandidate/Constants.java | 8 +- .../ImportCandidateClient.java | 9 +- .../ImportCandidateParameter.java | 20 +- .../ImportCandidateSearchQuery.java | 17 +- .../importcandidate/ImportCandidateSort.java | 9 +- .../unit/nva/search/resource/Constants.java | 10 +- .../nva/search/resource/LegacyMutator.java | 23 +- .../search/resource/ResourceAccessFilter.java | 8 +- .../nva/search/resource/ResourceClient.java | 15 +- .../search/resource/ResourceParameter.java | 623 +++--- .../search/resource/ResourceSearchQuery.java | 30 +- .../nva/search/resource/ResourceSort.java | 112 +- .../resource/ResourceStreamBuilders.java | 9 +- .../search/resource/SimplifiedMutator.java | 676 +++--- .../search/resource/UserSettingsClient.java | 16 +- .../search/resource/response/Contributor.java | 10 +- .../resource/response/OtherIdentifiers.java | 10 +- .../resource/response/PublishingDetails.java | 2 +- .../resource/response/RecordMetadata.java | 2 +- .../response/ResourceSearchResponse.java | 234 +- .../unit/nva/search/scroll/ScrollClient.java | 85 +- .../nva/search/scroll/ScrollParameter.java | 154 +- .../unit/nva/search/scroll/ScrollQuery.java | 201 +- .../no/unit/nva/search/ticket/Constants.java | 184 +- .../nva/search/ticket/TicketAccessFilter.java | 18 +- .../unit/nva/search/ticket/TicketClient.java | 9 +- .../nva/search/ticket/TicketParameter.java | 347 ++- .../nva/search/ticket/TicketSearchQuery.java | 504 +++-- .../no/unit/nva/search/ticket/TicketSort.java | 82 +- .../unit/nva/search/ticket/TicketStatus.java | 56 +- .../no/unit/nva/search/ticket/TicketType.java | 54 +- .../java/no/unit/nva/common/Containers.java | 186 +- .../no/unit/nva/common/EntrySetTools.java | 77 +- .../unit/nva/common/InjectedTestSettings.java | 40 +- .../unit/nva/common/MockedHttpResponse.java | 5 +- .../no/unit/nva/common/TestConstants.java | 8 +- .../nva/indexingclient/CachedJwtTest.java | 11 +- .../CognitoAuthenticatorTest.java | 161 +- .../indexingclient/IndexingClientTest.java | 16 +- .../models/IndexDocumentTest.java | 7 +- .../utils/HttpRequestMetadataMatcher.java | 3 +- .../utils/RequestOptionsHeaderMatcher.java | 20 +- .../nva/search/HardToHitFunctionsTest.java | 164 +- .../search/common/ContentTypeUtilsTest.java | 4 +- .../common/builder/PartOfQueryTest.java | 1 - .../ImportCandidateClientTest.java | 413 ++-- .../search/resource/ResourceClientTest.java | 1962 ++++++++--------- .../resource/ResourceSearchQueryTest.java | 21 +- .../resource/SimplifiedMutatorTest.java | 193 +- .../resource/UserSettingsClientTest.java | 100 +- .../nva/search/scroll/ScrollClientTest.java | 6 +- .../nva/search/ticket/TicketClientTest.java | 1264 ++++++----- .../resources/ContributorNodeReducer.java | 7 +- .../nva/search/ExportResourceHandler.java | 3 - .../SearchImportCandidateAuthHandler.java | 68 +- .../search/SearchResource20241201Handler.java | 7 +- .../nva/search/SearchResourceAuthHandler.java | 94 +- .../nva/search/SearchResourceHandler.java | 104 +- .../search/SearchResourceLegacyHandler.java | 86 +- .../nva/search/SearchTicketAuthHandler.java | 75 +- .../nva/search/ExportResourceHandlerTest.java | 13 +- .../SearchImportCandidateAuthHandlerTest.java | 500 +++-- .../SearchResource20241201HandlerTest.java | 145 +- .../search/SearchResourceAuthHandlerTest.java | 190 +- .../SearchResourceLegacyHandlerTest.java | 25 +- .../search/SearchTicketAuthHandlerTest.java | 23 +- .../main/java/no/unit/nva/LogAppender.java | 66 +- .../nva/indexing/testutils/Constants.java | 16 +- .../testutils/FakeIndexingClient.java | 115 +- .../testutils/FakeSearchResponse.java | 117 +- .../nva/indexing/testutils/FakeSqsClient.java | 22 +- .../indexing/testutils/MockedJwtProvider.java | 10 +- .../search/common/FakeGatewayResponse.java | 27 +- 168 files changed, 7359 insertions(+), 7989 deletions(-) diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/AggregationsValidator.java b/batch-index/src/main/java/no/unit/nva/indexingclient/AggregationsValidator.java index 20727e869..ab68d941c 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/AggregationsValidator.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/AggregationsValidator.java @@ -1,53 +1,52 @@ package no.unit.nva.indexingclient; import com.fasterxml.jackson.databind.JsonNode; - import java.util.ArrayList; import java.util.List; public class AggregationsValidator { - private static final String ENTITY_DESCRIPTION_POINTER = "/entityDescription"; - private static final String REFERENCE_POINTER = "/entityDescription/reference"; - private static final String PUBLICATION_CONTEXT_POINTER = - "/entityDescription/reference/publicationContext"; - private static final String PUBLICATION_INSTANCE_POINTER = - "/entityDescription/reference/publicationInstance"; - private static final String IDENTIFIER_POINTER = "/identifier"; - private static final String DELIMITER = ", "; - private static final String REPORT_TEMPLATE = "Document %s has missing fields %s"; - private final List report; - private final JsonNode document; - - public AggregationsValidator(JsonNode document) { - this.document = document; - this.report = new ArrayList<>(); - } - - private static boolean isNotValidNode(JsonNode node) { - return !node.isObject(); - } - - public String getReport() { - return String.format( - REPORT_TEMPLATE, - document.at(IDENTIFIER_POINTER).textValue(), - String.join(DELIMITER, report)); - } - - public boolean isValid() { - List.of( - ENTITY_DESCRIPTION_POINTER, - REFERENCE_POINTER, - PUBLICATION_CONTEXT_POINTER, - PUBLICATION_INSTANCE_POINTER) - .forEach(this::validateNode); - return report.isEmpty(); - } - - private void validateNode(String value) { - if (isNotValidNode(document.at(value))) { - report.add(value); - } + private static final String ENTITY_DESCRIPTION_POINTER = "/entityDescription"; + private static final String REFERENCE_POINTER = "/entityDescription/reference"; + private static final String PUBLICATION_CONTEXT_POINTER = + "/entityDescription/reference/publicationContext"; + private static final String PUBLICATION_INSTANCE_POINTER = + "/entityDescription/reference/publicationInstance"; + private static final String IDENTIFIER_POINTER = "/identifier"; + private static final String DELIMITER = ", "; + private static final String REPORT_TEMPLATE = "Document %s has missing fields %s"; + private final List report; + private final JsonNode document; + + public AggregationsValidator(JsonNode document) { + this.document = document; + this.report = new ArrayList<>(); + } + + private static boolean isNotValidNode(JsonNode node) { + return !node.isObject(); + } + + public String getReport() { + return String.format( + REPORT_TEMPLATE, + document.at(IDENTIFIER_POINTER).textValue(), + String.join(DELIMITER, report)); + } + + public boolean isValid() { + List.of( + ENTITY_DESCRIPTION_POINTER, + REFERENCE_POINTER, + PUBLICATION_CONTEXT_POINTER, + PUBLICATION_INSTANCE_POINTER) + .forEach(this::validateNode); + return report.isEmpty(); + } + + private void validateNode(String value) { + if (isNotValidNode(document.at(value))) { + report.add(value); } + } } diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/BatchIndexer.java b/batch-index/src/main/java/no/unit/nva/indexingclient/BatchIndexer.java index dd1aef373..5560d43dc 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/BatchIndexer.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/BatchIndexer.java @@ -1,25 +1,21 @@ package no.unit.nva.indexingclient; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.identifiers.SortableIdentifier; import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.s3.ListingResult; import no.unit.nva.s3.S3Driver; - import nva.commons.core.paths.UnixPath; - import org.opensearch.action.bulk.BulkItemResponse; import org.opensearch.action.bulk.BulkItemResponse.Failure; import org.opensearch.action.bulk.BulkResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import software.amazon.awssdk.services.s3.S3Client; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - public class BatchIndexer implements IndexingResult { private static final Logger logger = LoggerFactory.getLogger(BatchIndexer.class); diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/Constants.java b/batch-index/src/main/java/no/unit/nva/indexingclient/Constants.java index 395e5a5d5..2b11626cd 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/Constants.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/Constants.java @@ -14,45 +14,45 @@ public final class Constants { - public static final String MANDATORY_UNUSED_SUBTOPIC = "DETAIL.WITH.TOPIC"; - public static final String EVENT_BUS = ENVIRONMENT.readEnv("EVENT_BUS"); - public static final String TOPIC = ENVIRONMENT.readEnv("TOPIC"); - static final String BATCH_INDEX_EVENT_TOPIC = "SearchService.Index.Batch"; - static final String S3_LOCATION_FIELD = "s3Location"; - static final Config config = ConfigFactory.load(); - static final String PERSISTED_RESOURCES_PATH = config.getString("batch.persistedResourcesPath"); - static final String BATCH_INDEX_EVENT_BUS_NAME = config.getString("batch.index.eventbusname"); - static final boolean RECURSION_ENABLED = config.getBoolean("batch.index.recursion"); - private static final String AWS_REGION_ENV_VARIABLE = "AWS_REGION"; - private static final int NUMBER_OF_FILES_PER_EVENT = - config.getInt("batch.index.number_of_files_per_event"); - - public static final int NUMBER_OF_FILES_PER_EVENT_ENVIRONMENT_VARIABLE = - ENVIRONMENT - .readEnvOpt("NUMBER_OF_FILES_PER_EVENT") - .map(Integer::parseInt) - .orElse(NUMBER_OF_FILES_PER_EVENT); - - @JacocoGenerated - public Constants() {} - - @JacocoGenerated - public static EventBridgeClient defaultEventBridgeClient() { - return EventBridgeClient.builder().httpClient(UrlConnectionHttpClient.create()).build(); - } - - @JacocoGenerated - public static IndexingClient defaultEsClient() { - return IndexingClient.defaultIndexingClient(); - } - - @JacocoGenerated - public static S3Client defaultS3Client() { - String awsRegion = - ENVIRONMENT.readEnvOpt(AWS_REGION_ENV_VARIABLE).orElse(Region.EU_WEST_1.toString()); - return S3Client.builder() - .region(Region.of(awsRegion)) - .httpClient(UrlConnectionHttpClient.builder().build()) - .build(); - } + public static final String MANDATORY_UNUSED_SUBTOPIC = "DETAIL.WITH.TOPIC"; + public static final String EVENT_BUS = ENVIRONMENT.readEnv("EVENT_BUS"); + public static final String TOPIC = ENVIRONMENT.readEnv("TOPIC"); + static final String BATCH_INDEX_EVENT_TOPIC = "SearchService.Index.Batch"; + static final String S3_LOCATION_FIELD = "s3Location"; + static final Config config = ConfigFactory.load(); + static final String PERSISTED_RESOURCES_PATH = config.getString("batch.persistedResourcesPath"); + static final String BATCH_INDEX_EVENT_BUS_NAME = config.getString("batch.index.eventbusname"); + static final boolean RECURSION_ENABLED = config.getBoolean("batch.index.recursion"); + private static final String AWS_REGION_ENV_VARIABLE = "AWS_REGION"; + private static final int NUMBER_OF_FILES_PER_EVENT = + config.getInt("batch.index.number_of_files_per_event"); + + public static final int NUMBER_OF_FILES_PER_EVENT_ENVIRONMENT_VARIABLE = + ENVIRONMENT + .readEnvOpt("NUMBER_OF_FILES_PER_EVENT") + .map(Integer::parseInt) + .orElse(NUMBER_OF_FILES_PER_EVENT); + + @JacocoGenerated + public Constants() {} + + @JacocoGenerated + public static EventBridgeClient defaultEventBridgeClient() { + return EventBridgeClient.builder().httpClient(UrlConnectionHttpClient.create()).build(); + } + + @JacocoGenerated + public static IndexingClient defaultEsClient() { + return IndexingClient.defaultIndexingClient(); + } + + @JacocoGenerated + public static S3Client defaultS3Client() { + String awsRegion = + ENVIRONMENT.readEnvOpt(AWS_REGION_ENV_VARIABLE).orElse(Region.EU_WEST_1.toString()); + return S3Client.builder() + .region(Region.of(awsRegion)) + .httpClient(UrlConnectionHttpClient.builder().build()) + .build(); + } } diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/EmitEventUtils.java b/batch-index/src/main/java/no/unit/nva/indexingclient/EmitEventUtils.java index f482b2ba3..4613f7598 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/EmitEventUtils.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/EmitEventUtils.java @@ -4,16 +4,13 @@ import static no.unit.nva.indexingclient.Constants.MANDATORY_UNUSED_SUBTOPIC; import com.amazonaws.services.lambda.runtime.Context; - +import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import software.amazon.awssdk.services.eventbridge.EventBridgeClient; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequestEntry; -import java.time.Instant; - public final class EmitEventUtils { private static final Logger logger = LoggerFactory.getLogger(EmitEventUtils.class); diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/EventBasedBatchIndexer.java b/batch-index/src/main/java/no/unit/nva/indexingclient/EventBasedBatchIndexer.java index 7f854e9cf..4ddf5bc34 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/EventBasedBatchIndexer.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/EventBasedBatchIndexer.java @@ -8,23 +8,18 @@ import static no.unit.nva.indexingclient.EmitEventUtils.emitEvent; import com.amazonaws.services.lambda.runtime.Context; - +import java.io.InputStream; +import java.io.OutputStream; import no.unit.nva.events.handlers.EventHandler; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.identifiers.SortableIdentifier; - import nva.commons.core.JacocoGenerated; import nva.commons.core.ioutils.IoUtils; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import software.amazon.awssdk.services.eventbridge.EventBridgeClient; import software.amazon.awssdk.services.s3.S3Client; -import java.io.InputStream; -import java.io.OutputStream; - public class EventBasedBatchIndexer extends EventHandler { diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/ImportDataRequestEvent.java b/batch-index/src/main/java/no/unit/nva/indexingclient/ImportDataRequestEvent.java index bc87c8515..e45acc02e 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/ImportDataRequestEvent.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/ImportDataRequestEvent.java @@ -19,81 +19,76 @@ public class ImportDataRequestEvent implements EventBody, JsonSerializable { - private static final String START_OF_LISTING_INDEX = "startMarker"; - - @JsonProperty(S3_LOCATION_FIELD) - private final URI s3Location; - - @JsonProperty(START_OF_LISTING_INDEX) - private final String startMarker; - - @JsonCreator - public ImportDataRequestEvent( - @JsonProperty(S3_LOCATION_FIELD) String s3Location, - @JsonProperty(START_OF_LISTING_INDEX) String startMarker) { - this.s3Location = - Optional.ofNullable(s3Location) - .map(URI::create) - .orElseThrow(this::reportMissingValue); - this.startMarker = startMarker; + private static final String START_OF_LISTING_INDEX = "startMarker"; + + @JsonProperty(S3_LOCATION_FIELD) + private final URI s3Location; + + @JsonProperty(START_OF_LISTING_INDEX) + private final String startMarker; + + @JsonCreator + public ImportDataRequestEvent( + @JsonProperty(S3_LOCATION_FIELD) String s3Location, + @JsonProperty(START_OF_LISTING_INDEX) String startMarker) { + this.s3Location = + Optional.ofNullable(s3Location).map(URI::create).orElseThrow(this::reportMissingValue); + this.startMarker = startMarker; + } + + public ImportDataRequestEvent(String s3Location) { + this(s3Location, null); + } + + @Override + public String getTopic() { + return BATCH_INDEX_EVENT_TOPIC; + } + + public String getStartMarker() { + return startMarker; + } + + public String getS3Location() { + return s3Location.toString(); + } + + @JsonIgnore + @JacocoGenerated + public String getBucket() { + return s3Location.getHost(); + } + + @JsonIgnore + @JacocoGenerated + public String getS3Path() { + return Optional.ofNullable(s3Location).map(URI::getPath).map(this::removeRoot).orElseThrow(); + } + + @JacocoGenerated + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - public ImportDataRequestEvent(String s3Location) { - this(s3Location, null); - } - - @Override - public String getTopic() { - return BATCH_INDEX_EVENT_TOPIC; - } - - public String getStartMarker() { - return startMarker; - } - - public String getS3Location() { - return s3Location.toString(); - } - - @JsonIgnore - @JacocoGenerated - public String getBucket() { - return s3Location.getHost(); - } - - @JsonIgnore - @JacocoGenerated - public String getS3Path() { - return Optional.ofNullable(s3Location) - .map(URI::getPath) - .map(this::removeRoot) - .orElseThrow(); - } - - @JacocoGenerated - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ImportDataRequestEvent that)) { - return false; - } - return Objects.equals(getS3Location(), that.getS3Location()) - && Objects.equals(getStartMarker(), that.getStartMarker()); - } - - @JacocoGenerated - @Override - public int hashCode() { - return Objects.hash(getS3Location(), getStartMarker()); - } - - private IllegalArgumentException reportMissingValue() { - return new IllegalArgumentException("Missing input:" + S3_LOCATION_FIELD); - } - - private String removeRoot(String path) { - return path.startsWith(SLASH) ? path.substring(1) : path; + if (!(o instanceof ImportDataRequestEvent that)) { + return false; } + return Objects.equals(getS3Location(), that.getS3Location()) + && Objects.equals(getStartMarker(), that.getStartMarker()); + } + + @JacocoGenerated + @Override + public int hashCode() { + return Objects.hash(getS3Location(), getStartMarker()); + } + + private IllegalArgumentException reportMissingValue() { + return new IllegalArgumentException("Missing input:" + S3_LOCATION_FIELD); + } + + private String removeRoot(String path) { + return path.startsWith(SLASH) ? path.substring(1) : path; + } } diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResult.java b/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResult.java index 5fdd8f8e6..e07c771de 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResult.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResult.java @@ -4,9 +4,9 @@ public interface IndexingResult { - List failedResults(); + List failedResults(); - String nextStartMarker(); + String nextStartMarker(); - boolean truncated(); + boolean truncated(); } diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResultRecord.java b/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResultRecord.java index b9b49e104..14e1fd585 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResultRecord.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/IndexingResultRecord.java @@ -3,5 +3,5 @@ import java.util.List; public record IndexingResultRecord( - List failedResults, String nextStartMarker, boolean truncated) - implements IndexingResult {} + List failedResults, String nextStartMarker, boolean truncated) + implements IndexingResult {} diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/StartBatchIndexingHandler.java b/batch-index/src/main/java/no/unit/nva/indexingclient/StartBatchIndexingHandler.java index 877cffae1..8a2daefbf 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/StartBatchIndexingHandler.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/StartBatchIndexingHandler.java @@ -7,16 +7,13 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; - -import nva.commons.core.JacocoGenerated; - -import software.amazon.awssdk.services.eventbridge.EventBridgeClient; - import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import nva.commons.core.JacocoGenerated; +import software.amazon.awssdk.services.eventbridge.EventBridgeClient; public class StartBatchIndexingHandler implements RequestStreamHandler { diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandler.java b/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandler.java index 73796c578..4a4e67348 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandler.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandler.java @@ -1,5 +1,7 @@ package no.unit.nva.indexingclient.keybatch; +import static java.util.Objects.nonNull; +import static java.util.UUID.randomUUID; import static no.unit.nva.constants.Defaults.ENVIRONMENT; import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.indexingclient.Constants.EVENT_BUS; @@ -7,20 +9,17 @@ import static no.unit.nva.indexingclient.Constants.TOPIC; import static no.unit.nva.indexingclient.Constants.defaultS3Client; -import static java.util.Objects.nonNull; -import static java.util.UUID.randomUUID; - import com.amazonaws.services.lambda.runtime.Context; - +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.List; +import java.util.stream.Collectors; import no.unit.nva.events.handlers.EventHandler; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.indexingclient.EventBasedBatchIndexer; - import nva.commons.core.JacocoGenerated; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.services.eventbridge.EventBridgeClient; @@ -33,124 +32,113 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.S3Object; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.List; -import java.util.stream.Collectors; - public class GenerateKeyBatchesHandler extends EventHandler { - public static final String DEFAULT_BATCH_SIZE = "1000"; - public static final String DELIMITER = "/"; - public static final String DEFAULT_START_MARKER = null; - public static final String START_MARKER_MESSAGE = "Start marker: {}"; - public static final String INPUT_BUCKET = ENVIRONMENT.readEnv("PERSISTED_RESOURCES_BUCKET"); - public static final String OUTPUT_BUCKET = ENVIRONMENT.readEnv("KEY_BATCHES_BUCKET"); - public static final int MAX_KEYS = - Integer.parseInt(ENVIRONMENT.readEnvOpt("BATCH_SIZE").orElse(DEFAULT_BATCH_SIZE)); - private static final Logger logger = LoggerFactory.getLogger(GenerateKeyBatchesHandler.class); - private final S3Client inputClient; - private final S3Client outputClient; - private final EventBridgeClient eventBridgeClient; - - @JacocoGenerated - public GenerateKeyBatchesHandler() { - this(defaultS3Client(), defaultS3Client(), defaultEventBridgeClient()); - } - - public GenerateKeyBatchesHandler( - S3Client inputClient, S3Client outputClient, EventBridgeClient eventBridgeClient) { - super(KeyBatchRequestEvent.class); - this.inputClient = inputClient; - this.outputClient = outputClient; - this.eventBridgeClient = eventBridgeClient; - } - - private static boolean isNotEmptyEvent(KeyBatchRequestEvent event) { - return nonNull(event) && nonNull(event.location()); - } - - private static PutEventsRequestEntry constructRequestEntry( - String lastEvaluatedKey, Context context, String location) { - return PutEventsRequestEntry.builder() - .eventBusName(EVENT_BUS) - .detail(new KeyBatchRequestEvent(lastEvaluatedKey, TOPIC, location).toJsonString()) - .detailType(MANDATORY_UNUSED_SUBTOPIC) - .source(EventBasedBatchIndexer.class.getName()) - .resources(context.getInvokedFunctionArn()) - .time(Instant.now()) - .build(); - } - - private static String getStartMarker(KeyBatchRequestEvent input) { - return notEmptyEvent(input) ? input.startMarker() : DEFAULT_START_MARKER; - } - - private static boolean notEmptyEvent(KeyBatchRequestEvent event) { - return nonNull(event) && nonNull(event.startMarker()); - } - - private static ListObjectsV2Request createRequest(String startMarker, String location) { - return ListObjectsV2Request.builder() - .bucket(INPUT_BUCKET) - .prefix(location + DELIMITER) - .delimiter(DELIMITER) - .startAfter(startMarker) - .maxKeys(MAX_KEYS) - .build(); - } - - private static String toKeyString(List values) { - return values.stream().collect(Collectors.joining(System.lineSeparator())); - } - - private static List getKeys(ListObjectsV2Response response) { - return response.contents().stream().map(S3Object::key).toList(); - } - - private static String getLastEvaluatedKey(List keys) { - return keys.getLast(); - } - - @JacocoGenerated - private static EventBridgeClient defaultEventBridgeClient() { - return EventBridgeClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .build(); - } - - @Override - protected Void processInput( - KeyBatchRequestEvent input, - AwsEventBridgeEvent event, - Context context) { - var startMarker = getStartMarker(input); - var location = getLocation(input); - logger.debug(START_MARKER_MESSAGE, startMarker); - var response = inputClient.listObjectsV2(createRequest(startMarker, location)); - var keys = getKeys(response); - writeObject(toKeyString(keys)); - var lastEvaluatedKey = getLastEvaluatedKey(keys); - var eventsResponse = sendEvent(constructRequestEntry(lastEvaluatedKey, context, location)); - logger.debug(eventsResponse.toString()); - return null; - } - - private String getLocation(KeyBatchRequestEvent event) { - return isNotEmptyEvent(event) ? event.location() : RESOURCES; - } - - private PutEventsResponse sendEvent(PutEventsRequestEntry event) { - return eventBridgeClient.putEvents(PutEventsRequest.builder().entries(event).build()); - } - - private void writeObject(String object) { - var request = - PutObjectRequest.builder() - .bucket(OUTPUT_BUCKET) - .key(randomUUID().toString()) - .build(); - outputClient.putObject( - request, RequestBody.fromBytes(object.getBytes(StandardCharsets.UTF_8))); - } + public static final String DEFAULT_BATCH_SIZE = "1000"; + public static final String DELIMITER = "/"; + public static final String DEFAULT_START_MARKER = null; + public static final String START_MARKER_MESSAGE = "Start marker: {}"; + public static final String INPUT_BUCKET = ENVIRONMENT.readEnv("PERSISTED_RESOURCES_BUCKET"); + public static final String OUTPUT_BUCKET = ENVIRONMENT.readEnv("KEY_BATCHES_BUCKET"); + public static final int MAX_KEYS = + Integer.parseInt(ENVIRONMENT.readEnvOpt("BATCH_SIZE").orElse(DEFAULT_BATCH_SIZE)); + private static final Logger logger = LoggerFactory.getLogger(GenerateKeyBatchesHandler.class); + private final S3Client inputClient; + private final S3Client outputClient; + private final EventBridgeClient eventBridgeClient; + + @JacocoGenerated + public GenerateKeyBatchesHandler() { + this(defaultS3Client(), defaultS3Client(), defaultEventBridgeClient()); + } + + public GenerateKeyBatchesHandler( + S3Client inputClient, S3Client outputClient, EventBridgeClient eventBridgeClient) { + super(KeyBatchRequestEvent.class); + this.inputClient = inputClient; + this.outputClient = outputClient; + this.eventBridgeClient = eventBridgeClient; + } + + private static boolean isNotEmptyEvent(KeyBatchRequestEvent event) { + return nonNull(event) && nonNull(event.location()); + } + + private static PutEventsRequestEntry constructRequestEntry( + String lastEvaluatedKey, Context context, String location) { + return PutEventsRequestEntry.builder() + .eventBusName(EVENT_BUS) + .detail(new KeyBatchRequestEvent(lastEvaluatedKey, TOPIC, location).toJsonString()) + .detailType(MANDATORY_UNUSED_SUBTOPIC) + .source(EventBasedBatchIndexer.class.getName()) + .resources(context.getInvokedFunctionArn()) + .time(Instant.now()) + .build(); + } + + private static String getStartMarker(KeyBatchRequestEvent input) { + return notEmptyEvent(input) ? input.startMarker() : DEFAULT_START_MARKER; + } + + private static boolean notEmptyEvent(KeyBatchRequestEvent event) { + return nonNull(event) && nonNull(event.startMarker()); + } + + private static ListObjectsV2Request createRequest(String startMarker, String location) { + return ListObjectsV2Request.builder() + .bucket(INPUT_BUCKET) + .prefix(location + DELIMITER) + .delimiter(DELIMITER) + .startAfter(startMarker) + .maxKeys(MAX_KEYS) + .build(); + } + + private static String toKeyString(List values) { + return values.stream().collect(Collectors.joining(System.lineSeparator())); + } + + private static List getKeys(ListObjectsV2Response response) { + return response.contents().stream().map(S3Object::key).toList(); + } + + private static String getLastEvaluatedKey(List keys) { + return keys.getLast(); + } + + @JacocoGenerated + private static EventBridgeClient defaultEventBridgeClient() { + return EventBridgeClient.builder().httpClientBuilder(UrlConnectionHttpClient.builder()).build(); + } + + @Override + protected Void processInput( + KeyBatchRequestEvent input, + AwsEventBridgeEvent event, + Context context) { + var startMarker = getStartMarker(input); + var location = getLocation(input); + logger.debug(START_MARKER_MESSAGE, startMarker); + var response = inputClient.listObjectsV2(createRequest(startMarker, location)); + var keys = getKeys(response); + writeObject(toKeyString(keys)); + var lastEvaluatedKey = getLastEvaluatedKey(keys); + var eventsResponse = sendEvent(constructRequestEntry(lastEvaluatedKey, context, location)); + logger.debug(eventsResponse.toString()); + return null; + } + + private String getLocation(KeyBatchRequestEvent event) { + return isNotEmptyEvent(event) ? event.location() : RESOURCES; + } + + private PutEventsResponse sendEvent(PutEventsRequestEntry event) { + return eventBridgeClient.putEvents(PutEventsRequest.builder().entries(event).build()); + } + + private void writeObject(String object) { + var request = + PutObjectRequest.builder().bucket(OUTPUT_BUCKET).key(randomUUID().toString()).build(); + outputClient.putObject(request, RequestBody.fromBytes(object.getBytes(StandardCharsets.UTF_8))); + } } diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandler.java b/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandler.java index ab383eea6..3a0ea7992 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandler.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandler.java @@ -1,33 +1,34 @@ package no.unit.nva.indexingclient.keybatch; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.Defaults.ENVIRONMENT; import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.indexingclient.Constants.EVENT_BUS; import static no.unit.nva.indexingclient.Constants.MANDATORY_UNUSED_SUBTOPIC; import static no.unit.nva.indexingclient.Constants.TOPIC; import static no.unit.nva.indexingclient.Constants.defaultS3Client; - import static nva.commons.core.attempt.Try.attempt; -import static java.util.Objects.nonNull; - import com.amazonaws.services.lambda.runtime.Context; - +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; import no.unit.nva.events.handlers.EventHandler; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.indexingclient.AggregationsValidator; import no.unit.nva.indexingclient.IndexingClient; import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.s3.S3Driver; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.Failure; import nva.commons.core.paths.UnixPath; - import org.opensearch.action.bulk.BulkResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.services.eventbridge.EventBridgeClient; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; @@ -36,14 +37,6 @@ import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - public class KeyBasedBatchIndexHandler extends EventHandler { private static final Logger logger = LoggerFactory.getLogger(KeyBasedBatchIndexHandler.class); @@ -121,18 +114,17 @@ protected Void processInput( var batchResponse = fetchSingleBatch(startMarker); if (batchResponse.isTruncated()) { - sendEvent( - constructRequestEntry( - batchResponse.contents().get(0).key(), context, location)); + sendEvent( + constructRequestEntry(batchResponse.contents().getFirst().key(), context, location)); } - var batchKey = batchResponse.contents().get(0).key(); + var batchKey = batchResponse.contents().getFirst().key(); var content = extractContent(batchKey); var indexDocuments = mapToIndexDocuments(content); sendDocumentsToIndexInBatches(indexDocuments); - logger.info(LAST_CONSUMED_BATCH, batchResponse.contents().get(0)); + logger.info(LAST_CONSUMED_BATCH, batchResponse.contents().getFirst()); return null; } diff --git a/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBatchRequestEvent.java b/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBatchRequestEvent.java index 1a1007abc..08ddd915d 100644 --- a/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBatchRequestEvent.java +++ b/batch-index/src/main/java/no/unit/nva/indexingclient/keybatch/KeyBatchRequestEvent.java @@ -4,10 +4,10 @@ import no.unit.nva.events.models.EventBody; public record KeyBatchRequestEvent(String startMarker, String topic, String location) - implements JsonSerializable, EventBody { + implements JsonSerializable, EventBody { - @Override - public String getTopic() { - return topic; - } + @Override + public String getTopic() { + return topic; + } } diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/BatchIndexTest.java b/batch-index/src/test/java/no/unit/nva/indexingclient/BatchIndexTest.java index 29fcd9c97..bf0491037 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/BatchIndexTest.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/BatchIndexTest.java @@ -1,23 +1,19 @@ package no.unit.nva.indexingclient; import static no.unit.nva.indexingclient.TestConstants.RESOURCE_INDEX_NAME; - import static org.mockito.Mockito.mock; import com.amazonaws.services.lambda.runtime.Context; - +import java.util.List; +import java.util.Random; +import java.util.stream.Stream; import no.unit.nva.indexing.testutils.FakeIndexingClient; import no.unit.nva.indexingclient.models.IndexDocument; - import org.opensearch.action.DocWriteRequest.OpType; import org.opensearch.action.bulk.BulkItemResponse; import org.opensearch.action.bulk.BulkItemResponse.Failure; import org.opensearch.action.bulk.BulkResponse; -import java.util.List; -import java.util.Random; -import java.util.stream.Stream; - public class BatchIndexTest { public static final Context CONTEXT = mock(Context.class); diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/EventBasedBatchIndexerTest.java b/batch-index/src/test/java/no/unit/nva/indexingclient/EventBasedBatchIndexerTest.java index afec7a083..77763fcdb 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/EventBasedBatchIndexerTest.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/EventBasedBatchIndexerTest.java @@ -6,9 +6,7 @@ import static no.unit.nva.indexingclient.Constants.NUMBER_OF_FILES_PER_EVENT_ENVIRONMENT_VARIABLE; import static no.unit.nva.testutils.RandomDataGenerator.randomJson; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; @@ -20,7 +18,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; - +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import no.unit.nva.commons.json.JsonUtils; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.identifiers.SortableIdentifier; @@ -29,12 +33,10 @@ import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.s3.S3Driver; import no.unit.nva.stubs.FakeS3Client; - import nva.commons.core.attempt.Try; import nva.commons.core.ioutils.IoUtils; import nva.commons.core.paths.UnixPath; import nva.commons.core.paths.UriWrapper; - import org.apache.logging.log4j.core.test.appender.ListAppender; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -42,14 +44,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - @SuppressWarnings({"PMD.VariableDeclarationUsageDistance"}) public class EventBasedBatchIndexerTest extends BatchIndexTest { diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/ImportDataRequestEventTest.java b/batch-index/src/test/java/no/unit/nva/indexingclient/ImportDataRequestEventTest.java index 5221921c3..30ac59b43 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/ImportDataRequestEventTest.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/ImportDataRequestEventTest.java @@ -22,37 +22,36 @@ public class ImportDataRequestEventTest { - private static final String SOME_S3_LOCATION = "s3://some-bucket/some/path"; - - @Test - public void creatorReturnsValidObjectWhenInputIsNotEmpty() { - ImportDataRequestEvent request = new ImportDataRequestEvent(SOME_S3_LOCATION); - assertThat(request.getS3Location(), is(equalTo(SOME_S3_LOCATION))); - } - - @Test - public void creatorThrowsExceptionWhenInputIsInvalid() { - ObjectNode objectNode = objectMapperNoEmpty.createObjectNode(); - String jsonString = objectNode.toPrettyString(); - Executable action = - () -> objectMapperWithEmpty.readValue(jsonString, ImportDataRequestEvent.class); - ValueInstantiationException exception = - assertThrows(ValueInstantiationException.class, action); - assertThat(exception.getMessage(), containsString(S3_LOCATION_FIELD)); - } - - @Test - public void serializationWithJsonReturnsValidObject() throws JsonProcessingException { - ObjectNode objectNode = objectMapperNoEmpty.createObjectNode(); - String jsonString = objectNode.put(S3_LOCATION_FIELD, SOME_S3_LOCATION).toPrettyString(); - ImportDataRequestEvent deserialized = - objectMapperWithEmpty.readValue(jsonString, ImportDataRequestEvent.class); - assertThat(deserialized.getS3Location(), is(equalTo(SOME_S3_LOCATION))); - } - - @Test - public void getPathReturnsPathWithoutRoot() { - ImportDataRequestEvent request = new ImportDataRequestEvent(SOME_S3_LOCATION); - assertThat(request.getS3Path(), not(startsWith(SLASH))); - } + private static final String SOME_S3_LOCATION = "s3://some-bucket/some/path"; + + @Test + public void creatorReturnsValidObjectWhenInputIsNotEmpty() { + ImportDataRequestEvent request = new ImportDataRequestEvent(SOME_S3_LOCATION); + assertThat(request.getS3Location(), is(equalTo(SOME_S3_LOCATION))); + } + + @Test + public void creatorThrowsExceptionWhenInputIsInvalid() { + ObjectNode objectNode = objectMapperNoEmpty.createObjectNode(); + String jsonString = objectNode.toPrettyString(); + Executable action = + () -> objectMapperWithEmpty.readValue(jsonString, ImportDataRequestEvent.class); + ValueInstantiationException exception = assertThrows(ValueInstantiationException.class, action); + assertThat(exception.getMessage(), containsString(S3_LOCATION_FIELD)); + } + + @Test + public void serializationWithJsonReturnsValidObject() throws JsonProcessingException { + ObjectNode objectNode = objectMapperNoEmpty.createObjectNode(); + String jsonString = objectNode.put(S3_LOCATION_FIELD, SOME_S3_LOCATION).toPrettyString(); + ImportDataRequestEvent deserialized = + objectMapperWithEmpty.readValue(jsonString, ImportDataRequestEvent.class); + assertThat(deserialized.getS3Location(), is(equalTo(SOME_S3_LOCATION))); + } + + @Test + public void getPathReturnsPathWithoutRoot() { + ImportDataRequestEvent request = new ImportDataRequestEvent(SOME_S3_LOCATION); + assertThat(request.getS3Path(), not(startsWith(SLASH))); + } } diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/StartBatchIndexingHandlerTest.java b/batch-index/src/test/java/no/unit/nva/indexingclient/StartBatchIndexingHandlerTest.java index f52f8c550..c40ea1d14 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/StartBatchIndexingHandlerTest.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/StartBatchIndexingHandlerTest.java @@ -1,17 +1,15 @@ package no.unit.nva.indexingclient; import static no.unit.nva.indexingclient.Constants.PERSISTED_RESOURCES_PATH; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class StartBatchIndexingHandlerTest extends BatchIndexTest { diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/StubEventBridgeClient.java b/batch-index/src/test/java/no/unit/nva/indexingclient/StubEventBridgeClient.java index fa18d2215..d97fd2ef1 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/StubEventBridgeClient.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/StubEventBridgeClient.java @@ -1,11 +1,9 @@ package no.unit.nva.indexingclient; import static no.unit.nva.constants.Defaults.objectMapperWithEmpty; - import static nva.commons.core.attempt.Try.attempt; import nva.commons.core.SingletonCollector; - import software.amazon.awssdk.services.eventbridge.EventBridgeClient; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequestEntry; diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/TestConstants.java b/batch-index/src/test/java/no/unit/nva/indexingclient/TestConstants.java index 8bc2be465..74dc17e67 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/TestConstants.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/TestConstants.java @@ -4,8 +4,8 @@ public final class TestConstants { - public static final String RESOURCE_INDEX_NAME = "resources"; + public static final String RESOURCE_INDEX_NAME = "resources"; - @JacocoGenerated - private TestConstants() {} + @JacocoGenerated + private TestConstants() {} } diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandlerTest.java b/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandlerTest.java index 28d42c0d5..2438ed72d 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandlerTest.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/GenerateKeyBatchesHandlerTest.java @@ -3,9 +3,7 @@ import static no.unit.nva.constants.Defaults.objectMapperWithEmpty; import static no.unit.nva.indexingclient.TestConstants.RESOURCE_INDEX_NAME; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.core.Is.is; @@ -13,160 +11,148 @@ import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; - +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.identifiers.SortableIdentifier; import no.unit.nva.s3.S3Driver; import no.unit.nva.stubs.FakeS3Client; - import nva.commons.core.SingletonCollector; import nva.commons.core.ioutils.IoUtils; import nva.commons.core.paths.UnixPath; import nva.commons.core.paths.UriWrapper; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import software.amazon.awssdk.services.eventbridge.EventBridgeClient; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; import software.amazon.awssdk.services.eventbridge.model.PutEventsResponse; import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.net.URI; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - class GenerateKeyBatchesHandlerTest { - private static final String OUTPUT_BUCKET = "outputBucket"; - private static final int SINGLE_BATCH_FILE_SIZE = 10; - private static final int MULTIPLE_BATCH_FILE_SIZE = 1001; - - private ByteArrayOutputStream outputStream; - private FakeS3Client outputClient; - private StubEventBridgeClient eventBridgeClient; - private GenerateKeyBatchesHandler handler; - private S3Driver s3DriverInputBucket; - private S3Driver s3DriverOutputBucket; - - @BeforeEach - void setUp() { - this.outputStream = new ByteArrayOutputStream(); - var inputClient = new FakeS3Client(); - s3DriverInputBucket = new S3Driver(inputClient, "inputBucket"); - outputClient = new FakeS3Client(); - s3DriverOutputBucket = new S3Driver(outputClient, "outputBucket"); - eventBridgeClient = new StubEventBridgeClient(); - handler = new GenerateKeyBatchesHandler(inputClient, outputClient, eventBridgeClient); - } - - @Test - void shouldPersistS3KeysToBatchBucket() throws JsonProcessingException { - final var allFiles = putObjectsInInputBucket(SINGLE_BATCH_FILE_SIZE, RESOURCE_INDEX_NAME); - - handler.handleRequest(eventStream(RESOURCE_INDEX_NAME), outputStream, mock(Context.class)); - - var actual = getPersistedFileFromOutputBucket(); - var expected = allFiles.stream().collect(Collectors.joining(System.lineSeparator())); - - assertThat( - actual.stream().collect(Collectors.joining(System.lineSeparator())), - is(equalTo(expected))); - } - - @Test - void shouldReadGenerateBatchesFromS3LocationProvidedInEventBody() - throws JsonProcessingException { - var location = randomString(); - final var allFiles = putObjectsInInputBucket(SINGLE_BATCH_FILE_SIZE, location); - - handler.handleRequest(eventStream(location), outputStream, mock(Context.class)); - - var actual = getPersistedFileFromOutputBucket(); - var expected = allFiles.stream().collect(Collectors.joining(System.lineSeparator())); - - assertThat( - actual.stream().collect(Collectors.joining(System.lineSeparator())), - is(equalTo(expected))); - } - - @Test - void shouldEmitNewEventWhenS3BucketHasNotBeenTruncated() throws JsonProcessingException { - var s3Objects = putObjectsInInputBucket(MULTIPLE_BATCH_FILE_SIZE, RESOURCE_INDEX_NAME); - - handler.handleRequest(eventStream(RESOURCE_INDEX_NAME), outputStream, mock(Context.class)); - - var startMarker = eventBridgeClient.getLatestEvent().startMarker(); - - var startMarkerForNextIteration = s3Objects.get(s3Objects.size() - 2); - - assertThat(startMarker, is(equalTo(startMarkerForNextIteration))); - } - - private InputStream eventStream(String location) throws JsonProcessingException { - var event = new AwsEventBridgeEvent(); - event.setDetail(new KeyBatchRequestEvent(null, KeyBatchRequestEvent.TOPIC, location)); - var jsonString = objectMapperWithEmpty.writeValueAsString(event); - return IoUtils.stringToStream(jsonString); - } - - private List getPersistedFileFromOutputBucket() { - var request = ListObjectsV2Request.builder().bucket(OUTPUT_BUCKET).maxKeys(10_000).build(); - var content = outputClient.listObjectsV2(request).contents(); - return content.stream() - .map(object -> s3DriverOutputBucket.getFile(UnixPath.of(object.key()))) - .toList(); + private static final String OUTPUT_BUCKET = "outputBucket"; + private static final int SINGLE_BATCH_FILE_SIZE = 10; + private static final int MULTIPLE_BATCH_FILE_SIZE = 1001; + + private ByteArrayOutputStream outputStream; + private FakeS3Client outputClient; + private StubEventBridgeClient eventBridgeClient; + private GenerateKeyBatchesHandler handler; + private S3Driver s3DriverInputBucket; + private S3Driver s3DriverOutputBucket; + + @BeforeEach + void setUp() { + this.outputStream = new ByteArrayOutputStream(); + var inputClient = new FakeS3Client(); + s3DriverInputBucket = new S3Driver(inputClient, "inputBucket"); + outputClient = new FakeS3Client(); + s3DriverOutputBucket = new S3Driver(outputClient, "outputBucket"); + eventBridgeClient = new StubEventBridgeClient(); + handler = new GenerateKeyBatchesHandler(inputClient, outputClient, eventBridgeClient); + } + + @Test + void shouldPersistS3KeysToBatchBucket() throws JsonProcessingException { + final var allFiles = putObjectsInInputBucket(SINGLE_BATCH_FILE_SIZE, RESOURCE_INDEX_NAME); + + handler.handleRequest(eventStream(RESOURCE_INDEX_NAME), outputStream, mock(Context.class)); + + var actual = getPersistedFileFromOutputBucket(); + var expected = allFiles.stream().collect(Collectors.joining(System.lineSeparator())); + + assertThat( + actual.stream().collect(Collectors.joining(System.lineSeparator())), is(equalTo(expected))); + } + + @Test + void shouldReadGenerateBatchesFromS3LocationProvidedInEventBody() throws JsonProcessingException { + var location = randomString(); + final var allFiles = putObjectsInInputBucket(SINGLE_BATCH_FILE_SIZE, location); + + handler.handleRequest(eventStream(location), outputStream, mock(Context.class)); + + var actual = getPersistedFileFromOutputBucket(); + var expected = allFiles.stream().collect(Collectors.joining(System.lineSeparator())); + + assertThat( + actual.stream().collect(Collectors.joining(System.lineSeparator())), is(equalTo(expected))); + } + + @Test + void shouldEmitNewEventWhenS3BucketHasNotBeenTruncated() throws JsonProcessingException { + var s3Objects = putObjectsInInputBucket(MULTIPLE_BATCH_FILE_SIZE, RESOURCE_INDEX_NAME); + + handler.handleRequest(eventStream(RESOURCE_INDEX_NAME), outputStream, mock(Context.class)); + + var startMarker = eventBridgeClient.getLatestEvent().startMarker(); + + var startMarkerForNextIteration = s3Objects.get(s3Objects.size() - 2); + + assertThat(startMarker, is(equalTo(startMarkerForNextIteration))); + } + + private InputStream eventStream(String location) throws JsonProcessingException { + var event = new AwsEventBridgeEvent(); + event.setDetail(new KeyBatchRequestEvent(null, KeyBatchRequestEvent.TOPIC, location)); + var jsonString = objectMapperWithEmpty.writeValueAsString(event); + return IoUtils.stringToStream(jsonString); + } + + private List getPersistedFileFromOutputBucket() { + var request = ListObjectsV2Request.builder().bucket(OUTPUT_BUCKET).maxKeys(10_000).build(); + var content = outputClient.listObjectsV2(request).contents(); + return content.stream() + .map(object -> s3DriverOutputBucket.getFile(UnixPath.of(object.key()))) + .toList(); + } + + private List putObjectsInInputBucket(int numberOfItems, String location) { + return IntStream.range(0, numberOfItems) + .mapToObj(item -> SortableIdentifier.next()) + .map(SortableIdentifier::toString) + .map(key -> insertFileWithKey(key, location)) + .map(UriWrapper::fromUri) + .map(UriWrapper::toS3bucketPath) + .map(UnixPath::toString) + .toList(); + } + + private URI insertFileWithKey(String key, String location) { + return attempt(() -> s3DriverInputBucket.insertFile(UnixPath.of(location, key), randomString())) + .orElseThrow(); + } + + private static class StubEventBridgeClient implements EventBridgeClient { + + private KeyBatchRequestEvent latestEvent; + + public KeyBatchRequestEvent getLatestEvent() { + return latestEvent; } - private List putObjectsInInputBucket(int numberOfItems, String location) { - return IntStream.range(0, numberOfItems) - .mapToObj(item -> SortableIdentifier.next()) - .map(SortableIdentifier::toString) - .map(key -> insertFileWithKey(key, location)) - .map(UriWrapper::fromUri) - .map(UriWrapper::toS3bucketPath) - .map(UnixPath::toString) - .toList(); + public PutEventsResponse putEvents(PutEventsRequest putEventsRequest) { + this.latestEvent = saveContainedEvent(putEventsRequest); + return PutEventsResponse.builder().failedEntryCount(0).build(); } - private URI insertFileWithKey(String key, String location) { - return attempt( - () -> - s3DriverInputBucket.insertFile( - UnixPath.of(location, key), randomString())) - .orElseThrow(); + @Override + public String serviceName() { + return null; } - private static class StubEventBridgeClient implements EventBridgeClient { - - private KeyBatchRequestEvent latestEvent; - - public KeyBatchRequestEvent getLatestEvent() { - return latestEvent; - } - - public PutEventsResponse putEvents(PutEventsRequest putEventsRequest) { - this.latestEvent = saveContainedEvent(putEventsRequest); - return PutEventsResponse.builder().failedEntryCount(0).build(); - } - - @Override - public String serviceName() { - return null; - } - - @Override - public void close() {} + @Override + public void close() {} - private KeyBatchRequestEvent saveContainedEvent(PutEventsRequest putEventsRequest) { - var eventEntry = - putEventsRequest.entries().stream().collect(SingletonCollector.collect()); - return attempt(eventEntry::detail) - .map(json -> objectMapperWithEmpty.readValue(json, KeyBatchRequestEvent.class)) - .orElseThrow(); - } + private KeyBatchRequestEvent saveContainedEvent(PutEventsRequest putEventsRequest) { + var eventEntry = putEventsRequest.entries().stream().collect(SingletonCollector.collect()); + return attempt(eventEntry::detail) + .map(json -> objectMapperWithEmpty.readValue(json, KeyBatchRequestEvent.class)) + .orElseThrow(); } + } } diff --git a/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandlerTest.java b/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandlerTest.java index ecf30aaa1..188058584 100644 --- a/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandlerTest.java +++ b/batch-index/src/test/java/no/unit/nva/indexingclient/keybatch/KeyBasedBatchIndexHandlerTest.java @@ -1,13 +1,12 @@ package no.unit.nva.indexingclient.keybatch; +import static java.util.UUID.randomUUID; import static no.unit.nva.LogAppender.getAppender; import static no.unit.nva.LogAppender.logToString; import static no.unit.nva.constants.Defaults.objectMapperWithEmpty; import static no.unit.nva.indexingclient.TestConstants.RESOURCE_INDEX_NAME; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -18,12 +17,18 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import static java.util.UUID.randomUUID; - import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; - +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.identifiers.SortableIdentifier; import no.unit.nva.indexingclient.IndexingClient; @@ -31,12 +36,10 @@ import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.s3.S3Driver; import no.unit.nva.stubs.FakeS3Client; - import nva.commons.core.SingletonCollector; import nva.commons.core.StringUtils; import nva.commons.core.ioutils.IoUtils; import nva.commons.core.paths.UnixPath; - import org.apache.logging.log4j.core.test.appender.ListAppender; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -45,313 +48,289 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; import org.opensearch.action.bulk.BulkResponse; - import software.amazon.awssdk.services.eventbridge.EventBridgeClient; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; import software.amazon.awssdk.services.eventbridge.model.PutEventsRequestEntry; import software.amazon.awssdk.services.eventbridge.model.PutEventsResponse; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - class KeyBasedBatchIndexHandlerTest { - private static final String LINE_BREAK = "\n"; - private static final String IDENTIFIER = "__IDENTIFIER__"; - private static final String VALID_PUBLICATION = - IoUtils.stringFromResources(Path.of("publication.json")); - private static final String INVALID_PUBLICATION = - IoUtils.stringFromResources(Path.of("invalid_publication.json")); - private static ListAppender appender; - - private ByteArrayOutputStream outputStream; - private S3Driver s3ResourcesDriver; - private S3Driver s3BatchesDriver; - private FakeOpenSearchClient openSearchClient; - private EventBridgeClient eventBridgeClient; - private KeyBasedBatchIndexHandler handler; - - @BeforeAll - public static void initClass() { - appender = getAppender(KeyBasedBatchIndexHandler.class); - } - - private static ArrayList getDocuments( - List expectedDocuments, IndexDocument notExpectedDocument) { - var documents = new ArrayList<>(expectedDocuments); - documents.add(notExpectedDocument); - return documents; + private static final String LINE_BREAK = "\n"; + private static final String IDENTIFIER = "__IDENTIFIER__"; + private static final String VALID_PUBLICATION = + IoUtils.stringFromResources(Path.of("publication.json")); + private static final String INVALID_PUBLICATION = + IoUtils.stringFromResources(Path.of("invalid_publication.json")); + private static ListAppender appender; + + private ByteArrayOutputStream outputStream; + private S3Driver s3ResourcesDriver; + private S3Driver s3BatchesDriver; + private FakeOpenSearchClient openSearchClient; + private EventBridgeClient eventBridgeClient; + private KeyBasedBatchIndexHandler handler; + + @BeforeAll + public static void initClass() { + appender = getAppender(KeyBasedBatchIndexHandler.class); + } + + private static ArrayList getDocuments( + List expectedDocuments, IndexDocument notExpectedDocument) { + var documents = new ArrayList<>(expectedDocuments); + documents.add(notExpectedDocument); + return documents; + } + + private static EventConsumptionAttributes randomConsumptionAttribute() { + return new EventConsumptionAttributes(RESOURCE_INDEX_NAME, SortableIdentifier.next()); + } + + @BeforeEach + public void init() { + outputStream = new ByteArrayOutputStream(); + var s3ResourcesClient = new FakeS3Client(); + s3ResourcesDriver = new S3Driver(s3ResourcesClient, "resources"); + var s3BatchesClient = new FakeS3Client(); + s3BatchesDriver = new S3Driver(s3BatchesClient, "batchesBucket"); + openSearchClient = new FakeOpenSearchClient(); + eventBridgeClient = new StubEventBridgeClient(); + handler = + new KeyBasedBatchIndexHandler( + openSearchClient, s3ResourcesClient, s3BatchesClient, eventBridgeClient); + } + + @Test + void shouldReturnIndexDocumentsWhenIndexingInBatches() throws IOException { + var expectedDocuments = createExpectedDocuments(10); + var batch = + expectedDocuments.stream() + .map(IndexDocument::getDocumentIdentifier) + .collect(Collectors.joining(LINE_BREAK)); + var batchKey = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); + + handler.handleRequest(eventStream(null), outputStream, Mockito.mock(Context.class)); + + var documentsFromIndex = openSearchClient.getIndexedDocuments(); + + assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); + } + + @Test + void shouldSkipEmptyBatches() throws IOException { + var batchKey = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(batchKey), StringUtils.EMPTY_STRING); + + handler.handleRequest(eventStream(null), outputStream, Mockito.mock(Context.class)); + + var documentsFromIndex = openSearchClient.getIndexedDocuments(); + + assertThat(documentsFromIndex, is(emptyIterable())); + } + + @Test + void shouldNotEmitNewEventWhenNoMoreBatchesToRetrieve() throws IOException { + var expectedDocuments = createExpectedDocuments(10); + var batch = + expectedDocuments.stream() + .map(IndexDocument::getDocumentIdentifier) + .collect(Collectors.joining(LINE_BREAK)); + var batchKey = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); + + handler.handleRequest(eventStream(null), outputStream, Mockito.mock(Context.class)); + + var emittedEvent = ((StubEventBridgeClient) eventBridgeClient).getLatestEvent(); + assertThat(emittedEvent, is(nullValue())); + } + + @Test + void shouldEmitNewEventWhenThereAreMoreBatchesToIndex() throws IOException { + var expectedDocuments = createExpectedDocuments(10); + var batch = + expectedDocuments.stream() + .map(IndexDocument::getDocumentIdentifier) + .collect(Collectors.joining(LINE_BREAK)); + var batchKey = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); + var expectedStarMarkerFromEmittedEvent = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(expectedStarMarkerFromEmittedEvent), batch); + var list = new ArrayList(); + list.add(null); + list.add(batchKey); + list.add(expectedStarMarkerFromEmittedEvent); + + for (String s : list) { + handler.handleRequest(eventStream(s), outputStream, Mockito.mock(Context.class)); + + var emittedEvent = ((StubEventBridgeClient) eventBridgeClient).getLatestEvent(); + + assertThat(emittedEvent.startMarker(), is(equalTo(batchKey))); + assertThat(emittedEvent.location(), is(equalTo(RESOURCE_INDEX_NAME))); } - - private static EventConsumptionAttributes randomConsumptionAttribute() { - return new EventConsumptionAttributes(RESOURCE_INDEX_NAME, SortableIdentifier.next()); - } - - @BeforeEach - public void init() { - outputStream = new ByteArrayOutputStream(); - var s3ResourcesClient = new FakeS3Client(); - s3ResourcesDriver = new S3Driver(s3ResourcesClient, "resources"); - var s3BatchesClient = new FakeS3Client(); - s3BatchesDriver = new S3Driver(s3BatchesClient, "batchesBucket"); - openSearchClient = new FakeOpenSearchClient(); - eventBridgeClient = new StubEventBridgeClient(); - handler = - new KeyBasedBatchIndexHandler( - openSearchClient, s3ResourcesClient, s3BatchesClient, eventBridgeClient); - } - - @Test - void shouldReturnIndexDocumentsWhenIndexingInBatches() throws IOException { - var expectedDocuments = createExpectedDocuments(10); - var batch = - expectedDocuments.stream() - .map(IndexDocument::getDocumentIdentifier) - .collect(Collectors.joining(LINE_BREAK)); - var batchKey = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); - - handler.handleRequest(eventStream(null), outputStream, Mockito.mock(Context.class)); - - var documentsFromIndex = openSearchClient.getIndexedDocuments(); - - assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); - } - - @Test - void shouldSkipEmptyBatches() throws IOException { - var batchKey = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(batchKey), StringUtils.EMPTY_STRING); - - handler.handleRequest(eventStream(null), outputStream, Mockito.mock(Context.class)); - - var documentsFromIndex = openSearchClient.getIndexedDocuments(); - - assertThat(documentsFromIndex, is(emptyIterable())); - } - - @Test - void shouldNotEmitNewEventWhenNoMoreBatchesToRetrieve() throws IOException { - var expectedDocuments = createExpectedDocuments(10); - var batch = - expectedDocuments.stream() - .map(IndexDocument::getDocumentIdentifier) - .collect(Collectors.joining(LINE_BREAK)); - var batchKey = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); - - handler.handleRequest(eventStream(null), outputStream, Mockito.mock(Context.class)); - - var emittedEvent = ((StubEventBridgeClient) eventBridgeClient).getLatestEvent(); - assertThat(emittedEvent, is(nullValue())); - } - - @Test - void shouldEmitNewEventWhenThereAreMoreBatchesToIndex() throws IOException { - var expectedDocuments = createExpectedDocuments(10); - var batch = - expectedDocuments.stream() - .map(IndexDocument::getDocumentIdentifier) - .collect(Collectors.joining(LINE_BREAK)); - var batchKey = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); - var expectedStarMarkerFromEmittedEvent = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(expectedStarMarkerFromEmittedEvent), batch); - var list = new ArrayList(); - list.add(null); - list.add(batchKey); - list.add(expectedStarMarkerFromEmittedEvent); - - for (String s : list) { - handler.handleRequest(eventStream(s), outputStream, Mockito.mock(Context.class)); - - var emittedEvent = ((StubEventBridgeClient) eventBridgeClient).getLatestEvent(); - - assertThat(emittedEvent.startMarker(), is(equalTo(batchKey))); - assertThat(emittedEvent.location(), is(equalTo(RESOURCE_INDEX_NAME))); - } + } + + @Test + void shouldRemoveDocumentsThatDoesNotHaveEntityDescription() throws IOException { + var expectedDocuments = createExpectedDocuments(9); + var notExpectedDocument = + createInvalidDocument(INVALID_PUBLICATION.replace("entityDescription", "something")); + getDocuments(expectedDocuments, notExpectedDocument); + var batch = + expectedDocuments.stream() + .map(IndexDocument::getDocumentIdentifier) + .collect(Collectors.joining(LINE_BREAK)); + var batchKey = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); + + handler.handleRequest(eventStream(randomString()), outputStream, Mockito.mock(Context.class)); + + var documentsFromIndex = openSearchClient.getIndexedDocuments(); + + assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); + } + + @Test + void shouldRemoveDocumentsThatDoesNotHaveReference() throws IOException { + var expectedDocuments = createExpectedDocuments(9); + var notExpectedDocument = + createInvalidDocument(INVALID_PUBLICATION.replace("reference", "something")); + getDocuments(expectedDocuments, notExpectedDocument); + var batch = + expectedDocuments.stream() + .map(IndexDocument::getDocumentIdentifier) + .collect(Collectors.joining(LINE_BREAK)); + var batchKey = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); + + handler.handleRequest(eventStream(randomString()), outputStream, Mockito.mock(Context.class)); + + var documentsFromIndex = openSearchClient.getIndexedDocuments(); + + assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); + } + + @ParameterizedTest(name = "Should remove documents that do not have expected field {0}") + @ValueSource( + strings = {"entityDescription", "reference", "publicationContext", "publicationInstance"}) + void shouldRemoveDocumentsThatDoesNotHaveExpectedField(String field) throws IOException { + + var expectedDocuments = createExpectedDocuments(9); + var notExpectedDocument = + createInvalidDocument(INVALID_PUBLICATION.replace(field, "something")); + getDocuments(expectedDocuments, notExpectedDocument); + var batchContent = new ArrayList(); + batchContent.add(notExpectedDocument); + batchContent.addAll(expectedDocuments); + var batch = + batchContent.stream() + .map(IndexDocument::getDocumentIdentifier) + .collect(Collectors.joining(LINE_BREAK)); + var batchKey = randomString(); + s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); + + handler.handleRequest(eventStream(randomString()), outputStream, Mockito.mock(Context.class)); + + var documentsFromIndex = openSearchClient.getIndexedDocuments(); + + assertThat(logToString(appender), containsString("has missing fields")); + assertThat(logToString(appender), containsString(field)); + assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); + assertThat(documentsFromIndex, not(hasItem(notExpectedDocument))); + } + + private InputStream eventStream(String startMarker) throws JsonProcessingException { + var event = new AwsEventBridgeEvent(); + event.setDetail(new KeyBatchRequestEvent(startMarker, randomString(), RESOURCE_INDEX_NAME)); + event.setId(randomString()); + var jsonString = objectMapperWithEmpty.writeValueAsString(event); + return IoUtils.stringToStream(jsonString); + } + + private IndexDocument createInvalidDocument(String value) { + return Stream.of(new IndexDocument(randomConsumptionAttribute(), nodeFromString(value))) + .map(this::insertResourceInPersistedResourcesBucket) + .collect(SingletonCollector.collect()); + } + + private List createExpectedDocuments(int numberOfDocuments) { + return IntStream.range(0, numberOfDocuments) + .mapToObj(i -> new IndexDocument(randomConsumptionAttribute(), randomValidNode())) + .map(this::insertResourceInPersistedResourcesBucket) + .toList(); + } + + private JsonNode randomValidNode() { + return attempt( + () -> + objectMapperWithEmpty.readTree( + VALID_PUBLICATION.replace(IDENTIFIER, randomUUID().toString()))) + .orElseThrow(); + } + + private JsonNode nodeFromString(String value) { + return attempt(() -> objectMapperWithEmpty.readTree(value)).orElseThrow(); + } + + private IndexDocument insertResourceInPersistedResourcesBucket(IndexDocument document) { + attempt( + () -> + s3ResourcesDriver.insertFile( + UnixPath.of(document.getDocumentIdentifier()), document.toJsonString())) + .orElseThrow(); + return document; + } + + private static class FakeOpenSearchClient extends IndexingClient { + + private final List documents; + + public FakeOpenSearchClient() { + super(null, null); + this.documents = new ArrayList<>(); } - @Test - void shouldRemoveDocumentsThatDoesNotHaveEntityDescription() throws IOException { - var expectedDocuments = createExpectedDocuments(9); - var notExpectedDocument = - createInvalidDocument( - INVALID_PUBLICATION.replace("entityDescription", "something")); - getDocuments(expectedDocuments, notExpectedDocument); - var batch = - expectedDocuments.stream() - .map(IndexDocument::getDocumentIdentifier) - .collect(Collectors.joining(LINE_BREAK)); - var batchKey = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); - - handler.handleRequest( - eventStream(randomString()), outputStream, Mockito.mock(Context.class)); - - var documentsFromIndex = openSearchClient.getIndexedDocuments(); - - assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); + @Override + public Stream batchInsert(Stream contents) { + List list = contents.toList(); + documents.addAll(list); + return null; } - @Test - void shouldRemoveDocumentsThatDoesNotHaveReference() throws IOException { - var expectedDocuments = createExpectedDocuments(9); - var notExpectedDocument = - createInvalidDocument(INVALID_PUBLICATION.replace("reference", "something")); - getDocuments(expectedDocuments, notExpectedDocument); - var batch = - expectedDocuments.stream() - .map(IndexDocument::getDocumentIdentifier) - .collect(Collectors.joining(LINE_BREAK)); - var batchKey = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); - - handler.handleRequest( - eventStream(randomString()), outputStream, Mockito.mock(Context.class)); - - var documentsFromIndex = openSearchClient.getIndexedDocuments(); - - assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); + public List getIndexedDocuments() { + return documents; } + } - @ParameterizedTest(name = "Should remove documents that do not have expected field {0}") - @ValueSource( - strings = { - "entityDescription", - "reference", - "publicationContext", - "publicationInstance" - }) - void shouldRemoveDocumentsThatDoesNotHaveExpectedField(String field) throws IOException { - - var expectedDocuments = createExpectedDocuments(9); - var notExpectedDocument = - createInvalidDocument(INVALID_PUBLICATION.replace(field, "something")); - getDocuments(expectedDocuments, notExpectedDocument); - var batchContent = new ArrayList(); - batchContent.add(notExpectedDocument); - batchContent.addAll(expectedDocuments); - var batch = - batchContent.stream() - .map(IndexDocument::getDocumentIdentifier) - .collect(Collectors.joining(LINE_BREAK)); - var batchKey = randomString(); - s3BatchesDriver.insertFile(UnixPath.of(batchKey), batch); - - handler.handleRequest( - eventStream(randomString()), outputStream, Mockito.mock(Context.class)); - - var documentsFromIndex = openSearchClient.getIndexedDocuments(); - - assertThat(logToString(appender), containsString("has missing fields")); - assertThat(logToString(appender), containsString(field)); - assertThat(documentsFromIndex, containsInAnyOrder(expectedDocuments.toArray())); - assertThat(documentsFromIndex, not(hasItem(notExpectedDocument))); - } - - private InputStream eventStream(String startMarker) throws JsonProcessingException { - var event = new AwsEventBridgeEvent(); - event.setDetail(new KeyBatchRequestEvent(startMarker, randomString(), RESOURCE_INDEX_NAME)); - event.setId(randomString()); - var jsonString = objectMapperWithEmpty.writeValueAsString(event); - return IoUtils.stringToStream(jsonString); - } + private static class StubEventBridgeClient implements EventBridgeClient { - private IndexDocument createInvalidDocument(String value) { - return Stream.of(new IndexDocument(randomConsumptionAttribute(), nodeFromString(value))) - .map(this::insertResourceInPersistedResourcesBucket) - .collect(SingletonCollector.collect()); - } + private KeyBatchRequestEvent latestEvent; - private List createExpectedDocuments(int numberOfDocuments) { - return IntStream.range(0, numberOfDocuments) - .mapToObj(i -> new IndexDocument(randomConsumptionAttribute(), randomValidNode())) - .map(this::insertResourceInPersistedResourcesBucket) - .toList(); + public KeyBatchRequestEvent getLatestEvent() { + return latestEvent; } - private JsonNode randomValidNode() { - return attempt( - () -> - objectMapperWithEmpty.readTree( - VALID_PUBLICATION.replace( - IDENTIFIER, randomUUID().toString()))) - .orElseThrow(); + public PutEventsResponse putEvents(PutEventsRequest putEventsRequest) { + this.latestEvent = saveContainedEvent(putEventsRequest); + return PutEventsResponse.builder().failedEntryCount(0).build(); } - private JsonNode nodeFromString(String value) { - return attempt(() -> objectMapperWithEmpty.readTree(value)).orElseThrow(); + @Override + public String serviceName() { + return null; } - private IndexDocument insertResourceInPersistedResourcesBucket(IndexDocument document) { - attempt( - () -> - s3ResourcesDriver.insertFile( - UnixPath.of(document.getDocumentIdentifier()), - document.toJsonString())) - .orElseThrow(); - return document; - } - - private static class FakeOpenSearchClient extends IndexingClient { - - private final List documents; - - public FakeOpenSearchClient() { - super(null, null); - this.documents = new ArrayList<>(); - } - - @Override - public Stream batchInsert(Stream contents) { - List list = contents.toList(); - documents.addAll(list); - return null; - } - - public List getIndexedDocuments() { - return documents; - } - } + @Override + public void close() {} - private static class StubEventBridgeClient implements EventBridgeClient { - - private KeyBatchRequestEvent latestEvent; - - public KeyBatchRequestEvent getLatestEvent() { - return latestEvent; - } - - public PutEventsResponse putEvents(PutEventsRequest putEventsRequest) { - this.latestEvent = saveContainedEvent(putEventsRequest); - return PutEventsResponse.builder().failedEntryCount(0).build(); - } - - @Override - public String serviceName() { - return null; - } - - @Override - public void close() {} - - private KeyBatchRequestEvent saveContainedEvent(PutEventsRequest putEventsRequest) { - PutEventsRequestEntry eventEntry = - putEventsRequest.entries().stream().collect(SingletonCollector.collect()); - return attempt(eventEntry::detail) - .map( - jsonString -> - objectMapperWithEmpty.readValue( - jsonString, KeyBatchRequestEvent.class)) - .orElseThrow(); - } + private KeyBatchRequestEvent saveContainedEvent(PutEventsRequest putEventsRequest) { + PutEventsRequestEntry eventEntry = + putEventsRequest.entries().stream().collect(SingletonCollector.collect()); + return attempt(eventEntry::detail) + .map( + jsonString -> objectMapperWithEmpty.readValue(jsonString, KeyBatchRequestEvent.class)) + .orElseThrow(); } + } } diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandler.java index 55204563c..8e529c2bc 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandler.java @@ -1,15 +1,12 @@ package no.unit.nva.indexing.handlers; import com.amazonaws.services.lambda.runtime.Context; - import no.unit.nva.events.handlers.DestinationsEventBridgeEventHandler; import no.unit.nva.events.models.AwsEventBridgeDetail; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.indexing.model.DeleteImportCandidateEvent; import no.unit.nva.indexingclient.IndexingClient; - import nva.commons.core.JacocoGenerated; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandler.java index 279b23846..5922bbf33 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandler.java @@ -1,16 +1,12 @@ package no.unit.nva.indexing.handlers; import static no.unit.nva.constants.Words.IMPORT_CANDIDATES_INDEX; - import static nva.commons.core.attempt.Try.attempt; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; - import no.unit.nva.indexingclient.IndexingClient; - import nva.commons.core.JacocoGenerated; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteIndicesHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteIndicesHandler.java index 120995868..b9dec20d7 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteIndicesHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteIndicesHandler.java @@ -5,53 +5,49 @@ import static no.unit.nva.constants.Words.PUBLISHING_REQUESTS_INDEX; import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.constants.Words.TICKETS; - import static nva.commons.core.attempt.Try.attempt; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; - import no.unit.nva.indexingclient.IndexingClient; - import nva.commons.core.JacocoGenerated; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DeleteIndicesHandler implements RequestHandler { - private static final Logger logger = LoggerFactory.getLogger(DeleteIndicesHandler.class); - private static final String FINISHED = "FINISHED"; - private final IndexingClient indexingClient; - - @JacocoGenerated - public DeleteIndicesHandler() { - this(IndexingClient.defaultIndexingClient()); - } - - public DeleteIndicesHandler(IndexingClient indexingClient) { - this.indexingClient = indexingClient; - } - - @Override - public String handleRequest(Object input, Context context) { - - attempt(() -> indexingClient.deleteIndex(RESOURCES)) - .orElse(fail -> logError(fail.getException())); - attempt(() -> indexingClient.deleteIndex(DOIREQUESTS_INDEX)) - .orElse(fail -> logError(fail.getException())); - attempt(() -> indexingClient.deleteIndex(MESSAGES_INDEX)) - .orElse(fail -> logError(fail.getException())); - attempt(() -> indexingClient.deleteIndex(PUBLISHING_REQUESTS_INDEX)) - .orElse(fail -> logError(fail.getException())); - attempt(() -> indexingClient.deleteIndex(TICKETS)) - .orElse(fail -> logError(fail.getException())); - - return FINISHED; - } - - private Void logError(Exception exception) { - logger.warn("Index deletion failed", exception); - return null; - } + private static final Logger logger = LoggerFactory.getLogger(DeleteIndicesHandler.class); + private static final String FINISHED = "FINISHED"; + private final IndexingClient indexingClient; + + @JacocoGenerated + public DeleteIndicesHandler() { + this(IndexingClient.defaultIndexingClient()); + } + + public DeleteIndicesHandler(IndexingClient indexingClient) { + this.indexingClient = indexingClient; + } + + @Override + public String handleRequest(Object input, Context context) { + + attempt(() -> indexingClient.deleteIndex(RESOURCES)) + .orElse(fail -> logError(fail.getException())); + attempt(() -> indexingClient.deleteIndex(DOIREQUESTS_INDEX)) + .orElse(fail -> logError(fail.getException())); + attempt(() -> indexingClient.deleteIndex(MESSAGES_INDEX)) + .orElse(fail -> logError(fail.getException())); + attempt(() -> indexingClient.deleteIndex(PUBLISHING_REQUESTS_INDEX)) + .orElse(fail -> logError(fail.getException())); + attempt(() -> indexingClient.deleteIndex(TICKETS)) + .orElse(fail -> logError(fail.getException())); + + return FINISHED; + } + + private Void logError(Exception exception) { + logger.warn("Index deletion failed", exception); + return null; + } } diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceEvent.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceEvent.java index 8f6ee5f19..f54b54fce 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceEvent.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceEvent.java @@ -2,14 +2,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; - +import java.util.Objects; import no.unit.nva.commons.json.JsonSerializable; import no.unit.nva.identifiers.SortableIdentifier; - import nva.commons.core.JacocoGenerated; -import java.util.Objects; - public class DeleteResourceEvent implements JsonSerializable { public static final String EVENT_TOPIC = "PublicationService.ExpandedEntry.Deleted"; diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandler.java index 860c0a422..272602a71 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandler.java @@ -1,51 +1,48 @@ package no.unit.nva.indexing.handlers; import com.amazonaws.services.lambda.runtime.Context; - import no.unit.nva.events.handlers.DestinationsEventBridgeEventHandler; import no.unit.nva.events.models.AwsEventBridgeDetail; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.indexingclient.IndexingClient; - import nva.commons.core.JacocoGenerated; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DeleteResourceFromIndexHandler - extends DestinationsEventBridgeEventHandler { - - private static final Logger logger = - LoggerFactory.getLogger(DeleteResourceFromIndexHandler.class); - - private final IndexingClient indexingClient; - - @JacocoGenerated - public DeleteResourceFromIndexHandler() { - this(IndexingClient.defaultIndexingClient()); + extends DestinationsEventBridgeEventHandler { + + private static final Logger logger = + LoggerFactory.getLogger(DeleteResourceFromIndexHandler.class); + + private final IndexingClient indexingClient; + + @JacocoGenerated + public DeleteResourceFromIndexHandler() { + this(IndexingClient.defaultIndexingClient()); + } + + public DeleteResourceFromIndexHandler(IndexingClient indexingClient) { + super(DeleteResourceEvent.class); + this.indexingClient = indexingClient; + } + + @Override + protected Void processInputPayload( + DeleteResourceEvent input, + AwsEventBridgeEvent> event, + Context context) { + try { + indexingClient.removeDocumentFromResourcesIndex(input.getIdentifier().toString()); + } catch (Exception e) { + logError(e); + throw new RuntimeException(e); } - public DeleteResourceFromIndexHandler(IndexingClient indexingClient) { - super(DeleteResourceEvent.class); - this.indexingClient = indexingClient; - } + return null; + } - @Override - protected Void processInputPayload( - DeleteResourceEvent input, - AwsEventBridgeEvent> event, - Context context) { - try { - indexingClient.removeDocumentFromResourcesIndex(input.getIdentifier().toString()); - } catch (Exception e) { - logError(e); - throw new RuntimeException(e); - } - - return null; - } - - private void logError(Exception exception) { - logger.warn("Removing document failed", exception); - } + private void logError(Exception exception) { + logger.warn("Removing document failed", exception); + } } diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandler.java index fbf2c6a7d..02a7b98f4 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandler.java @@ -1,25 +1,20 @@ package no.unit.nva.indexing.handlers; import static no.unit.nva.constants.Words.IMPORT_CANDIDATES_INDEX; - import static nva.commons.core.attempt.Try.attempt; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; - +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicBoolean; import no.unit.nva.indexing.model.IndexRequest; import no.unit.nva.indexingclient.IndexingClient; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.Failure; import nva.commons.core.ioutils.IoUtils; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicBoolean; - public class ImportCandidateInitHandler implements RequestHandler { public static final String SUCCESS = "SUCCESS"; diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandler.java index d372383d9..3fa33d34c 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandler.java @@ -3,7 +3,6 @@ import static nva.commons.core.attempt.Try.attempt; import com.amazonaws.services.lambda.runtime.Context; - import no.unit.nva.events.handlers.DestinationsEventBridgeEventHandler; import no.unit.nva.events.models.AwsEventBridgeDetail; import no.unit.nva.events.models.AwsEventBridgeEvent; @@ -11,7 +10,6 @@ import no.unit.nva.indexingclient.IndexingClient; import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.s3.S3Driver; - import nva.commons.core.Environment; import nva.commons.core.JacocoGenerated; import nva.commons.core.paths.UnixPath; diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexResourceHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexResourceHandler.java index 57836ea6b..b8aae3038 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexResourceHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/IndexResourceHandler.java @@ -1,11 +1,9 @@ package no.unit.nva.indexing.handlers; import static no.unit.nva.constants.Defaults.ENVIRONMENT; - import static nva.commons.core.attempt.Try.attempt; import com.amazonaws.services.lambda.runtime.Context; - import no.unit.nva.events.handlers.DestinationsEventBridgeEventHandler; import no.unit.nva.events.models.AwsEventBridgeDetail; import no.unit.nva.events.models.AwsEventBridgeEvent; @@ -16,12 +14,10 @@ import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.indexingclient.models.QueueClient; import no.unit.nva.s3.S3Driver; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.Failure; import nva.commons.core.paths.UnixPath; import nva.commons.core.paths.UriWrapper; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/InitHandler.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/InitHandler.java index 84f0148c6..fc85ef057 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/InitHandler.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/handlers/InitHandler.java @@ -6,77 +6,70 @@ import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.constants.Words.TICKETS; import static no.unit.nva.indexingclient.IndexingClient.defaultIndexingClient; - import static nva.commons.core.attempt.Try.attempt; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; - +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import no.unit.nva.indexing.model.IndexRequest; import no.unit.nva.indexingclient.IndexingClient; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.Failure; import nva.commons.core.ioutils.IoUtils; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.Path; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - public class InitHandler implements RequestHandler { - public static final String SUCCESS = "SUCCESS"; - public static final String FAILED = "FAILED. See logs"; - public static final String TICKET_MAPPINGS = - IoUtils.stringFromResources(Path.of("ticket_mappings.json")); - private static final String RESOURCE_MAPPINGS = - IoUtils.stringFromResources(Path.of("resource_mappings.json")); - private static final String RESOURCE_SETTINGS = - IoUtils.stringFromResources(Path.of("resource_settings.json")); - - private static final List INDEXES = - List.of( - new IndexRequest(RESOURCES, RESOURCE_MAPPINGS, RESOURCE_SETTINGS), - new IndexRequest(DOIREQUESTS_INDEX), - new IndexRequest(MESSAGES_INDEX), - new IndexRequest(TICKETS, TICKET_MAPPINGS), - new IndexRequest(PUBLISHING_REQUESTS_INDEX)); - private static final Logger logger = LoggerFactory.getLogger(InitHandler.class); - private final IndexingClient indexingClient; - - @JacocoGenerated - public InitHandler() { - this(defaultIndexingClient()); - } - - public InitHandler(IndexingClient indexingClient) { - this.indexingClient = indexingClient; - } - - @Override - public String handleRequest(Object input, Context context) { - - var failState = new AtomicBoolean(false); - - INDEXES.forEach( - request -> - attempt( - () -> - indexingClient.createIndex( - request.getName(), - request.getMappings(), - request.getSettings())) - .orElse(fail -> handleFailure(failState, fail))); - - return failState.get() ? FAILED : SUCCESS; - } - - private Void handleFailure(AtomicBoolean failState, Failure failure) { - failState.set(true); - logger.warn("Index creation failed", failure.getException()); - return null; - } + public static final String SUCCESS = "SUCCESS"; + public static final String FAILED = "FAILED. See logs"; + public static final String TICKET_MAPPINGS = + IoUtils.stringFromResources(Path.of("ticket_mappings.json")); + private static final String RESOURCE_MAPPINGS = + IoUtils.stringFromResources(Path.of("resource_mappings.json")); + private static final String RESOURCE_SETTINGS = + IoUtils.stringFromResources(Path.of("resource_settings.json")); + + private static final List INDEXES = + List.of( + new IndexRequest(RESOURCES, RESOURCE_MAPPINGS, RESOURCE_SETTINGS), + new IndexRequest(DOIREQUESTS_INDEX), + new IndexRequest(MESSAGES_INDEX), + new IndexRequest(TICKETS, TICKET_MAPPINGS), + new IndexRequest(PUBLISHING_REQUESTS_INDEX)); + private static final Logger logger = LoggerFactory.getLogger(InitHandler.class); + private final IndexingClient indexingClient; + + @JacocoGenerated + public InitHandler() { + this(defaultIndexingClient()); + } + + public InitHandler(IndexingClient indexingClient) { + this.indexingClient = indexingClient; + } + + @Override + public String handleRequest(Object input, Context context) { + + var failState = new AtomicBoolean(false); + + INDEXES.forEach( + request -> + attempt( + () -> + indexingClient.createIndex( + request.getName(), request.getMappings(), request.getSettings())) + .orElse(fail -> handleFailure(failState, fail))); + + return failState.get() ? FAILED : SUCCESS; + } + + private Void handleFailure(AtomicBoolean failState, Failure failure) { + failState.set(true); + logger.warn("Index creation failed", failure.getException()); + return null; + } } diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/model/DeleteImportCandidateEvent.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/model/DeleteImportCandidateEvent.java index c33553faf..2d14a06fe 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/model/DeleteImportCandidateEvent.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/model/DeleteImportCandidateEvent.java @@ -7,17 +7,16 @@ import no.unit.nva.identifiers.SortableIdentifier; public record DeleteImportCandidateEvent(String topic, SortableIdentifier identifier) - implements JsonSerializable { + implements JsonSerializable { - public static final String EVENT_TOPIC = "ImportCandidates.ExpandedEntry.Deleted"; - public static final String TOPIC = "topic"; - public static final String IDENTIFIER = "identifier"; + public static final String EVENT_TOPIC = "ImportCandidates.ExpandedEntry.Deleted"; + public static final String TOPIC = "topic"; + public static final String IDENTIFIER = "identifier"; - @JsonCreator - public DeleteImportCandidateEvent( - @JsonProperty(TOPIC) String topic, - @JsonProperty(IDENTIFIER) SortableIdentifier identifier) { - this.topic = topic; - this.identifier = identifier; - } + @JsonCreator + public DeleteImportCandidateEvent( + @JsonProperty(TOPIC) String topic, @JsonProperty(IDENTIFIER) SortableIdentifier identifier) { + this.topic = topic; + this.identifier = identifier; + } } diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/model/IndexRequest.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/model/IndexRequest.java index 222649da9..dc65c6d13 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/model/IndexRequest.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/model/IndexRequest.java @@ -3,11 +3,9 @@ import static nva.commons.core.attempt.Try.attempt; import com.fasterxml.jackson.core.type.TypeReference; - -import no.unit.nva.commons.json.JsonUtils; - import java.util.Collections; import java.util.Map; +import no.unit.nva.commons.json.JsonUtils; public class IndexRequest { private final String name; diff --git a/indexing-handlers/src/main/java/no/unit/nva/indexing/utils/RecoveryEntry.java b/indexing-handlers/src/main/java/no/unit/nva/indexing/utils/RecoveryEntry.java index 8de354d2b..47528c853 100644 --- a/indexing-handlers/src/main/java/no/unit/nva/indexing/utils/RecoveryEntry.java +++ b/indexing-handlers/src/main/java/no/unit/nva/indexing/utils/RecoveryEntry.java @@ -1,17 +1,14 @@ package no.unit.nva.indexing.utils; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Map; import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.indexingclient.models.QueueClient; - import nva.commons.core.Environment; - import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; import software.amazon.awssdk.services.sqs.model.SendMessageRequest; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Map; - public final class RecoveryEntry { public static final String DATA_TYPE_STRING = "String"; diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandlerTest.java index c6d66b49d..484d51a6f 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateFromIndexHandlerTest.java @@ -3,9 +3,7 @@ import static no.unit.nva.constants.Defaults.objectMapperWithEmpty; import static no.unit.nva.constants.Words.IMPORT_CANDIDATES_INDEX; import static no.unit.nva.testutils.RandomDataGenerator.randomJson; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.not; import static org.hamcrest.collection.IsIterableContainingInOrder.contains; @@ -14,7 +12,11 @@ import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; import no.unit.nva.events.models.AwsEventBridgeDetail; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.identifiers.SortableIdentifier; @@ -22,17 +24,10 @@ import no.unit.nva.indexing.testutils.FakeIndexingClient; import no.unit.nva.indexingclient.models.EventConsumptionAttributes; import no.unit.nva.indexingclient.models.IndexDocument; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Set; - public class DeleteImportCandidateFromIndexHandlerTest { public static final String SOMETHING_BAD_HAPPENED = "Something bad happened"; diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandlerTest.java index 6096b2438..3b5192df6 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteImportCandidateIndexHandlerTest.java @@ -20,41 +20,41 @@ public class DeleteImportCandidateIndexHandlerTest { - private static ListAppender appender; - - @BeforeAll - public static void initClass() { - appender = getAppender(DeleteIndicesHandler.class); - } - - @Test - void shouldDeleteIndicesWhenFunctionIsInvoked() { - final var buffer = new ArrayList(); - var indexingClient = - new IndexingClient(null, null) { - @Override - public Void deleteIndex(String indexName) { - buffer.add(indexName); - return null; - } - }; - var handler = new DeleteImportCandidateIndexHandler(indexingClient); - handler.handleRequest(null, new FakeContext()); - assertThat(buffer, hasItem(IMPORT_CANDIDATES_INDEX)); - } - - @Test - void shouldLogWarningWhenIndexDeletionFails() { - var expectedMessage = randomString(); - var indexingClient = - new IndexingClient(null, null) { - @Override - public Void deleteIndex(String indexName) { - throw new RuntimeException(expectedMessage); - } - }; - var handler = new DeleteImportCandidateIndexHandler(indexingClient); - handler.handleRequest(null, new FakeContext()); - assertThat(logToString(appender), containsString(expectedMessage)); - } + private static ListAppender appender; + + @BeforeAll + public static void initClass() { + appender = getAppender(DeleteIndicesHandler.class); + } + + @Test + void shouldDeleteIndicesWhenFunctionIsInvoked() { + final var buffer = new ArrayList(); + var indexingClient = + new IndexingClient(null, null) { + @Override + public Void deleteIndex(String indexName) { + buffer.add(indexName); + return null; + } + }; + var handler = new DeleteImportCandidateIndexHandler(indexingClient); + handler.handleRequest(null, new FakeContext()); + assertThat(buffer, hasItem(IMPORT_CANDIDATES_INDEX)); + } + + @Test + void shouldLogWarningWhenIndexDeletionFails() { + var expectedMessage = randomString(); + var indexingClient = + new IndexingClient(null, null) { + @Override + public Void deleteIndex(String indexName) { + throw new RuntimeException(expectedMessage); + } + }; + var handler = new DeleteImportCandidateIndexHandler(indexingClient); + handler.handleRequest(null, new FakeContext()); + assertThat(logToString(appender), containsString(expectedMessage)); + } } diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteIndicesHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteIndicesHandlerTest.java index 6aef761ce..cfb619e23 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteIndicesHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteIndicesHandlerTest.java @@ -8,66 +8,58 @@ import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.constants.Words.TICKETS; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.core.StringContains.containsString; +import java.util.ArrayList; +import java.util.List; import no.unit.nva.indexingclient.IndexingClient; import no.unit.nva.stubs.FakeContext; - import org.apache.logging.log4j.core.test.appender.ListAppender; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; - class DeleteIndicesHandlerTest { - private static final List ALL_INDICES = - List.of( - RESOURCES, - DOIREQUESTS_INDEX, - MESSAGES_INDEX, - TICKETS, - PUBLISHING_REQUESTS_INDEX); + private static final List ALL_INDICES = + List.of(RESOURCES, DOIREQUESTS_INDEX, MESSAGES_INDEX, TICKETS, PUBLISHING_REQUESTS_INDEX); - private static ListAppender appender; + private static ListAppender appender; - @BeforeAll - public static void initClass() { - appender = getAppender(DeleteIndicesHandler.class); - } + @BeforeAll + public static void initClass() { + appender = getAppender(DeleteIndicesHandler.class); + } - @Test - void shouldDeleteIndicesWhenFunctionIsInvoked() { - final var buffer = new ArrayList(); - var indexingClient = - new IndexingClient(null, null) { - @Override - public Void deleteIndex(String indexName) { - buffer.add(indexName); - return null; - } - }; - var handler = new DeleteIndicesHandler(indexingClient); - handler.handleRequest(null, new FakeContext()); - assertThat(buffer, containsInAnyOrder(ALL_INDICES.toArray(String[]::new))); - } + @Test + void shouldDeleteIndicesWhenFunctionIsInvoked() { + final var buffer = new ArrayList(); + var indexingClient = + new IndexingClient(null, null) { + @Override + public Void deleteIndex(String indexName) { + buffer.add(indexName); + return null; + } + }; + var handler = new DeleteIndicesHandler(indexingClient); + handler.handleRequest(null, new FakeContext()); + assertThat(buffer, containsInAnyOrder(ALL_INDICES.toArray(String[]::new))); + } - @Test - void shouldLogWarningWhenIndexDeletionFails() { - var expectedMessage = randomString(); - var indexingClient = - new IndexingClient(null, null) { - @Override - public Void deleteIndex(String indexName) { - throw new RuntimeException(expectedMessage); - } - }; - var handler = new DeleteIndicesHandler(indexingClient); - handler.handleRequest(null, new FakeContext()); - assertThat(logToString(appender), containsString(expectedMessage)); - } + @Test + void shouldLogWarningWhenIndexDeletionFails() { + var expectedMessage = randomString(); + var indexingClient = + new IndexingClient(null, null) { + @Override + public Void deleteIndex(String indexName) { + throw new RuntimeException(expectedMessage); + } + }; + var handler = new DeleteIndicesHandler(indexingClient); + handler.handleRequest(null, new FakeContext()); + assertThat(logToString(appender), containsString(expectedMessage)); + } } diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandlerTest.java index fbad7bd85..f687f29a0 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/DeleteResourceFromIndexHandlerTest.java @@ -39,85 +39,83 @@ public class DeleteResourceFromIndexHandlerTest { - public static final String RESOURCES_INDEX = "resource"; - - public static final String SOMETHING_BAD_HAPPENED = "Something bad happened"; - private static final Context CONTEXT = Mockito.mock(Context.class); - private static ListAppender appender; - private FakeIndexingClient indexingClient; - private ByteArrayOutputStream output; - private DeleteResourceFromIndexHandler handler; - - @BeforeAll - public static void initClass() { - appender = getAppender(DeleteResourceFromIndexHandler.class); + public static final String RESOURCES_INDEX = "resource"; + + public static final String SOMETHING_BAD_HAPPENED = "Something bad happened"; + private static final Context CONTEXT = Mockito.mock(Context.class); + private static ListAppender appender; + private FakeIndexingClient indexingClient; + private ByteArrayOutputStream output; + private DeleteResourceFromIndexHandler handler; + + @BeforeAll + public static void initClass() { + appender = getAppender(DeleteResourceFromIndexHandler.class); + } + + private static IndexDocument createSampleResource(SortableIdentifier identifierProvider) { + String randomJson = randomJson(); + ObjectNode objectNode = + attempt(() -> (ObjectNode) objectMapperWithEmpty.readTree(randomJson)).orElseThrow(); + EventConsumptionAttributes metadata = + new EventConsumptionAttributes(RESOURCES_INDEX, identifierProvider); + return new IndexDocument(metadata, objectNode); + } + + @BeforeEach + void init() { + indexingClient = new FakeIndexingClient(); + handler = new DeleteResourceFromIndexHandler(indexingClient); + output = new ByteArrayOutputStream(); + } + + @Test + void shouldThrowRuntimeExceptionAndLogErrorWhenIndexingClientIsThrowingException() + throws IOException { + indexingClient = new FakeIndexingClientThrowingException(); + handler = new DeleteResourceFromIndexHandler(indexingClient); + try (var eventReference = createEventBridgeEvent(SortableIdentifier.next())) { + assertThrows( + RuntimeException.class, () -> handler.handleRequest(eventReference, output, CONTEXT)); } - - private static IndexDocument createSampleResource(SortableIdentifier identifierProvider) { - String randomJson = randomJson(); - ObjectNode objectNode = - attempt(() -> (ObjectNode) objectMapperWithEmpty.readTree(randomJson)) - .orElseThrow(); - EventConsumptionAttributes metadata = - new EventConsumptionAttributes(RESOURCES_INDEX, identifierProvider); - return new IndexDocument(metadata, objectNode); - } - - @BeforeEach - void init() { - indexingClient = new FakeIndexingClient(); - handler = new DeleteResourceFromIndexHandler(indexingClient); - output = new ByteArrayOutputStream(); - } - - @Test - void shouldThrowRuntimeExceptionAndLogErrorWhenIndexingClientIsThrowingException() - throws IOException { - indexingClient = new FakeIndexingClientThrowingException(); - handler = new DeleteResourceFromIndexHandler(indexingClient); - try (var eventReference = createEventBridgeEvent(SortableIdentifier.next())) { - assertThrows( - RuntimeException.class, - () -> handler.handleRequest(eventReference, output, CONTEXT)); - } - assertThat(logToString(appender), containsString(SOMETHING_BAD_HAPPENED)); - } - - @Test - void shouldRemoveDocumentFromSearchIndexClient() throws IOException { - var resourceIdentifier = SortableIdentifier.next(); - var sampleDocument = createSampleResorce(resourceIdentifier); - indexingClient.addDocumentToIndex(sampleDocument); - var eventReference = createEventBridgeEvent(resourceIdentifier); - handler.handleRequest(eventReference, output, CONTEXT); - Set allIndexedDocuments = indexingClient.listAllDocuments(RESOURCES_INDEX); - assertThat(allIndexedDocuments, not(contains(sampleDocument.resource()))); - } - - private IndexDocument createSampleResorce(SortableIdentifier resourceIdentifier) { - return createSampleResource(resourceIdentifier); - } - - private InputStream createEventBridgeEvent(SortableIdentifier resourceIdentifier) - throws IOException { - DeleteResourceEvent deleteResourceEvent = - new DeleteResourceEvent(DeleteImportCandidateEvent.EVENT_TOPIC, resourceIdentifier); - - AwsEventBridgeDetail detail = new AwsEventBridgeDetail<>(); - detail.setResponsePayload(deleteResourceEvent); - - AwsEventBridgeEvent> event = - new AwsEventBridgeEvent<>(); - event.setDetail(detail); - - return new ByteArrayInputStream(objectMapperWithEmpty.writeValueAsBytes(event)); - } - - static class FakeIndexingClientThrowingException extends FakeIndexingClient { - - @Override - public void removeDocumentFromResourcesIndex(String identifier) throws IOException { - throw new IOException(SOMETHING_BAD_HAPPENED); - } + assertThat(logToString(appender), containsString(SOMETHING_BAD_HAPPENED)); + } + + @Test + void shouldRemoveDocumentFromSearchIndexClient() throws IOException { + var resourceIdentifier = SortableIdentifier.next(); + var sampleDocument = createSampleResorce(resourceIdentifier); + indexingClient.addDocumentToIndex(sampleDocument); + var eventReference = createEventBridgeEvent(resourceIdentifier); + handler.handleRequest(eventReference, output, CONTEXT); + Set allIndexedDocuments = indexingClient.listAllDocuments(RESOURCES_INDEX); + assertThat(allIndexedDocuments, not(contains(sampleDocument.resource()))); + } + + private IndexDocument createSampleResorce(SortableIdentifier resourceIdentifier) { + return createSampleResource(resourceIdentifier); + } + + private InputStream createEventBridgeEvent(SortableIdentifier resourceIdentifier) + throws IOException { + DeleteResourceEvent deleteResourceEvent = + new DeleteResourceEvent(DeleteImportCandidateEvent.EVENT_TOPIC, resourceIdentifier); + + AwsEventBridgeDetail detail = new AwsEventBridgeDetail<>(); + detail.setResponsePayload(deleteResourceEvent); + + AwsEventBridgeEvent> event = + new AwsEventBridgeEvent<>(); + event.setDetail(detail); + + return new ByteArrayInputStream(objectMapperWithEmpty.writeValueAsBytes(event)); + } + + static class FakeIndexingClientThrowingException extends FakeIndexingClient { + + @Override + public void removeDocumentFromResourcesIndex(String identifier) throws IOException { + throw new IOException(SOMETHING_BAD_HAPPENED); } + } } diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandlerTest.java index c08ce1022..fe2313d7a 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/ImportCandidateInitHandlerTest.java @@ -5,7 +5,6 @@ import static no.unit.nva.indexing.handlers.InitHandler.FAILED; import static no.unit.nva.indexing.handlers.InitHandler.SUCCESS; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringContains.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -15,17 +14,14 @@ import static org.mockito.Mockito.when; import com.amazonaws.services.lambda.runtime.Context; - +import java.io.IOException; import no.unit.nva.indexingclient.IndexingClient; - import org.apache.logging.log4j.core.test.appender.ListAppender; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import java.io.IOException; - public class ImportCandidateInitHandlerTest { private static ListAppender appender; @@ -49,7 +45,7 @@ void init() { void shouldNotThrowExceptionIfIndicesClientDoesNotThrowException() throws IOException { doNothing().when(indexingClient).createIndex(any(String.class)); var response = initHandler.handleRequest(null, context); - assertEquals(response, SUCCESS); + assertEquals(SUCCESS, response); } @Test diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandlerTest.java index 20b381c78..80ffedc23 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexImportCandidateHandlerTest.java @@ -6,9 +6,7 @@ import static no.unit.nva.constants.Words.IMPORT_CANDIDATES_INDEX; import static no.unit.nva.testutils.RandomDataGenerator.randomJson; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.collection.IsIterableContainingInOrder.contains; @@ -17,7 +15,11 @@ import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; import no.unit.nva.events.models.AwsEventBridgeDetail; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.events.models.EventReference; @@ -28,21 +30,12 @@ import no.unit.nva.s3.S3Driver; import no.unit.nva.stubs.FakeS3Client; import no.unit.nva.testutils.RandomDataGenerator; - import nva.commons.core.paths.UriWrapper; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; - import software.amazon.awssdk.services.s3.model.NoSuchKeyException; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; - public class IndexImportCandidateHandlerTest { public static final IndexDocument SAMPLE_RESOURCE = diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexResourceHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexResourceHandlerTest.java index ccea8efc6..10ea8824d 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexResourceHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/IndexResourceHandlerTest.java @@ -7,9 +7,7 @@ import static no.unit.nva.constants.Words.TICKETS; import static no.unit.nva.testutils.RandomDataGenerator.randomJson; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -22,7 +20,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.Set; import no.unit.nva.events.models.AwsEventBridgeDetail; import no.unit.nva.events.models.AwsEventBridgeEvent; import no.unit.nva.events.models.EventReference; @@ -34,23 +37,13 @@ import no.unit.nva.s3.S3Driver; import no.unit.nva.stubs.FakeS3Client; import no.unit.nva.testutils.RandomDataGenerator; - import nva.commons.core.paths.UnixPath; import nva.commons.core.paths.UriWrapper; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; - import software.amazon.awssdk.services.s3.model.NoSuchKeyException; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.util.Set; - public class IndexResourceHandlerTest { public static final IndexDocument SAMPLE_RESOURCE = @@ -113,7 +106,7 @@ void shouldSendMessageToRecoveryQueueWhenIndexingResourceIsFailing() throws Exce var input = createEventBridgeEvent(resourceLocation); indexResourceHandler.handleRequest(input, output, context); - var deliveredMessage = sqsClient.getDeliveredMessages().get(0); + var deliveredMessage = sqsClient.getDeliveredMessages().getFirst(); assertThat( deliveredMessage.messageAttributes().get("id").stringValue(), is(notNullValue())); @@ -131,7 +124,7 @@ void shouldSendMessageToRecoveryQueueWhenIndexingTicketIsFailing() throws Except var input = createEventBridgeEvent(resourceLocation); indexResourceHandler.handleRequest(input, output, context); - var deliveredMessage = sqsClient.getDeliveredMessages().get(0); + var deliveredMessage = sqsClient.getDeliveredMessages().getFirst(); assertThat( deliveredMessage.messageAttributes().get("id").stringValue(), is(notNullValue())); diff --git a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/InitHandlerTest.java b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/InitHandlerTest.java index b3b1ab220..11169f0c9 100644 --- a/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/InitHandlerTest.java +++ b/indexing-handlers/src/test/java/no/unit/nva/indexing/handlers/InitHandlerTest.java @@ -5,7 +5,6 @@ import static no.unit.nva.indexing.handlers.InitHandler.FAILED; import static no.unit.nva.indexing.handlers.InitHandler.SUCCESS; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringContains.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -15,51 +14,48 @@ import static org.mockito.Mockito.when; import com.amazonaws.services.lambda.runtime.Context; - +import java.io.IOException; import no.unit.nva.indexingclient.IndexingClient; - import org.apache.logging.log4j.core.test.appender.ListAppender; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import java.io.IOException; - class InitHandlerTest { - private static ListAppender appender; - private InitHandler initHandler; - private IndexingClient indexingClient; - private Context context; - - @BeforeAll - public static void initClass() { - appender = getAppender(InitHandler.class); - } - - @BeforeEach - void init() { - indexingClient = mock(IndexingClient.class); - initHandler = new InitHandler(indexingClient); - context = mock(Context.class); - } - - @Test - void shouldNotThrowExceptionIfIndicesClientDoesNotThrowException() throws IOException { - doNothing().when(indexingClient).createIndex(any(String.class)); - var response = initHandler.handleRequest(null, context); - assertEquals(response, SUCCESS); - } - - @Test - void shouldLogWarningAndReturnFailedWhenIndexingClientFailedToCreateIndex() throws IOException { - String expectedMessage = randomString(); - when(indexingClient.createIndex(Mockito.anyString(), Mockito.anyMap(), Mockito.anyMap())) - .thenThrow(new IOException(expectedMessage)); - var response = initHandler.handleRequest(null, context); - assertEquals(FAILED, response); - - assertThat(logToString(appender), containsString(expectedMessage)); - } + private static ListAppender appender; + private InitHandler initHandler; + private IndexingClient indexingClient; + private Context context; + + @BeforeAll + public static void initClass() { + appender = getAppender(InitHandler.class); + } + + @BeforeEach + void init() { + indexingClient = mock(IndexingClient.class); + initHandler = new InitHandler(indexingClient); + context = mock(Context.class); + } + + @Test + void shouldNotThrowExceptionIfIndicesClientDoesNotThrowException() throws IOException { + doNothing().when(indexingClient).createIndex(any(String.class)); + var response = initHandler.handleRequest(null, context); + assertEquals(SUCCESS, response); + } + + @Test + void shouldLogWarningAndReturnFailedWhenIndexingClientFailedToCreateIndex() throws IOException { + String expectedMessage = randomString(); + when(indexingClient.createIndex(Mockito.anyString(), Mockito.anyMap(), Mockito.anyMap())) + .thenThrow(new IOException(expectedMessage)); + var response = initHandler.handleRequest(null, context); + assertEquals(FAILED, response); + + assertThat(logToString(appender), containsString(expectedMessage)); + } } diff --git a/search-commons/src/main/java/no/unit/nva/constants/Defaults.java b/search-commons/src/main/java/no/unit/nva/constants/Defaults.java index 526a918cc..180e96ce8 100644 --- a/search-commons/src/main/java/no/unit/nva/constants/Defaults.java +++ b/search-commons/src/main/java/no/unit/nva/constants/Defaults.java @@ -16,24 +16,24 @@ */ public final class Defaults { - public static final ObjectMapper objectMapperWithEmpty = JsonUtils.dtoObjectMapper; - public static final ObjectMapper objectMapperNoEmpty = JsonUtils.dynamoObjectMapper; - public static final Environment ENVIRONMENT = new Environment(); + public static final ObjectMapper objectMapperWithEmpty = JsonUtils.dtoObjectMapper; + public static final ObjectMapper objectMapperNoEmpty = JsonUtils.dynamoObjectMapper; + public static final Environment ENVIRONMENT = new Environment(); - public static final String DEFAULT_OFFSET = "0"; - public static final String DEFAULT_VALUE_PER_PAGE = "15"; + public static final String DEFAULT_OFFSET = "0"; + public static final String DEFAULT_VALUE_PER_PAGE = "15"; - public static final String DEFAULT_SORT_ORDER = "desc"; - public static final int DEFAULT_AGGREGATION_SIZE = 100; - public static final URI PAGINATED_SEARCH_RESULT_CONTEXT = - URI.create("https://bibsysdev.github.io/src/search/paginated-search-result.json"); + public static final String DEFAULT_SORT_ORDER = "desc"; + public static final int DEFAULT_AGGREGATION_SIZE = 100; + public static final URI PAGINATED_SEARCH_RESULT_CONTEXT = + URI.create("https://bibsysdev.github.io/src/search/paginated-search-result.json"); - public static final List DEFAULT_RESPONSE_MEDIA_TYPES = - List.of(MediaType.JSON_UTF_8, MediaTypes.APPLICATION_JSON_LD, MediaType.CSV_UTF_8); + public static final List DEFAULT_RESPONSE_MEDIA_TYPES = + List.of(MediaType.JSON_UTF_8, MediaTypes.APPLICATION_JSON_LD, MediaType.CSV_UTF_8); - public static final int ZERO_RESULTS_AGGREGATION_ONLY = 0; - public static final String DEFAULT_SHARD_ID = "0"; + public static final int ZERO_RESULTS_AGGREGATION_ONLY = 0; + public static final String DEFAULT_SHARD_ID = "0"; - @JacocoGenerated - public Defaults() {} + @JacocoGenerated + public Defaults() {} } diff --git a/search-commons/src/main/java/no/unit/nva/constants/ErrorMessages.java b/search-commons/src/main/java/no/unit/nva/constants/ErrorMessages.java index 03a0f21df..46ab2c9ec 100644 --- a/search-commons/src/main/java/no/unit/nva/constants/ErrorMessages.java +++ b/search-commons/src/main/java/no/unit/nva/constants/ErrorMessages.java @@ -3,14 +3,12 @@ import static no.unit.nva.constants.Words.PREFIX; import static no.unit.nva.constants.Words.QUOTE; import static no.unit.nva.constants.Words.SUFFIX; - import static nva.commons.core.StringUtils.EMPTY_STRING; -import nva.commons.core.JacocoGenerated; - import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; +import nva.commons.core.JacocoGenerated; /** * Error messages used in the search service. diff --git a/search-commons/src/main/java/no/unit/nva/constants/Words.java b/search-commons/src/main/java/no/unit/nva/constants/Words.java index e5f925657..d3be62095 100644 --- a/search-commons/src/main/java/no/unit/nva/constants/Words.java +++ b/search-commons/src/main/java/no/unit/nva/constants/Words.java @@ -10,149 +10,148 @@ */ public final class Words { - public static final float PI = 3.14F; // π -> used for boosting. - public static final float PHI = 1.618F; // Golden Ratio (Φ) -> used for boosting. + public static final float PI = 3.14F; // π -> used for boosting. + public static final float PHI = 1.618F; // Golden Ratio (Φ) -> used for boosting. - public static final int NAME_AND_SORT_LENGTH = 2; + public static final int NAME_AND_SORT_LENGTH = 2; - public static final boolean KEYWORD_TRUE = true; - public static final boolean KEYWORD_FALSE = false; + public static final boolean KEYWORD_TRUE = true; + public static final boolean KEYWORD_FALSE = false; - public static final char CHAR_UNDERSCORE = '_'; + public static final char CHAR_UNDERSCORE = '_'; - public static final String ABSTRACT = "abstract"; - public static final String ADDITIONAL_IDENTIFIERS = "additionalIdentifiers"; - public static final String AFFILIATIONS = "affiliations"; - public static final String ALL = "all"; - public static final String AMPERSAND = "&"; - public static final String ASSOCIATED_ARTIFACTS = "associatedArtifacts"; - public static final String ASTERISK = "*"; - public static final String AUTHORIZATION = "Authorization"; - public static final String BODY = "body"; - public static final String BOKMAAL_CODE = "nb"; - public static final String BUCKETS = "buckets"; - public static final String CODE = "code"; - public static final String COLON = ":"; - public static final String COMMA = ","; - public static final String CONSUMPTION_ATTRIBUTES = "consumptionAttributes"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String CONTRIBUTOR = "contributor"; - public static final String CONTRIBUTORS = "contributors"; - public static final String CONTRIBUTOR_ORGANIZATIONS = "contributorOrganizations"; - public static final String COUNTRY_CODE = "countryCode"; - public static final String COURSE = "course"; - public static final String CREATED_DATE = "createdDate"; - public static final String CREATOR = "Creator"; - public static final String CRISTIN_AS_TYPE = "Cristin"; - public static final String CURATING_INSTITUTIONS = "curatingInstitutions"; - public static final String CUSTOMER_ID = "customerId"; - public static final String DAY = "day"; - public static final String DOI = "doi"; - public static final String DOIREQUESTS_INDEX = "doirequests"; - public static final String DOT = "."; - public static final String ENGLISH_CODE = "en"; - public static final String ENTITY_DESCRIPTION = "entityDescription"; - public static final String ENTITY_DESCRIPTION_CONTRIBUTORS_PATH = - "/entityDescription/contributors"; - public static final String EQUAL = "="; - public static final String FILES = "files"; - public static final String FILES_STATUS = "filesStatus"; - public static final String FIRST_NAME = "firstName"; - public static final String FROM = "from"; - public static final String FUNDINGS = "fundings"; - public static final String FUNDING_SOURCE = "fundingSource"; - public static final String HANDLE = "handle"; - public static final String HAS_PARTS = "hasParts"; - public static final String HTTPS = "https://"; - public static final String ID = "id"; - public static final String IDENTIFIER = "identifier"; - public static final String IDENTITY = "identity"; - public static final String IMPORT_CANDIDATES_INDEX = "import-candidates"; - public static final String ISBN_LIST = "isbnList"; - public static final String ISBN_PREFIX = "isbnPrefix"; - public static final String JOURNAL = "journal"; - public static final String JOURNAL_AS_TYPE = "Journal"; - public static final String KEY = "key"; - public static final String KEYWORD = "keyword"; - public static final String LABELS = "labels"; - public static final String LANGUAGE = "language"; - public static final String LAST_NAME = "lastName"; - public static final String LICENSE = "license"; - public static final String MAIN_TITLE = "mainTitle"; - public static final String MESSAGES = "messages"; - public static final String MESSAGES_INDEX = "messages"; - public static final String META_INFO = "metaInfo"; - public static final String MODIFIED_DATE = "modifiedDate"; - public static final String MONTH = "month"; - public static final String NAME = "name"; - public static final String NO = "NO"; - public static final String NONE = "none"; - public static final String NOT_VERIFIED = "NotVerified"; - public static final String NYNORSK_CODE = "nn"; - public static final String ONLINE_ISSN = "onlineIssn"; - public static final String ORC_ID = "orcId"; - public static final String ORGANIZATION = "organization"; - public static final String OWNER = "owner"; - public static final String OWNER_AFFILIATION = "ownerAffiliation"; - public static final String PAGE = "page"; - public static final String PAGES = "pages"; - public static final String PART_OF = "partOf"; - public static final String PIPE = "|"; - public static final String PLUS = "+"; - public static final String POST_FILTER = "withAppliedFilter"; - public static final String PREFIX = "("; - public static final String PRINT_ISSN = "printIssn"; - public static final String PROJECTS_ID = "projects.id.keyword"; - public static final String PUBLICATION = "publication"; - public static final String PUBLICATION_CONTEXT = "publicationContext"; - public static final String PUBLICATION_DATE = "publicationDate"; - public static final String PUBLICATION_INSTANCE = "publicationInstance"; - public static final String PUBLICATION_STATUS = "publicationStatus"; - public static final String PUBLISHED_DATE = "publishedDate"; - public static final String PUBLISHER = "publisher"; - public static final String PUBLISHING_REQUESTS_INDEX = "publishingrequests"; - public static final String Q = "q"; - public static final String QUOTE = "'"; - public static final String REFERENCE = "reference"; - public static final String RELEVANCE_KEY_NAME = "relevance"; - public static final String RESOURCES = "resources"; - public static final String RESOURCE_OWNER = "resourceOwner"; - public static final String ROLE = "role"; - public static final String ROOT = "root"; - public static final String SAMI_CODE = "sme"; - public static final String SCIENTIFIC_INDEX = "scientificIndex"; - public static final String SCIENTIFIC_VALUE = "scientificValue"; - public static final String SCOPUS_AS_TYPE = "Scopus"; - public static final String SCORE = "_score"; - public static final String SEARCH = "_search"; - public static final String SEARCH_AFTER = "SEARCH_AFTER"; - public static final String SEARCH_INFRASTRUCTURE_CREDENTIALS = - "SearchInfrastructureCredentials"; - public static final String SERIES = "series"; - public static final String SERIES_AS_TYPE = "Series"; - public static final String SLASH = "/"; - public static final String SORT_LAST = "_last"; - public static final String SOURCE = "source"; - public static final String SOURCE_NAME = "sourceName"; - public static final String SPACE = " "; - public static final String STATUS = "status"; - public static final String SUFFIX = ")"; - public static final String TAGS = "tags"; - public static final String TEXT_CSV = "text/csv"; - public static final String TICKETS = "tickets"; - public static final String TITLE = "title"; - public static final String TOP_LEVEL_ORGANIZATION = "topLevelOrganization"; - public static final String TOP_LEVEL_ORGANIZATIONS = "topLevelOrganizations"; - public static final String TYPE = "type"; - public static final String UNDERSCORE = "_"; - public static final String USERNAME = "username"; - public static final String VALUE = "value"; - public static final String VERIFICATION_STATUS = "verificationStatus"; - public static final String VERIFIED = "Verified"; - public static final String VIEWED_BY = "viewedBy"; - public static final String YEAR = "year"; - public static final String ZERO = "0"; + public static final String ABSTRACT = "abstract"; + public static final String ADDITIONAL_IDENTIFIERS = "additionalIdentifiers"; + public static final String AFFILIATIONS = "affiliations"; + public static final String ALL = "all"; + public static final String AMPERSAND = "&"; + public static final String ASSOCIATED_ARTIFACTS = "associatedArtifacts"; + public static final String ASTERISK = "*"; + public static final String AUTHORIZATION = "Authorization"; + public static final String BODY = "body"; + public static final String BOKMAAL_CODE = "nb"; + public static final String BUCKETS = "buckets"; + public static final String CODE = "code"; + public static final String COLON = ":"; + public static final String COMMA = ","; + public static final String CONSUMPTION_ATTRIBUTES = "consumptionAttributes"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTRIBUTOR = "contributor"; + public static final String CONTRIBUTORS = "contributors"; + public static final String CONTRIBUTOR_ORGANIZATIONS = "contributorOrganizations"; + public static final String COUNTRY_CODE = "countryCode"; + public static final String COURSE = "course"; + public static final String CREATED_DATE = "createdDate"; + public static final String CREATOR = "Creator"; + public static final String CRISTIN_AS_TYPE = "Cristin"; + public static final String CURATING_INSTITUTIONS = "curatingInstitutions"; + public static final String CUSTOMER_ID = "customerId"; + public static final String DAY = "day"; + public static final String DOI = "doi"; + public static final String DOIREQUESTS_INDEX = "doirequests"; + public static final String DOT = "."; + public static final String ENGLISH_CODE = "en"; + public static final String ENTITY_DESCRIPTION = "entityDescription"; + public static final String ENTITY_DESCRIPTION_CONTRIBUTORS_PATH = + "/entityDescription/contributors"; + public static final String EQUAL = "="; + public static final String FILES = "files"; + public static final String FILES_STATUS = "filesStatus"; + public static final String FIRST_NAME = "firstName"; + public static final String FROM = "from"; + public static final String FUNDINGS = "fundings"; + public static final String FUNDING_SOURCE = "fundingSource"; + public static final String HANDLE = "handle"; + public static final String HAS_PARTS = "hasParts"; + public static final String HTTPS = "https://"; + public static final String ID = "id"; + public static final String IDENTIFIER = "identifier"; + public static final String IDENTITY = "identity"; + public static final String IMPORT_CANDIDATES_INDEX = "import-candidates"; + public static final String ISBN_LIST = "isbnList"; + public static final String ISBN_PREFIX = "isbnPrefix"; + public static final String JOURNAL = "journal"; + public static final String JOURNAL_AS_TYPE = "Journal"; + public static final String KEY = "key"; + public static final String KEYWORD = "keyword"; + public static final String LABELS = "labels"; + public static final String LANGUAGE = "language"; + public static final String LAST_NAME = "lastName"; + public static final String LICENSE = "license"; + public static final String MAIN_TITLE = "mainTitle"; + public static final String MESSAGES = "messages"; + public static final String MESSAGES_INDEX = "messages"; + public static final String META_INFO = "metaInfo"; + public static final String MODIFIED_DATE = "modifiedDate"; + public static final String MONTH = "month"; + public static final String NAME = "name"; + public static final String NO = "NO"; + public static final String NONE = "none"; + public static final String NOT_VERIFIED = "NotVerified"; + public static final String NYNORSK_CODE = "nn"; + public static final String ONLINE_ISSN = "onlineIssn"; + public static final String ORC_ID = "orcId"; + public static final String ORGANIZATION = "organization"; + public static final String OWNER = "owner"; + public static final String OWNER_AFFILIATION = "ownerAffiliation"; + public static final String PAGE = "page"; + public static final String PAGES = "pages"; + public static final String PART_OF = "partOf"; + public static final String PIPE = "|"; + public static final String PLUS = "+"; + public static final String POST_FILTER = "withAppliedFilter"; + public static final String PREFIX = "("; + public static final String PRINT_ISSN = "printIssn"; + public static final String PROJECTS_ID = "projects.id.keyword"; + public static final String PUBLICATION = "publication"; + public static final String PUBLICATION_CONTEXT = "publicationContext"; + public static final String PUBLICATION_DATE = "publicationDate"; + public static final String PUBLICATION_INSTANCE = "publicationInstance"; + public static final String PUBLICATION_STATUS = "publicationStatus"; + public static final String PUBLISHED_DATE = "publishedDate"; + public static final String PUBLISHER = "publisher"; + public static final String PUBLISHING_REQUESTS_INDEX = "publishingrequests"; + public static final String Q = "q"; + public static final String QUOTE = "'"; + public static final String REFERENCE = "reference"; + public static final String RELEVANCE_KEY_NAME = "relevance"; + public static final String RESOURCES = "resources"; + public static final String RESOURCE_OWNER = "resourceOwner"; + public static final String ROLE = "role"; + public static final String ROOT = "root"; + public static final String SAMI_CODE = "sme"; + public static final String SCIENTIFIC_INDEX = "scientificIndex"; + public static final String SCIENTIFIC_VALUE = "scientificValue"; + public static final String SCOPUS_AS_TYPE = "Scopus"; + public static final String SCORE = "_score"; + public static final String SEARCH = "_search"; + public static final String SEARCH_AFTER = "SEARCH_AFTER"; + public static final String SEARCH_INFRASTRUCTURE_CREDENTIALS = "SearchInfrastructureCredentials"; + public static final String SERIES = "series"; + public static final String SERIES_AS_TYPE = "Series"; + public static final String SLASH = "/"; + public static final String SORT_LAST = "_last"; + public static final String SOURCE = "source"; + public static final String SOURCE_NAME = "sourceName"; + public static final String SPACE = " "; + public static final String STATUS = "status"; + public static final String SUFFIX = ")"; + public static final String TAGS = "tags"; + public static final String TEXT_CSV = "text/csv"; + public static final String TICKETS = "tickets"; + public static final String TITLE = "title"; + public static final String TOP_LEVEL_ORGANIZATION = "topLevelOrganization"; + public static final String TOP_LEVEL_ORGANIZATIONS = "topLevelOrganizations"; + public static final String TYPE = "type"; + public static final String UNDERSCORE = "_"; + public static final String USERNAME = "username"; + public static final String VALUE = "value"; + public static final String VERIFICATION_STATUS = "verificationStatus"; + public static final String VERIFIED = "Verified"; + public static final String VIEWED_BY = "viewedBy"; + public static final String YEAR = "year"; + public static final String ZERO = "0"; - @JacocoGenerated - public Words() {} + @JacocoGenerated + public Words() {} } diff --git a/search-commons/src/main/java/no/unit/nva/indexingclient/IndexQueueClient.java b/search-commons/src/main/java/no/unit/nva/indexingclient/IndexQueueClient.java index ccc684cce..c8dfa0d44 100644 --- a/search-commons/src/main/java/no/unit/nva/indexingclient/IndexQueueClient.java +++ b/search-commons/src/main/java/no/unit/nva/indexingclient/IndexQueueClient.java @@ -1,18 +1,15 @@ package no.unit.nva.indexingclient; +import java.time.Duration; import no.unit.nva.indexingclient.models.QueueClient; - import nva.commons.core.Environment; import nva.commons.core.JacocoGenerated; - import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.SendMessageRequest; -import java.time.Duration; - @JacocoGenerated public final class IndexQueueClient implements QueueClient { diff --git a/search-commons/src/main/java/no/unit/nva/indexingclient/IndexingClient.java b/search-commons/src/main/java/no/unit/nva/indexingclient/IndexingClient.java index 77e46f840..c61d7f2e8 100644 --- a/search-commons/src/main/java/no/unit/nva/indexingclient/IndexingClient.java +++ b/search-commons/src/main/java/no/unit/nva/indexingclient/IndexingClient.java @@ -4,24 +4,27 @@ import static no.unit.nva.constants.Words.IMPORT_CANDIDATES_INDEX; import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.indexingclient.models.RestHighLevelClientWrapper.defaultRestHighLevelClientWrapper; - import static nva.commons.core.attempt.Try.attempt; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Iterators; import com.google.common.collect.UnmodifiableIterator; - +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import no.unit.nva.commons.json.JsonUtils; import no.unit.nva.indexingclient.models.AuthenticatedOpenSearchClientWrapper; import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.indexingclient.models.RestHighLevelClientWrapper; import no.unit.nva.search.common.jwt.CachedJwtProvider; import no.unit.nva.search.common.jwt.CognitoAuthenticator; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.Try; import nva.commons.secrets.SecretsReader; - import org.opensearch.action.DocWriteResponse; import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; import org.opensearch.action.bulk.BulkRequest; @@ -39,149 +42,139 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - public class IndexingClient extends AuthenticatedOpenSearchClientWrapper { - public static final int BULK_SIZE = 100; - private static final Logger logger = LoggerFactory.getLogger(IndexingClient.class); - - private static final String INITIAL_LOG_MESSAGE = "Adding document [{}] to -> {}"; - private static final String DOCUMENT_WITH_ID_WAS_NOT_FOUND_IN_SEARCH_INFRASTRUCTURE = - "Document with id={} was not found in search infrastructure"; - private static final boolean SEQUENTIAL = false; - - /** - * Creates a new OpenSearchRestClient. - * - * @param openSearchClient client to use for access to search infrastructure - */ - public IndexingClient( - RestHighLevelClientWrapper openSearchClient, CachedJwtProvider cachedJwtProvider) { - super(openSearchClient, cachedJwtProvider); - } - - @JacocoGenerated - public static IndexingClient defaultIndexingClient() { - return prepareWithSecretReader(new SecretsReader()); - } - - public static IndexingClient prepareWithSecretReader(SecretsReader secretReader) { - var cognitoCredentials = createCognitoCredentials(secretReader); - var cognitoAuthenticator = - CognitoAuthenticator.prepareWithCognitoCredentials(cognitoCredentials); - var cachedJwtProvider = CachedJwtProvider.prepareWithAuthenticator(cognitoAuthenticator); - return new IndexingClient(defaultRestHighLevelClientWrapper(), cachedJwtProvider); - } - - private static DeleteRequest deleteDocumentRequest(String index, String identifier) { - return new DeleteRequest(index, identifier).routing(DEFAULT_SHARD_ID); - } - - public Void addDocumentToIndex(IndexDocument indexDocument) throws IOException { - logger.debug( - INITIAL_LOG_MESSAGE, - indexDocument.getDocumentIdentifier(), - indexDocument.getIndexName()); - openSearchClient.index(indexDocument.toIndexRequest(), getRequestOptions()); - return null; - } - - /** - * Removes a document from Opensearch index. - * - * @param identifier og document - */ - public void removeDocumentFromResourcesIndex(String identifier) throws IOException { - var deleteRequest = deleteDocumentRequest(RESOURCES, identifier); - var deleteResponse = openSearchClient.delete(deleteRequest, getRequestOptions()); - loggWarningIfNotFound(identifier, deleteResponse); - } - - public void removeDocumentFromImportCandidateIndex(String identifier) throws IOException { - var deleteRequest = deleteDocumentRequest(IMPORT_CANDIDATES_INDEX, identifier); - var deleteResponse = openSearchClient.delete(deleteRequest, getRequestOptions()); - loggWarningIfNotFound(identifier, deleteResponse); - } - - public Void createIndex(String indexName) throws IOException { - var createRequest = new CreateIndexRequest(indexName); - openSearchClient.indices().create(createRequest, getRequestOptions()); - return null; - } - - public Void createIndex(String indexName, Map mappings) throws IOException { - var createRequest = new CreateIndexRequest(indexName); - createRequest.mapping(mappings); - openSearchClient.indices().create(createRequest, getRequestOptions()); - return null; - } - - public Void createIndex(String indexName, Map mappings, Map settings) - throws IOException { - var createRequest = new CreateIndexRequest(indexName); - createRequest.mapping(mappings); - createRequest.settings(settings); - openSearchClient.indices().create(createRequest, getRequestOptions()); - return null; - } - - public Stream batchInsert(Stream contents) { - var batches = splitStreamToBatches(contents); - return batches.map(attempt(this::insertBatch)).map(Try::orElseThrow); - } - - private Stream> splitStreamToBatches(Stream indexDocuments) { - UnmodifiableIterator> bulks = - Iterators.partition(indexDocuments.iterator(), BULK_SIZE); - return StreamSupport.stream( - Spliterators.spliteratorUnknownSize(bulks, Spliterator.ORDERED), SEQUENTIAL); - } - - private BulkResponse insertBatch(List bulk) throws IOException { - List indexRequests = - bulk.stream().parallel().map(IndexDocument::toIndexRequest).toList(); - - BulkRequest request = new BulkRequest(); - indexRequests.forEach(request::add); - request.setRefreshPolicy(RefreshPolicy.WAIT_UNTIL); - request.waitForActiveShards(ActiveShardCount.ONE); - return openSearchClient.bulk(request, getRequestOptions()); - } - - public Void deleteIndex(String indexName) throws IOException { - openSearchClient.indices().delete(new DeleteIndexRequest(indexName), getRequestOptions()); - return null; - } - - public JsonNode getMapping(String indexName) { - return attempt(() -> getMappingMetadata(indexName)) - .map(MappingMetadata::source) - .map(CompressedXContent::uncompressed) - .map(BytesReference::utf8ToString) - .map(JsonUtils.dtoObjectMapper::readTree) - .orElseThrow(); - } - - private void loggWarningIfNotFound(String identifier, DeleteResponse deleteResponse) { - if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) { - logger.warn(DOCUMENT_WITH_ID_WAS_NOT_FOUND_IN_SEARCH_INFRASTRUCTURE, identifier); - } - } - - private MappingMetadata getMappingMetadata(String indexName) throws IOException { - var request = new GetMappingsRequest().indices(indexName); - return openSearchClient - .indices() - .getIndicesClient() - .getMapping(request, getRequestOptions()) - .mappings() - .get(indexName); - } + public static final int BULK_SIZE = 100; + private static final Logger logger = LoggerFactory.getLogger(IndexingClient.class); + + private static final String INITIAL_LOG_MESSAGE = "Adding document [{}] to -> {}"; + private static final String DOCUMENT_WITH_ID_WAS_NOT_FOUND_IN_SEARCH_INFRASTRUCTURE = + "Document with id={} was not found in search infrastructure"; + private static final boolean SEQUENTIAL = false; + + /** + * Creates a new OpenSearchRestClient. + * + * @param openSearchClient client to use for access to search infrastructure + */ + public IndexingClient( + RestHighLevelClientWrapper openSearchClient, CachedJwtProvider cachedJwtProvider) { + super(openSearchClient, cachedJwtProvider); + } + + @JacocoGenerated + public static IndexingClient defaultIndexingClient() { + return prepareWithSecretReader(new SecretsReader()); + } + + public static IndexingClient prepareWithSecretReader(SecretsReader secretReader) { + var cognitoCredentials = createCognitoCredentials(secretReader); + var cognitoAuthenticator = + CognitoAuthenticator.prepareWithCognitoCredentials(cognitoCredentials); + var cachedJwtProvider = CachedJwtProvider.prepareWithAuthenticator(cognitoAuthenticator); + return new IndexingClient(defaultRestHighLevelClientWrapper(), cachedJwtProvider); + } + + private static DeleteRequest deleteDocumentRequest(String index, String identifier) { + return new DeleteRequest(index, identifier).routing(DEFAULT_SHARD_ID); + } + + public Void addDocumentToIndex(IndexDocument indexDocument) throws IOException { + logger.debug( + INITIAL_LOG_MESSAGE, indexDocument.getDocumentIdentifier(), indexDocument.getIndexName()); + openSearchClient.index(indexDocument.toIndexRequest(), getRequestOptions()); + return null; + } + + /** + * Removes a document from Opensearch index. + * + * @param identifier og document + */ + public void removeDocumentFromResourcesIndex(String identifier) throws IOException { + var deleteRequest = deleteDocumentRequest(RESOURCES, identifier); + var deleteResponse = openSearchClient.delete(deleteRequest, getRequestOptions()); + loggWarningIfNotFound(identifier, deleteResponse); + } + + public void removeDocumentFromImportCandidateIndex(String identifier) throws IOException { + var deleteRequest = deleteDocumentRequest(IMPORT_CANDIDATES_INDEX, identifier); + var deleteResponse = openSearchClient.delete(deleteRequest, getRequestOptions()); + loggWarningIfNotFound(identifier, deleteResponse); + } + + public Void createIndex(String indexName) throws IOException { + var createRequest = new CreateIndexRequest(indexName); + openSearchClient.indices().create(createRequest, getRequestOptions()); + return null; + } + + public Void createIndex(String indexName, Map mappings) throws IOException { + var createRequest = new CreateIndexRequest(indexName); + createRequest.mapping(mappings); + openSearchClient.indices().create(createRequest, getRequestOptions()); + return null; + } + + public Void createIndex(String indexName, Map mappings, Map settings) + throws IOException { + var createRequest = new CreateIndexRequest(indexName); + createRequest.mapping(mappings); + createRequest.settings(settings); + openSearchClient.indices().create(createRequest, getRequestOptions()); + return null; + } + + public Stream batchInsert(Stream contents) { + var batches = splitStreamToBatches(contents); + return batches.map(attempt(this::insertBatch)).map(Try::orElseThrow); + } + + private Stream> splitStreamToBatches(Stream indexDocuments) { + UnmodifiableIterator> bulks = + Iterators.partition(indexDocuments.iterator(), BULK_SIZE); + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(bulks, Spliterator.ORDERED), SEQUENTIAL); + } + + private BulkResponse insertBatch(List bulk) throws IOException { + List indexRequests = + bulk.stream().parallel().map(IndexDocument::toIndexRequest).toList(); + + BulkRequest request = new BulkRequest(); + indexRequests.forEach(request::add); + request.setRefreshPolicy(RefreshPolicy.WAIT_UNTIL); + request.waitForActiveShards(ActiveShardCount.ONE); + return openSearchClient.bulk(request, getRequestOptions()); + } + + public Void deleteIndex(String indexName) throws IOException { + openSearchClient.indices().delete(new DeleteIndexRequest(indexName), getRequestOptions()); + return null; + } + + public JsonNode getMapping(String indexName) { + return attempt(() -> getMappingMetadata(indexName)) + .map(MappingMetadata::source) + .map(CompressedXContent::uncompressed) + .map(BytesReference::utf8ToString) + .map(JsonUtils.dtoObjectMapper::readTree) + .orElseThrow(); + } + + private void loggWarningIfNotFound(String identifier, DeleteResponse deleteResponse) { + if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) { + logger.warn(DOCUMENT_WITH_ID_WAS_NOT_FOUND_IN_SEARCH_INFRASTRUCTURE, identifier); + } + } + + private MappingMetadata getMappingMetadata(String indexName) throws IOException { + var request = new GetMappingsRequest().indices(indexName); + return openSearchClient + .indices() + .getIndicesClient() + .getMapping(request, getRequestOptions()) + .mappings() + .get(indexName); + } } diff --git a/search-commons/src/main/java/no/unit/nva/indexingclient/models/AuthenticatedOpenSearchClientWrapper.java b/search-commons/src/main/java/no/unit/nva/indexingclient/models/AuthenticatedOpenSearchClientWrapper.java index 3d2c30d12..bfe1ab2fc 100644 --- a/search-commons/src/main/java/no/unit/nva/indexingclient/models/AuthenticatedOpenSearchClientWrapper.java +++ b/search-commons/src/main/java/no/unit/nva/indexingclient/models/AuthenticatedOpenSearchClientWrapper.java @@ -3,16 +3,13 @@ import static no.unit.nva.constants.Defaults.ENVIRONMENT; import static no.unit.nva.constants.Words.AUTHORIZATION; +import java.net.URI; import no.unit.nva.auth.CognitoCredentials; import no.unit.nva.search.common.jwt.CachedJwtProvider; import no.unit.nva.search.common.records.UsernamePasswordWrapper; - import nva.commons.secrets.SecretsReader; - import org.opensearch.client.RequestOptions; -import java.net.URI; - public class AuthenticatedOpenSearchClientWrapper { static final String SEARCH_INFRASTRUCTURE_CREDENTIALS = "SearchInfrastructureCredentials"; diff --git a/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndexDocument.java b/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndexDocument.java index a05d7eae4..991c303ac 100644 --- a/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndexDocument.java +++ b/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndexDocument.java @@ -9,24 +9,19 @@ import static no.unit.nva.constants.Words.IMPORT_CANDIDATES_INDEX; import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.constants.Words.TICKETS; - import static nva.commons.core.attempt.Try.attempt; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; - +import java.util.Objects; +import java.util.Optional; import no.unit.nva.commons.json.JsonSerializable; import no.unit.nva.identifiers.SortableIdentifier; - import nva.commons.core.StringUtils; - import org.opensearch.action.index.IndexRequest; import org.opensearch.common.xcontent.XContentType; -import java.util.Objects; -import java.util.Optional; - public record IndexDocument( @JsonProperty(CONSUMPTION_ATTRIBUTES) EventConsumptionAttributes consumptionAttributes, @JsonProperty(BODY) JsonNode resource) diff --git a/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndicesClientWrapper.java b/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndicesClientWrapper.java index d3eed84eb..8f7b894b8 100644 --- a/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndicesClientWrapper.java +++ b/search-commons/src/main/java/no/unit/nva/indexingclient/models/IndicesClientWrapper.java @@ -16,43 +16,41 @@ @JacocoGenerated public class IndicesClientWrapper { - private final IndicesClient indicesClient; - - public IndicesClientWrapper(IndicesClient indices) { - this.indicesClient = indices; - } - - /** - * Do not use this method. This method is only for experimenting. If you want to use a method of - * {@link IndicesClient} replicate the method in {@link IndicesClientWrapper} and call the - * respective {@link IndicesClient} one. - * - * @return the contained client. - */ - public IndicesClient getIndicesClient() { - return indicesClient; - } - - public CreateIndexResponse create( - CreateIndexRequest createIndexRequest, RequestOptions requestOptions) - throws IOException { - return indicesClient.create(createIndexRequest, requestOptions); - } - - public AcknowledgedResponse putSettings( - UpdateSettingsRequest updateSettingsRequest, RequestOptions requestOptions) - throws IOException { - return indicesClient.putSettings(updateSettingsRequest, requestOptions); - } - - public GetIndexResponse get(GetIndexRequest getIndexRequest, RequestOptions requestOptions) - throws IOException { - return indicesClient.get(getIndexRequest, requestOptions); - } - - public AcknowledgedResponse delete( - DeleteIndexRequest deleteIndexRequest, RequestOptions requestOptions) - throws IOException { - return indicesClient.delete(deleteIndexRequest, requestOptions); - } + private final IndicesClient indicesClient; + + public IndicesClientWrapper(IndicesClient indices) { + this.indicesClient = indices; + } + + /** + * Do not use this method. This method is only for experimenting. If you want to use a method of + * {@link IndicesClient} replicate the method in {@link IndicesClientWrapper} and call the + * respective {@link IndicesClient} one. + * + * @return the contained client. + */ + public IndicesClient getIndicesClient() { + return indicesClient; + } + + public CreateIndexResponse create( + CreateIndexRequest createIndexRequest, RequestOptions requestOptions) throws IOException { + return indicesClient.create(createIndexRequest, requestOptions); + } + + public AcknowledgedResponse putSettings( + UpdateSettingsRequest updateSettingsRequest, RequestOptions requestOptions) + throws IOException { + return indicesClient.putSettings(updateSettingsRequest, requestOptions); + } + + public GetIndexResponse get(GetIndexRequest getIndexRequest, RequestOptions requestOptions) + throws IOException { + return indicesClient.get(getIndexRequest, requestOptions); + } + + public AcknowledgedResponse delete( + DeleteIndexRequest deleteIndexRequest, RequestOptions requestOptions) throws IOException { + return indicesClient.delete(deleteIndexRequest, requestOptions); + } } diff --git a/search-commons/src/main/java/no/unit/nva/indexingclient/models/QueueClient.java b/search-commons/src/main/java/no/unit/nva/indexingclient/models/QueueClient.java index 3fd390277..d1ca37573 100644 --- a/search-commons/src/main/java/no/unit/nva/indexingclient/models/QueueClient.java +++ b/search-commons/src/main/java/no/unit/nva/indexingclient/models/QueueClient.java @@ -4,5 +4,5 @@ public interface QueueClient { - void sendMessage(SendMessageRequest sendMessageRequest); + void sendMessage(SendMessageRequest sendMessageRequest); } diff --git a/search-commons/src/main/java/no/unit/nva/indexingclient/models/RestHighLevelClientWrapper.java b/search-commons/src/main/java/no/unit/nva/indexingclient/models/RestHighLevelClientWrapper.java index 00bbee215..d866b20ba 100644 --- a/search-commons/src/main/java/no/unit/nva/indexingclient/models/RestHighLevelClientWrapper.java +++ b/search-commons/src/main/java/no/unit/nva/indexingclient/models/RestHighLevelClientWrapper.java @@ -2,8 +2,8 @@ import static no.unit.nva.constants.Defaults.ENVIRONMENT; +import java.io.IOException; import nva.commons.core.JacocoGenerated; - import org.apache.http.HttpHost; import org.opensearch.action.bulk.BulkRequest; import org.opensearch.action.bulk.BulkResponse; @@ -22,79 +22,76 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; - /** Class for avoiding mocking/spying the ES final classes. */ public class RestHighLevelClientWrapper { - private static final Logger logger = LoggerFactory.getLogger(RestHighLevelClientWrapper.class); - - private static final String SEARCH_INFRASTRUCTURE_API_URI = - ENVIRONMENT.readEnv("SEARCH_INFRASTRUCTURE_API_URI"); - private static final String INITIAL_LOG_MESSAGE = "Connecting to search infrastructure at {}"; - - private final RestHighLevelClient client; - - public RestHighLevelClientWrapper(RestHighLevelClient client) { - this.client = client; - } - - public RestHighLevelClientWrapper(RestClientBuilder clientBuilder) { - this(new RestHighLevelClient(clientBuilder)); - logger.debug(INITIAL_LOG_MESSAGE, clientBuilder); - } - - public static RestHighLevelClientWrapper defaultRestHighLevelClientWrapper() { - return prepareRestHighLevelClientWrapperForUri(SEARCH_INFRASTRUCTURE_API_URI); - } - - public static RestHighLevelClientWrapper prepareRestHighLevelClientWrapperForUri( - String address) { - return new RestHighLevelClientWrapper(RestClient.builder(HttpHost.create(address))); - } - - /** - * Use this method only to experiment and to extend the functionality of the wrapper. - * - * @return the contained client - */ - @JacocoGenerated - public RestHighLevelClient getClient() { - logger.warn("Use getClient only for finding which methods you need to add to the wrapper"); - return this.client; - } - - @JacocoGenerated - public SearchResponse search(SearchRequest searchRequest, RequestOptions requestOptions) - throws IOException { - return client.search(searchRequest, requestOptions); - } - - @JacocoGenerated - public IndexResponse index(IndexRequest updateRequest, RequestOptions requestOptions) - throws IOException { - return client.index(updateRequest, requestOptions); - } - - @JacocoGenerated - public DeleteResponse delete(DeleteRequest deleteRequest, RequestOptions requestOptions) - throws IOException { - return client.delete(deleteRequest, requestOptions); - } - - @JacocoGenerated - public UpdateResponse update(UpdateRequest updateRequest, RequestOptions requestOptions) - throws IOException { - return client.update(updateRequest, requestOptions); - } - - @JacocoGenerated - public IndicesClientWrapper indices() { - return new IndicesClientWrapper(client.indices()); - } - - @JacocoGenerated - public BulkResponse bulk(BulkRequest request, RequestOptions requestOption) throws IOException { - return client.bulk(request, requestOption); - } + private static final Logger logger = LoggerFactory.getLogger(RestHighLevelClientWrapper.class); + + private static final String SEARCH_INFRASTRUCTURE_API_URI = + ENVIRONMENT.readEnv("SEARCH_INFRASTRUCTURE_API_URI"); + private static final String INITIAL_LOG_MESSAGE = "Connecting to search infrastructure at {}"; + + private final RestHighLevelClient client; + + public RestHighLevelClientWrapper(RestHighLevelClient client) { + this.client = client; + } + + public RestHighLevelClientWrapper(RestClientBuilder clientBuilder) { + this(new RestHighLevelClient(clientBuilder)); + logger.debug(INITIAL_LOG_MESSAGE, clientBuilder); + } + + public static RestHighLevelClientWrapper defaultRestHighLevelClientWrapper() { + return prepareRestHighLevelClientWrapperForUri(SEARCH_INFRASTRUCTURE_API_URI); + } + + public static RestHighLevelClientWrapper prepareRestHighLevelClientWrapperForUri(String address) { + return new RestHighLevelClientWrapper(RestClient.builder(HttpHost.create(address))); + } + + /** + * Use this method only to experiment and to extend the functionality of the wrapper. + * + * @return the contained client + */ + @JacocoGenerated + public RestHighLevelClient getClient() { + logger.warn("Use getClient only for finding which methods you need to add to the wrapper"); + return this.client; + } + + @JacocoGenerated + public SearchResponse search(SearchRequest searchRequest, RequestOptions requestOptions) + throws IOException { + return client.search(searchRequest, requestOptions); + } + + @JacocoGenerated + public IndexResponse index(IndexRequest updateRequest, RequestOptions requestOptions) + throws IOException { + return client.index(updateRequest, requestOptions); + } + + @JacocoGenerated + public DeleteResponse delete(DeleteRequest deleteRequest, RequestOptions requestOptions) + throws IOException { + return client.delete(deleteRequest, requestOptions); + } + + @JacocoGenerated + public UpdateResponse update(UpdateRequest updateRequest, RequestOptions requestOptions) + throws IOException { + return client.update(updateRequest, requestOptions); + } + + @JacocoGenerated + public IndicesClientWrapper indices() { + return new IndicesClientWrapper(client.indices()); + } + + @JacocoGenerated + public BulkResponse bulk(BulkRequest request, RequestOptions requestOption) throws IOException { + return client.bulk(request, requestOption); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/AggregationFormat.java b/search-commons/src/main/java/no/unit/nva/search/common/AggregationFormat.java index 12a0593ad..a57164bb4 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/AggregationFormat.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/AggregationFormat.java @@ -34,119 +34,116 @@ */ public final class AggregationFormat { - public static final String UNIQUE_PUBLICATIONS = "unique_publications"; - - @JacocoGenerated - public AggregationFormat() {} - - public static JsonNode apply(JsonNode aggregations, Map definitions) { - var objectNode = JsonUtils.dtoObjectMapper.createObjectNode(); - if (nonNull(aggregations)) { - getAggregationFieldStreams(aggregations, definitions) - .map(AggregationFormat::getJsonNodeEntry) - .forEach(item -> objectNode.set(item.getKey(), fixNodes(item.getValue()))); - } - return objectNode; - } - - @JacocoGenerated - private static JsonNode fixNodes(JsonNode node) { - if (node.isArray()) { - var arrayNode = JsonUtils.dtoObjectMapper.createArrayNode(); - node.forEach(element -> arrayNode.add(fixNodes(element))); - return arrayNode; - } else { - var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); - node.fields() - .forEachRemaining(entry -> extractKeyAndLabels(entry, outputAggregationNode)); - return outputAggregationNode.isEmpty() - ? JsonUtils.dtoObjectMapper.createArrayNode() - : outputAggregationNode; - } - } + public static final String UNIQUE_PUBLICATIONS = "unique_publications"; - private static void extractKeyAndLabels( - Entry entry, ObjectNode outputAggregationNode) { - if (keyIsLabel(entry)) { - outputAggregationNode.set(entry.getKey(), formatLabels(entry.getValue())); - } else if (keyIsName(entry)) { - outputAggregationNode.set(LABELS, formatName(entry.getValue())); - } else if (rootHasUniquePublicationsCount(entry)) { - outputAggregationNode.set( - DOC_COUNT, entry.getValue().get(UNIQUE_PUBLICATIONS).get(VALUE)); - } else { - outputAggregationNode.set(entry.getKey(), entry.getValue()); - } - } + @JacocoGenerated + public AggregationFormat() {} - private static Stream> getAggregationFieldStreams( - JsonNode aggregations, Map definitions) { - return definitions.entrySet().stream() - .map(entry -> Map.entry(entry.getKey(), aggregations.at(entry.getValue()))); + public static JsonNode apply(JsonNode aggregations, Map definitions) { + var objectNode = JsonUtils.dtoObjectMapper.createObjectNode(); + if (nonNull(aggregations)) { + getAggregationFieldStreams(aggregations, definitions) + .map(AggregationFormat::getJsonNodeEntry) + .forEach(item -> objectNode.set(item.getKey(), fixNodes(item.getValue()))); } - - private static Map.Entry getJsonNodeEntry(Map.Entry entry) { - return Map.entry( - getNormalizedFieldName(entry.getKey()), getBucketOrValue(entry.getValue())); + return objectNode; + } + + @JacocoGenerated + private static JsonNode fixNodes(JsonNode node) { + if (node.isArray()) { + var arrayNode = JsonUtils.dtoObjectMapper.createArrayNode(); + node.forEach(element -> arrayNode.add(fixNodes(element))); + return arrayNode; + } else { + var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); + node.fields().forEachRemaining(entry -> extractKeyAndLabels(entry, outputAggregationNode)); + return outputAggregationNode.isEmpty() + ? JsonUtils.dtoObjectMapper.createArrayNode() + : outputAggregationNode; } - - private static Map.Entry getNormalizedJsonNodeEntry( - Map.Entry entry) { - return Map.entry(getNormalizedFieldName(entry.getKey()), entry.getValue()); + } + + private static void extractKeyAndLabels( + Entry entry, ObjectNode outputAggregationNode) { + if (keyIsLabel(entry)) { + outputAggregationNode.set(entry.getKey(), formatLabels(entry.getValue())); + } else if (keyIsName(entry)) { + outputAggregationNode.set(LABELS, formatName(entry.getValue())); + } else if (rootHasUniquePublicationsCount(entry)) { + outputAggregationNode.set(DOC_COUNT, entry.getValue().get(UNIQUE_PUBLICATIONS).get(VALUE)); + } else { + outputAggregationNode.set(entry.getKey(), entry.getValue()); } - - @JacocoGenerated - private static JsonNode getBucketOrValue(JsonNode node) { - if (node.has(BUCKETS)) { - return node.at(Constants.BUCKETS_PTR); - } - return node; - } - - private static JsonNode formatName(JsonNode nodeEntry) { - var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); - var keyValue = nodeEntry.at(Constants.BUCKETS_KEY_PTR); - outputAggregationNode.set(ENGLISH_CODE, keyValue); - return outputAggregationNode; - } - - private static JsonNode formatLabels(JsonNode value) { - var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); - - Streams.stream(value.fields()) - .map(AggregationFormat::getNormalizedJsonNodeEntry) - .filter(entry -> !DOC_COUNT.equals(entry.getKey())) - .forEach( - node -> { - var keyValue = node.getValue().at(Constants.BUCKETS_KEY_PTR); - outputAggregationNode.set(node.getKey(), keyValue); - }); - return outputAggregationNode; - } - - private static String getNormalizedFieldName(String fieldName) { - return fieldName.replaceFirst(PATTERN_IS_WORD_ENDING_WITH_HASHTAG, EMPTY_STRING); - } - - private static boolean keyIsName(Entry entry) { - return NAME.equals(entry.getKey()); - } - - private static boolean keyIsLabel(Entry entry) { - return LABELS.equals(entry.getKey()); - } - - @JacocoGenerated - private static boolean rootHasUniquePublicationsCount(Entry entry) { - return nonNull(entry.getValue().get(UNIQUE_PUBLICATIONS)) - && nonNull(entry.getValue().get(UNIQUE_PUBLICATIONS).get(VALUE)); - } - - @JacocoGenerated - static final class Constants { - public static final String DOC_COUNT = "doc_count"; - public static final String BUCKETS_PTR = SLASH + BUCKETS; - public static final String KEY_PTR = SLASH + ZERO + SLASH + KEY; - public static final String BUCKETS_KEY_PTR = SLASH + BUCKETS + KEY_PTR; + } + + private static Stream> getAggregationFieldStreams( + JsonNode aggregations, Map definitions) { + return definitions.entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), aggregations.at(entry.getValue()))); + } + + private static Map.Entry getJsonNodeEntry(Map.Entry entry) { + return Map.entry(getNormalizedFieldName(entry.getKey()), getBucketOrValue(entry.getValue())); + } + + private static Map.Entry getNormalizedJsonNodeEntry( + Map.Entry entry) { + return Map.entry(getNormalizedFieldName(entry.getKey()), entry.getValue()); + } + + @JacocoGenerated + private static JsonNode getBucketOrValue(JsonNode node) { + if (node.has(BUCKETS)) { + return node.at(Constants.BUCKETS_PTR); } + return node; + } + + private static JsonNode formatName(JsonNode nodeEntry) { + var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); + var keyValue = nodeEntry.at(Constants.BUCKETS_KEY_PTR); + outputAggregationNode.set(ENGLISH_CODE, keyValue); + return outputAggregationNode; + } + + private static JsonNode formatLabels(JsonNode value) { + var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); + + Streams.stream(value.fields()) + .map(AggregationFormat::getNormalizedJsonNodeEntry) + .filter(entry -> !DOC_COUNT.equals(entry.getKey())) + .forEach( + node -> { + var keyValue = node.getValue().at(Constants.BUCKETS_KEY_PTR); + outputAggregationNode.set(node.getKey(), keyValue); + }); + return outputAggregationNode; + } + + private static String getNormalizedFieldName(String fieldName) { + return fieldName.replaceFirst(PATTERN_IS_WORD_ENDING_WITH_HASHTAG, EMPTY_STRING); + } + + private static boolean keyIsName(Entry entry) { + return NAME.equals(entry.getKey()); + } + + private static boolean keyIsLabel(Entry entry) { + return LABELS.equals(entry.getKey()); + } + + @JacocoGenerated + private static boolean rootHasUniquePublicationsCount(Entry entry) { + return nonNull(entry.getValue().get(UNIQUE_PUBLICATIONS)) + && nonNull(entry.getValue().get(UNIQUE_PUBLICATIONS).get(VALUE)); + } + + @JacocoGenerated + static final class Constants { + public static final String DOC_COUNT = "doc_count"; + public static final String BUCKETS_PTR = SLASH + BUCKETS; + public static final String KEY_PTR = SLASH + ZERO + SLASH + KEY; + public static final String BUCKETS_KEY_PTR = SLASH + BUCKETS + KEY_PTR; + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/AsType.java b/search-commons/src/main/java/no/unit/nva/search/common/AsType.java index 0d197c376..26156d72c 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/AsType.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/AsType.java @@ -28,120 +28,120 @@ @SuppressWarnings({"PMD.ShortMethodName"}) public class AsType & ParameterKey> { - private final String value; - private final K key; - - public AsType(String value, K key) { - this.value = value; - this.key = key; - } - - private static Stream splitParts(String delimiter, String value) { - return Arrays.stream(value.split(delimiter)) - .filter(predicate -> !predicate.isBlank()) - .sequential(); - } - - public T as() { - if (isNull(value)) { - return null; - } - if (getKey().fieldType().equals(ParameterKind.CUSTOM)) { - SearchQuery.logger.debug( - "CUSTOM lacks TypeInfo, use explicit casting if 'String' doesn't cut it."); - } - - return (T) - switch (getKey().fieldType()) { - case DATE -> castDateTime(); - case NUMBER -> castNumber(); - default -> value; - }; - } - - public K getKey() { - return key; - } - - private T castDateTime() { - return ((Class) DateTime.class).cast(asDateTime()); - } - - public DateTime asDateTime() { - return DateTime.parse(value); - } - - private T castNumber() { - return (T) attempt(this::asNumber).orElseThrow(); - } - - public Number asNumber() { - return Integer.parseInt(value); - } - - public boolean isEmpty() { - return isNull(value) || value.isEmpty(); - } - - public boolean contains(Object o) { - return nonNull(value) && value.contains(o.toString()); - } - - /** - * EqualsIgnoreCase. - * - * @param o Object with toString() - * @return equalsIgnoreCase of objects toString() - */ - public boolean equalsIgnoreCase(Object o) { - return nonNull(value) && value.equalsIgnoreCase(o.toString()); - } - - /** - * Split. - * - * @param delimiter regex to split on - * @return The value split, or null. - */ - public String[] split(String delimiter) { - return nonNull(value) ? value.split(delimiter) : null; - } - - /** - * AsSplitStream. - * - * @param delimiter regex to split on - * @return The value as an optional Stream, split by delimiter. - */ - public Stream asSplitStream(String delimiter) { - // Optional null stream, skipping null check - return asStream().flatMap(value -> splitParts(delimiter, value)); - } - - /** - * AsStream. - * - * @return The value as an optional Stream. - */ - public Stream asStream() { - return Optional.ofNullable(value).stream(); - } - - /** - * AsBoolean. - * - * @return False if value is null or FALSE, otherwise True - */ - public Boolean asBoolean() { - return Boolean.parseBoolean(value); - } - - public String asLowerCase() { - return value.toLowerCase(Locale.getDefault()); - } - - @Override - public String toString() { - return nonNull(value) ? value : EMPTY_STRING; - } + private final String value; + private final K key; + + public AsType(String value, K key) { + this.value = value; + this.key = key; + } + + private static Stream splitParts(String delimiter, String value) { + return Arrays.stream(value.split(delimiter)) + .filter(predicate -> !predicate.isBlank()) + .sequential(); + } + + public T as() { + if (isNull(value)) { + return null; + } + if (getKey().fieldType().equals(ParameterKind.CUSTOM)) { + SearchQuery.logger.debug( + "CUSTOM lacks TypeInfo, use explicit casting if 'String' doesn't cut it."); + } + + return (T) + switch (getKey().fieldType()) { + case DATE -> castDateTime(); + case NUMBER -> castNumber(); + default -> value; + }; + } + + public K getKey() { + return key; + } + + private T castDateTime() { + return ((Class) DateTime.class).cast(asDateTime()); + } + + public DateTime asDateTime() { + return DateTime.parse(value); + } + + private T castNumber() { + return (T) attempt(this::asNumber).orElseThrow(); + } + + public Number asNumber() { + return Integer.parseInt(value); + } + + public boolean isEmpty() { + return isNull(value) || value.isEmpty(); + } + + public boolean contains(Object o) { + return nonNull(value) && value.contains(o.toString()); + } + + /** + * EqualsIgnoreCase. + * + * @param o Object with toString() + * @return equalsIgnoreCase of objects toString() + */ + public boolean equalsIgnoreCase(Object o) { + return nonNull(value) && value.equalsIgnoreCase(o.toString()); + } + + /** + * Split. + * + * @param delimiter regex to split on + * @return The value split, or null. + */ + public String[] split(String delimiter) { + return nonNull(value) ? value.split(delimiter) : null; + } + + /** + * AsSplitStream. + * + * @param delimiter regex to split on + * @return The value as an optional Stream, split by delimiter. + */ + public Stream asSplitStream(String delimiter) { + // Optional null stream, skipping null check + return asStream().flatMap(value -> splitParts(delimiter, value)); + } + + /** + * AsStream. + * + * @return The value as an optional Stream. + */ + public Stream asStream() { + return Optional.ofNullable(value).stream(); + } + + /** + * AsBoolean. + * + * @return False if value is null or FALSE, otherwise True + */ + public Boolean asBoolean() { + return Boolean.parseBoolean(value); + } + + public String asLowerCase() { + return value.toLowerCase(Locale.getDefault()); + } + + @Override + public String toString() { + return nonNull(value) ? value : EMPTY_STRING; + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/ContentTypeUtils.java b/search-commons/src/main/java/no/unit/nva/search/common/ContentTypeUtils.java index a9b4a0571..3c424e356 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/ContentTypeUtils.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/ContentTypeUtils.java @@ -2,12 +2,10 @@ import static org.apache.http.HttpHeaders.ACCEPT; +import java.util.Optional; import nva.commons.apigateway.RequestInfo; - import org.apache.http.entity.ContentType; -import java.util.Optional; - public class ContentTypeUtils { public static final String VERSION = "version"; diff --git a/search-commons/src/main/java/no/unit/nva/search/common/OpenSearchClient.java b/search-commons/src/main/java/no/unit/nva/search/common/OpenSearchClient.java index e75ad9b93..ede0afafc 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/OpenSearchClient.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/OpenSearchClient.java @@ -1,29 +1,15 @@ package no.unit.nva.search.common; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.util.stream.Collectors.joining; import static no.unit.nva.auth.AuthorizedBackendClient.AUTHORIZATION_HEADER; import static no.unit.nva.auth.uriretriever.UriRetriever.ACCEPT; import static no.unit.nva.constants.Words.AMPERSAND; import static no.unit.nva.constants.Words.CONTENT_TYPE; - import static nva.commons.core.attempt.Try.attempt; -import static java.net.HttpURLConnection.HTTP_OK; -import static java.util.stream.Collectors.joining; - import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.net.MediaType; - -import no.unit.nva.commons.json.JsonSerializable; -import no.unit.nva.search.common.jwt.CachedJwtProvider; -import no.unit.nva.search.common.records.QueryContentWrapper; -import no.unit.nva.search.common.records.ResponseLogInfo; -import no.unit.nva.search.common.records.SwsResponse; - -import nva.commons.core.attempt.FunctionWithException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -35,6 +21,14 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.BinaryOperator; +import no.unit.nva.commons.json.JsonSerializable; +import no.unit.nva.search.common.jwt.CachedJwtProvider; +import no.unit.nva.search.common.records.QueryContentWrapper; +import no.unit.nva.search.common.records.ResponseLogInfo; +import no.unit.nva.search.common.records.SwsResponse; +import nva.commons.core.attempt.FunctionWithException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Abstract class for OpenSearch clients. diff --git a/search-commons/src/main/java/no/unit/nva/search/common/ParameterValidator.java b/search-commons/src/main/java/no/unit/nva/search/common/ParameterValidator.java index 13a96e9d4..45e2f5266 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/ParameterValidator.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/ParameterValidator.java @@ -1,5 +1,6 @@ package no.unit.nva.search.common; +import static java.util.Objects.isNull; import static no.unit.nva.constants.ErrorMessages.RELEVANCE_SEARCH_AFTER_ARE_MUTUAL_EXCLUSIVE; import static no.unit.nva.constants.ErrorMessages.requiredMissingMessage; import static no.unit.nva.constants.ErrorMessages.validQueryParameterNamesMessage; @@ -10,21 +11,8 @@ import static no.unit.nva.search.common.ContentTypeUtils.extractContentTypeFromRequestInfo; import static no.unit.nva.search.common.constant.Functions.decodeUTF; import static no.unit.nva.search.common.constant.Functions.mergeWithColonOrComma; - import static nva.commons.core.StringUtils.EMPTY_STRING; -import static java.util.Objects.isNull; - -import no.unit.nva.search.common.constant.Patterns; -import no.unit.nva.search.common.enums.ParameterKey; -import no.unit.nva.search.common.enums.ValueEncoding; - -import nva.commons.apigateway.RequestInfo; -import nva.commons.apigateway.exceptions.BadRequestException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.URI; import java.util.Arrays; import java.util.Collection; @@ -33,6 +21,13 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import no.unit.nva.search.common.constant.Patterns; +import no.unit.nva.search.common.enums.ParameterKey; +import no.unit.nva.search.common.enums.ValueEncoding; +import nva.commons.apigateway.RequestInfo; +import nva.commons.apigateway.exceptions.BadRequestException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Builder for OpenSearchQuery. diff --git a/search-commons/src/main/java/no/unit/nva/search/common/Query.java b/search-commons/src/main/java/no/unit/nva/search/common/Query.java index a4f62da62..31cebf80b 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/Query.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/Query.java @@ -2,13 +2,12 @@ import static no.unit.nva.search.common.constant.Functions.readSearchInfrastructureApiUri; -import no.unit.nva.search.common.enums.ParameterKey; -import no.unit.nva.search.common.records.HttpResponseFormatter; -import no.unit.nva.search.common.records.QueryContentWrapper; - import java.net.URI; import java.time.Instant; import java.util.stream.Stream; +import no.unit.nva.search.common.enums.ParameterKey; +import no.unit.nva.search.common.records.HttpResponseFormatter; +import no.unit.nva.search.common.records.QueryContentWrapper; /** * Query is a class that represents a query to the search service. diff --git a/search-commons/src/main/java/no/unit/nva/search/common/QueryFilter.java b/search-commons/src/main/java/no/unit/nva/search/common/QueryFilter.java index b61037f96..d9cfb1204 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/QueryFilter.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/QueryFilter.java @@ -2,14 +2,13 @@ import static no.unit.nva.constants.Words.COMMA; -import org.opensearch.index.query.BoolQueryBuilder; -import org.opensearch.index.query.QueryBuilder; -import org.opensearch.index.query.QueryBuilders; - import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; /** * QueryFilter is a class that represents a query filter to the search service. diff --git a/search-commons/src/main/java/no/unit/nva/search/common/QueryKeys.java b/search-commons/src/main/java/no/unit/nva/search/common/QueryKeys.java index 2ebd555d9..4c3fb2200 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/QueryKeys.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/QueryKeys.java @@ -1,14 +1,10 @@ package no.unit.nva.search.common; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.Words.PLUS; import static no.unit.nva.constants.Words.SPACE; import static no.unit.nva.search.common.constant.Functions.decodeUTF; -import static java.util.Objects.nonNull; - -import no.unit.nva.search.common.enums.ParameterKey; -import no.unit.nva.search.common.enums.ValueEncoding; - import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; @@ -16,6 +12,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; +import no.unit.nva.search.common.enums.ParameterKey; +import no.unit.nva.search.common.enums.ValueEncoding; /** * This class operates on the queryKeys that a request provides. diff --git a/search-commons/src/main/java/no/unit/nva/search/common/SearchQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/SearchQuery.java index d3c8c5393..db07e7a96 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/SearchQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/SearchQuery.java @@ -2,7 +2,7 @@ import static com.google.common.net.MediaType.CSV_UTF_8; import static com.google.common.net.MediaType.JSON_UTF_8; - +import static java.util.Objects.nonNull; import static no.unit.nva.constants.Defaults.DEFAULT_SORT_ORDER; import static no.unit.nva.constants.Defaults.ZERO_RESULTS_AGGREGATION_ONLY; import static no.unit.nva.constants.Words.ALL; @@ -15,13 +15,19 @@ import static no.unit.nva.search.common.constant.Patterns.COLON_OR_SPACE; import static no.unit.nva.search.common.enums.FieldOperator.NOT_ALL_OF; import static no.unit.nva.search.common.enums.FieldOperator.NOT_ANY_OF; - import static nva.commons.core.attempt.Try.attempt; -import static java.util.Objects.nonNull; - import com.google.common.net.MediaType; - +import java.net.URI; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.constants.ErrorMessages; import no.unit.nva.constants.Words; import no.unit.nva.search.common.builder.AcrossFieldsQuery; @@ -38,10 +44,8 @@ import no.unit.nva.search.common.records.HttpResponseFormatter; import no.unit.nva.search.common.records.QueryContentWrapper; import no.unit.nva.search.common.records.SwsResponse; - import nva.commons.apigateway.AccessRight; import nva.commons.core.JacocoGenerated; - import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.MultiMatchQueryBuilder.Type; import org.opensearch.index.query.Operator; @@ -56,17 +60,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.URI; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * SearchQuery is a class that represents a query to the search service. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/AbstractBuilder.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/AbstractBuilder.java index 1e63635f8..f9bbf9bdd 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/AbstractBuilder.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/AbstractBuilder.java @@ -5,17 +5,14 @@ import static no.unit.nva.search.common.enums.FieldOperator.BETWEEN; import static no.unit.nva.search.common.enums.FieldOperator.NOT_ANY_OF; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; import no.unit.nva.search.common.enums.ParameterKey; - import nva.commons.core.JacocoGenerated; - import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * Abstract class for building OpenSearch queries. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/AcrossFieldsQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/AcrossFieldsQuery.java index efda49949..682fab2fe 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/AcrossFieldsQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/AcrossFieldsQuery.java @@ -2,19 +2,17 @@ import static no.unit.nva.constants.Words.KEYWORD_FALSE; +import java.util.Arrays; +import java.util.Map.Entry; +import java.util.stream.Stream; import no.unit.nva.search.common.constant.Functions; import no.unit.nva.search.common.enums.ParameterKey; - import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.MultiMatchQueryBuilder; import org.opensearch.index.query.Operator; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; -import java.util.Arrays; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that search across multiple fields. * @@ -24,43 +22,36 @@ */ public class AcrossFieldsQuery & ParameterKey> extends AbstractBuilder { - public static final String ANY_VALUE = "AcrossFieldsAnyValue"; - public static final String ALL_VALUES = "AcrossFieldsAllValues"; - - @Override - protected Stream> buildMatchAnyValueQuery(K key, String... values) { - return Functions.queryToEntry( - key, - buildMultiMatchQueryStream(key, values) - .collect( - BoolQueryBuilder::new, - BoolQueryBuilder::should, - BoolQueryBuilder::should) - .minimumShouldMatch(1) - .queryName(ANY_VALUE + key.asCamelCase())); - } - - @Override - protected Stream> buildMatchAllValuesQuery(K key, String... values) { - return Functions.queryToEntry( - key, - buildMultiMatchQueryStream(key, values) - .collect( - BoolQueryBuilder::new, - BoolQueryBuilder::must, - BoolQueryBuilder::must) - .queryName(ALL_VALUES + key.asCamelCase())); - } - - private Stream buildMultiMatchQueryStream(K key, String... values) { - return Arrays.stream(values) - .map(singleValue -> getMultiMatchQueryBuilder(singleValue, key)); - } - - private QueryBuilder getMultiMatchQueryBuilder(String value, K key) { - final var searchFields = key.searchFields(KEYWORD_FALSE).toArray(String[]::new); - return QueryBuilders.multiMatchQuery(value, searchFields) - .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) - .operator(Operator.AND); - } + public static final String ANY_VALUE = "AcrossFieldsAnyValue"; + public static final String ALL_VALUES = "AcrossFieldsAllValues"; + + @Override + protected Stream> buildMatchAnyValueQuery(K key, String... values) { + return Functions.queryToEntry( + key, + buildMultiMatchQueryStream(key, values) + .collect(BoolQueryBuilder::new, BoolQueryBuilder::should, BoolQueryBuilder::should) + .minimumShouldMatch(1) + .queryName(ANY_VALUE + key.asCamelCase())); + } + + @Override + protected Stream> buildMatchAllValuesQuery(K key, String... values) { + return Functions.queryToEntry( + key, + buildMultiMatchQueryStream(key, values) + .collect(BoolQueryBuilder::new, BoolQueryBuilder::must, BoolQueryBuilder::must) + .queryName(ALL_VALUES + key.asCamelCase())); + } + + private Stream buildMultiMatchQueryStream(K key, String... values) { + return Arrays.stream(values).map(singleValue -> getMultiMatchQueryBuilder(singleValue, key)); + } + + private QueryBuilder getMultiMatchQueryBuilder(String value, K key) { + final var searchFields = key.searchFields(KEYWORD_FALSE).toArray(String[]::new); + return QueryBuilders.multiMatchQuery(value, searchFields) + .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) + .operator(Operator.AND); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/ExistsQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/ExistsQuery.java index bee0c9ec7..cddf36dd7 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/ExistsQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/ExistsQuery.java @@ -2,21 +2,17 @@ import static no.unit.nva.constants.Words.KEYWORD_FALSE; import static no.unit.nva.search.common.constant.Functions.queryToEntry; - import static org.opensearch.index.query.QueryBuilders.boolQuery; import static org.opensearch.index.query.QueryBuilders.existsQuery; +import java.util.Map.Entry; +import java.util.stream.Stream; import no.unit.nva.search.common.enums.ParameterKey; - import nva.commons.core.JacocoGenerated; - import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.ExistsQueryBuilder; import org.opensearch.index.query.QueryBuilder; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that check if a field exists. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/FuzzyKeywordQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/FuzzyKeywordQuery.java index df1218d2c..0568767f1 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/FuzzyKeywordQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/FuzzyKeywordQuery.java @@ -27,80 +27,73 @@ */ public class FuzzyKeywordQuery & ParameterKey> extends AbstractBuilder { - public static final String KEYWORD_ANY = "FuzzyKeywordAny-"; - public static final String KEYWORD_ALL = "FuzzyKeywordAll-"; + public static final String KEYWORD_ANY = "FuzzyKeywordAny-"; + public static final String KEYWORD_ALL = "FuzzyKeywordAll-"; - @Override - protected Stream> buildMatchAnyValueQuery(K key, String... values) { - var boolQuery = - QueryBuilders.boolQuery() - .should(buildMatchAnyKeyword(key, values)) - .must(buildMatchAnyFuzzy(key, values)) - .queryName(KEYWORD_ANY + key.asCamelCase()); - return Functions.queryToEntry(key, boolQuery); - } + @Override + protected Stream> buildMatchAnyValueQuery(K key, String... values) { + var boolQuery = + QueryBuilders.boolQuery() + .should(buildMatchAnyKeyword(key, values)) + .must(buildMatchAnyFuzzy(key, values)) + .queryName(KEYWORD_ANY + key.asCamelCase()); + return Functions.queryToEntry(key, boolQuery); + } - @Override - protected Stream> buildMatchAllValuesQuery(K key, String... values) { - var boolQuery = QueryBuilders.boolQuery().queryName(KEYWORD_ALL + key.asCamelCase()); - buildMatchAllKeyword(key, values).forEach(boolQuery::should); - buildMatchAllFuzzy(key, values).forEach(boolQuery::must); + @Override + protected Stream> buildMatchAllValuesQuery(K key, String... values) { + var boolQuery = QueryBuilders.boolQuery().queryName(KEYWORD_ALL + key.asCamelCase()); + buildMatchAllKeyword(key, values).forEach(boolQuery::should); + buildMatchAllFuzzy(key, values).forEach(boolQuery::must); - return Functions.queryToEntry(key, boolQuery); - } + return Functions.queryToEntry(key, boolQuery); + } - private DisMaxQueryBuilder buildMatchAnyKeyword(K key, String... values) { - return key.searchFields(KEYWORD_TRUE) - .map(searchField -> new TermsQueryBuilder(searchField, values)) - .collect(DisMaxQueryBuilder::new, DisMaxQueryBuilder::add, DisMaxQueryBuilder::add) - .boost(key.fieldBoost()); - } + private DisMaxQueryBuilder buildMatchAnyKeyword(K key, String... values) { + return key.searchFields(KEYWORD_TRUE) + .map(searchField -> new TermsQueryBuilder(searchField, values)) + .collect(DisMaxQueryBuilder::new, DisMaxQueryBuilder::add, DisMaxQueryBuilder::add) + .boost(key.fieldBoost()); + } - private DisMaxQueryBuilder buildMatchAnyFuzzy(K key, String... values) { - return Arrays.stream(values) - .map(value -> getMultiMatchQueryBuilder(value, key)) - .collect(DisMaxQueryBuilder::new, DisMaxQueryBuilder::add, DisMaxQueryBuilder::add); - } + private DisMaxQueryBuilder buildMatchAnyFuzzy(K key, String... values) { + return Arrays.stream(values) + .map(value -> getMultiMatchQueryBuilder(value, key)) + .collect(DisMaxQueryBuilder::new, DisMaxQueryBuilder::add, DisMaxQueryBuilder::add); + } - private QueryBuilder getMultiMatchQueryBuilder(String value, K key) { - final var searchFields = key.searchFields(KEYWORD_FALSE).toArray(String[]::new); - return QueryBuilders.multiMatchQuery(value, searchFields) - .fuzziness(Fuzziness.ZERO) - .maxExpansions(10) - .operator(Operator.AND); - } + private QueryBuilder getMultiMatchQueryBuilder(String value, K key) { + final var searchFields = key.searchFields(KEYWORD_FALSE).toArray(String[]::new); + return QueryBuilders.multiMatchQuery(value, searchFields) + .fuzziness(Fuzziness.ZERO) + .maxExpansions(10) + .operator(Operator.AND); + } - private Stream buildMatchAllKeyword(K key, String... values) { - return Arrays.stream(values) - .map( - value -> - key.searchFields(KEYWORD_TRUE) - .map( - searchField -> - new TermQueryBuilder(searchField, value)) - .collect( - DisMaxQueryBuilder::new, - DisMaxQueryBuilder::add, - DisMaxQueryBuilder::add) - .boost(key.fieldBoost())); - } + private Stream buildMatchAllKeyword(K key, String... values) { + return Arrays.stream(values) + .map( + value -> + key.searchFields(KEYWORD_TRUE) + .map(searchField -> new TermQueryBuilder(searchField, value)) + .collect( + DisMaxQueryBuilder::new, DisMaxQueryBuilder::add, DisMaxQueryBuilder::add) + .boost(key.fieldBoost())); + } - private Stream buildMatchAllFuzzy(K key, String... values) { - return Arrays.stream(values) - .map( - singleValue -> - getMatchPhrasePrefixBuilderStream(singleValue, key) - .collect( - DisMaxQueryBuilder::new, - DisMaxQueryBuilder::add, - DisMaxQueryBuilder::add)); - } + private Stream buildMatchAllFuzzy(K key, String... values) { + return Arrays.stream(values) + .map( + singleValue -> + getMatchPhrasePrefixBuilderStream(singleValue, key) + .collect( + DisMaxQueryBuilder::new, DisMaxQueryBuilder::add, DisMaxQueryBuilder::add)); + } - private Stream getMatchPhrasePrefixBuilderStream(String singleValue, K key) { - return key.searchFields(KEYWORD_FALSE) - .map( - fieldName -> - QueryBuilders.matchPhrasePrefixQuery(fieldName, singleValue) - .maxExpansions(10)); - } + private Stream getMatchPhrasePrefixBuilderStream(String singleValue, K key) { + return key.searchFields(KEYWORD_FALSE) + .map( + fieldName -> + QueryBuilders.matchPhrasePrefixQuery(fieldName, singleValue).maxExpansions(10)); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/HasPartsQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/HasPartsQuery.java index 830e0bf45..b928873ed 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/HasPartsQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/HasPartsQuery.java @@ -2,18 +2,15 @@ import static no.unit.nva.constants.Words.PART_OF; +import java.util.Map; +import java.util.stream.Stream; import no.unit.nva.search.common.constant.Functions; import no.unit.nva.search.common.enums.ParameterKey; - import nva.commons.core.JacocoGenerated; - import org.apache.lucene.search.join.ScoreMode; import org.opensearch.index.query.QueryBuilder; import org.opensearch.join.query.HasChildQueryBuilder; -import java.util.Map; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that search for parts of a document. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/KeywordQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/KeywordQuery.java index 98b1e8dc9..29df58f4e 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/KeywordQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/KeywordQuery.java @@ -2,19 +2,17 @@ import static no.unit.nva.constants.Words.KEYWORD_TRUE; +import java.util.Arrays; +import java.util.Map.Entry; +import java.util.stream.Stream; import no.unit.nva.search.common.constant.Functions; import no.unit.nva.search.common.enums.ParameterKey; - import org.opensearch.index.query.DisMaxQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.index.query.TermQueryBuilder; import org.opensearch.index.query.TermsQueryBuilder; -import java.util.Arrays; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that search for keywords. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/PartOfQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/PartOfQuery.java index f1330444f..510dcd090 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/PartOfQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/PartOfQuery.java @@ -2,15 +2,13 @@ import static no.unit.nva.constants.Words.HAS_PARTS; +import java.util.Map; +import java.util.stream.Stream; import no.unit.nva.search.common.constant.Functions; import no.unit.nva.search.common.enums.ParameterKey; - import org.opensearch.index.query.QueryBuilder; import org.opensearch.join.query.HasParentQueryBuilder; -import java.util.Map; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that search for parts of a document. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/RangeQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/RangeQuery.java index f01ac71ea..bcdf799da 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/RangeQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/RangeQuery.java @@ -2,20 +2,17 @@ import static no.unit.nva.constants.ErrorMessages.OPERATOR_NOT_SUPPORTED; +import java.util.Map.Entry; +import java.util.stream.Stream; import no.unit.nva.search.common.constant.Functions; import no.unit.nva.search.common.enums.ParameterKey; import no.unit.nva.search.common.enums.ParameterKind; - import nva.commons.core.JacocoGenerated; - import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that search for a range of values. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/builder/TextQuery.java b/search-commons/src/main/java/no/unit/nva/search/common/builder/TextQuery.java index 8cb360c8a..14d762577 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/builder/TextQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/builder/TextQuery.java @@ -1,13 +1,14 @@ package no.unit.nva.search.common.builder; import static no.unit.nva.constants.Words.KEYWORD_FALSE; - import static org.opensearch.index.query.QueryBuilders.matchPhrasePrefixQuery; import static org.opensearch.index.query.QueryBuilders.matchQuery; +import java.util.Arrays; +import java.util.Map.Entry; +import java.util.stream.Stream; import no.unit.nva.search.common.constant.Functions; import no.unit.nva.search.common.enums.ParameterKey; - import org.opensearch.index.query.DisMaxQueryBuilder; import org.opensearch.index.query.MatchPhrasePrefixQueryBuilder; import org.opensearch.index.query.MatchQueryBuilder; @@ -15,10 +16,6 @@ import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; -import java.util.Arrays; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that search for text. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/constant/Functions.java b/search-commons/src/main/java/no/unit/nva/search/common/constant/Functions.java index bece08caa..5d2801202 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/constant/Functions.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/constant/Functions.java @@ -1,5 +1,6 @@ package no.unit.nva.search.common.constant; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.Defaults.ENVIRONMENT; import static no.unit.nva.constants.Words.BOKMAAL_CODE; import static no.unit.nva.constants.Words.COLON; @@ -14,16 +15,19 @@ import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_ASC_DESC_VALUE; import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_ASC_OR_DESC_GROUP; import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_SELECTED_GROUP; - import static nva.commons.core.StringUtils.SPACE; -import static java.util.Objects.nonNull; - +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.constants.Defaults; import no.unit.nva.search.common.enums.ParameterKey; - import nva.commons.core.JacocoGenerated; - import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.search.aggregations.AggregationBuilders; @@ -31,15 +35,6 @@ import org.opensearch.search.aggregations.bucket.nested.NestedAggregationBuilder; import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Class for building OpenSearch queries that search across multiple fields. * @@ -47,123 +42,122 @@ */ public final class Functions { - private static final String SEARCH_INFRASTRUCTURE_AUTH_URI = "SEARCH_INFRASTRUCTURE_AUTH_URI"; - private static final String SEARCH_INFRASTRUCTURE_API_URI = "SEARCH_INFRASTRUCTURE_API_URI"; - private static final String API_HOST = "API_HOST"; - - @JacocoGenerated - public Functions() {} - - public static String jsonPath(String... args) { - return String.join(DOT, args); - } - - @JacocoGenerated - public static String readSearchInfrastructureAuthUri() { - return ENVIRONMENT.readEnv(SEARCH_INFRASTRUCTURE_AUTH_URI); - } - - private static Stream languageCodes() { - return Stream.of(BOKMAAL_CODE, ENGLISH_CODE, NYNORSK_CODE, SAMI_CODE); - } - - public static String readSearchInfrastructureApiUri() { - return ENVIRONMENT.readEnv(SEARCH_INFRASTRUCTURE_API_URI); - } - - public static String readApiHost() { - return ENVIRONMENT.readEnv(API_HOST); - } - - public static NestedAggregationBuilder labels(String jsonPath) { - var nestedAggregation = nestedBranchBuilder(LABELS, jsonPath, LABELS); - - languageCodes() - .map(code -> branchBuilder(code, jsonPath, LABELS, code, KEYWORD)) - .forEach(nestedAggregation::subAggregation); - - return nestedAggregation; - } - - public static TermsAggregationBuilder branchBuilder(String name, String... pathElements) { - return AggregationBuilders.terms(name) - .field(jsonPath(pathElements)) - .size(Defaults.DEFAULT_AGGREGATION_SIZE); - } - - public static NestedAggregationBuilder nestedBranchBuilder( - String name, String... pathElements) { - return new NestedAggregationBuilder(name, jsonPath(pathElements)); - } - - public static FilterAggregationBuilder filterBranchBuilder( - String name, String filter, String... paths) { - return AggregationBuilders.filter(name, QueryBuilders.termQuery(jsonPath(paths), filter)); - } - - public static FilterAggregationBuilder filterBranchBuilder( - String name, QueryBuilder queryBuilder) { - return AggregationBuilders.filter(name, queryBuilder); - } - - public static String mergeWithColonOrComma(String oldValue, String newValue) { - if (nonNull(oldValue)) { - var delimiter = newValue.matches(PATTERN_IS_ASC_DESC_VALUE) ? COLON : COMMA; - return String.join(delimiter, oldValue, newValue); - } else { - return newValue; - } - } - - public static String trimSpace(String value) { - return value.replaceAll(PATTERN_IS_ASC_OR_DESC_GROUP, PATTERN_IS_SELECTED_GROUP); - } - - public static String toEnumStrings(Function> fromString, String decodedValue) { - return Arrays.stream(decodedValue.split(COMMA + PIPE + SPACE)) - .map(fromString) - .map(Enum::toString) - .collect(Collectors.joining(COMMA)); - } - - public static String multipleFields(String... values) { - return String.join(PIPE, values); - } - - @JacocoGenerated - public static boolean hasContent(String value) { - return nonNull(value) && !value.isEmpty(); - } - - @JacocoGenerated - public static boolean hasContent(Collection value) { - return nonNull(value) && !value.isEmpty(); - } - - public static String decodeUTF(String encoded) { - return URLDecoder.decode(encoded, StandardCharsets.UTF_8); - } - - public static & ParameterKey> - Stream> queryToEntry(K key, QueryBuilder qb) { - final var entry = - new Map.Entry() { - @Override - public K getKey() { - return key; - } - - @Override - public QueryBuilder getValue() { - return qb; - } - - @Override - @JacocoGenerated - public QueryBuilder setValue(QueryBuilder value) { - return null; - } - }; - return Stream.of(entry); - } + private static final String SEARCH_INFRASTRUCTURE_AUTH_URI = "SEARCH_INFRASTRUCTURE_AUTH_URI"; + private static final String SEARCH_INFRASTRUCTURE_API_URI = "SEARCH_INFRASTRUCTURE_API_URI"; + private static final String API_HOST = "API_HOST"; + + @JacocoGenerated + public Functions() {} + + public static String jsonPath(String... args) { + return String.join(DOT, args); + } + + @JacocoGenerated + public static String readSearchInfrastructureAuthUri() { + return ENVIRONMENT.readEnv(SEARCH_INFRASTRUCTURE_AUTH_URI); + } + + private static Stream languageCodes() { + return Stream.of(BOKMAAL_CODE, ENGLISH_CODE, NYNORSK_CODE, SAMI_CODE); + } + + public static String readSearchInfrastructureApiUri() { + return ENVIRONMENT.readEnv(SEARCH_INFRASTRUCTURE_API_URI); + } + + public static String readApiHost() { + return ENVIRONMENT.readEnv(API_HOST); + } + + public static NestedAggregationBuilder labels(String jsonPath) { + var nestedAggregation = nestedBranchBuilder(LABELS, jsonPath, LABELS); + + languageCodes() + .map(code -> branchBuilder(code, jsonPath, LABELS, code, KEYWORD)) + .forEach(nestedAggregation::subAggregation); + + return nestedAggregation; + } + + public static TermsAggregationBuilder branchBuilder(String name, String... pathElements) { + return AggregationBuilders.terms(name) + .field(jsonPath(pathElements)) + .size(Defaults.DEFAULT_AGGREGATION_SIZE); + } + + public static NestedAggregationBuilder nestedBranchBuilder(String name, String... pathElements) { + return new NestedAggregationBuilder(name, jsonPath(pathElements)); + } + + public static FilterAggregationBuilder filterBranchBuilder( + String name, String filter, String... paths) { + return AggregationBuilders.filter(name, QueryBuilders.termQuery(jsonPath(paths), filter)); + } + + public static FilterAggregationBuilder filterBranchBuilder( + String name, QueryBuilder queryBuilder) { + return AggregationBuilders.filter(name, queryBuilder); + } + + public static String mergeWithColonOrComma(String oldValue, String newValue) { + if (nonNull(oldValue)) { + var delimiter = newValue.matches(PATTERN_IS_ASC_DESC_VALUE) ? COLON : COMMA; + return String.join(delimiter, oldValue, newValue); + } else { + return newValue; + } + } + + public static String trimSpace(String value) { + return value.replaceAll(PATTERN_IS_ASC_OR_DESC_GROUP, PATTERN_IS_SELECTED_GROUP); + } + + public static String toEnumStrings(Function> fromString, String decodedValue) { + return Arrays.stream(decodedValue.split(COMMA + PIPE + SPACE)) + .map(fromString) + .map(Enum::toString) + .collect(Collectors.joining(COMMA)); + } + + public static String multipleFields(String... values) { + return String.join(PIPE, values); + } + + @JacocoGenerated + public static boolean hasContent(String value) { + return nonNull(value) && !value.isEmpty(); + } + + @JacocoGenerated + public static boolean hasContent(Collection value) { + return nonNull(value) && !value.isEmpty(); + } + + public static String decodeUTF(String encoded) { + return URLDecoder.decode(encoded, StandardCharsets.UTF_8); + } + + public static & ParameterKey> + Stream> queryToEntry(K key, QueryBuilder qb) { + final var entry = + new Map.Entry() { + @Override + public K getKey() { + return key; + } + + @Override + public QueryBuilder getValue() { + return qb; + } + + @Override + @JacocoGenerated + public QueryBuilder setValue(QueryBuilder value) { + return null; + } + }; + return Stream.of(entry); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/constant/Patterns.java b/search-commons/src/main/java/no/unit/nva/search/common/constant/Patterns.java index 2ec7a67fa..f92c2a48e 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/constant/Patterns.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/constant/Patterns.java @@ -13,98 +13,96 @@ */ public final class Patterns { - public static final String COLON_OR_SPACE = COLON + PIPE + SPACE; + public static final String COLON_OR_SPACE = COLON + PIPE + SPACE; - /** Pattern for ignoring case */ - public static final String PATTERN_IS_IGNORE_CASE = "(?i)"; + /** Pattern for ignoring case */ + public static final String PATTERN_IS_IGNORE_CASE = "(?i)"; - /** Pattern for matching a word ending with a hashtag */ - public static final String PATTERN_IS_WORD_ENDING_WITH_HASHTAG = "[A-za-z0-9]*#"; + /** Pattern for matching a word ending with a hashtag */ + public static final String PATTERN_IS_WORD_ENDING_WITH_HASHTAG = "[A-za-z0-9]*#"; - /** Pattern for matching asc or desc */ - public static final String PATTERN_IS_ASC_DESC_VALUE = "(?i)asc|desc"; + /** Pattern for matching asc or desc */ + public static final String PATTERN_IS_ASC_DESC_VALUE = "(?i)asc|desc"; - /** Pattern for matching asc or desc */ - public static final String PATTERN_IS_ASC_OR_DESC_GROUP = "(?i) (asc|desc)"; + /** Pattern for matching asc or desc */ + public static final String PATTERN_IS_ASC_OR_DESC_GROUP = "(?i) (asc|desc)"; - /** Pattern for matching category keys */ - public static final String PATTERN_IS_CATEGORY_KEYS = - "(?i)(type|instance_?type|category)(_?should)?$"; + /** Pattern for matching category keys */ + public static final String PATTERN_IS_CATEGORY_KEYS = + "(?i)(type|instance_?type|category)(_?should)?$"; - /** Pattern for matching category not keys */ - public static final String PATTERN_IS_CATEGORY_NOT_KEYS = - "(?i)(type_?not|instance_?type_?not|category_?not)$"; + /** Pattern for matching category not keys */ + public static final String PATTERN_IS_CATEGORY_NOT_KEYS = + "(?i)(type_?not|instance_?type_?not|category_?not)$"; - /** Pattern for matching a boolean */ - public static final String PATTERN_IS_BOOLEAN = "(?i)(true|false)"; + /** Pattern for matching a boolean */ + public static final String PATTERN_IS_BOOLEAN = "(?i)(true|false)"; - /** - * Pattern for matching a date string. yyyy | yyyy-MM-dd | yyyy-MM-ddTHH:mm:ssZ | - * yyyy-MM-ddTHH:mm:ss.SSSZ - */ - public static final String PATTERN_IS_DATE = - "\\d{4}(-\\d{2}(-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?)?)?)?"; + /** + * Pattern for matching a date string. yyyy | yyyy-MM-dd | yyyy-MM-ddTHH:mm:ssZ | + * yyyy-MM-ddTHH:mm:ss.SSSZ + */ + public static final String PATTERN_IS_DATE = + "\\d{4}(-\\d{2}(-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?)?)?)?"; - /** Pattern for matching from key */ - public static final String PATTERN_IS_FROM_KEY = "(?i)offset|from"; + /** Pattern for matching from key */ + public static final String PATTERN_IS_FROM_KEY = "(?i)offset|from"; - /** Pattern for matching fields searched */ - public static final String PATTERN_IS_FIELDS_SEARCHED = "(?i)fields|nodes_?searched"; + /** Pattern for matching fields searched */ + public static final String PATTERN_IS_FIELDS_SEARCHED = "(?i)fields|nodes_?searched"; - /** Pattern for matching organization */ - public static final String PATTERN_IS_ORGANIZATION = "(?i)(organization_?id|viewing_?scope)$"; + /** Pattern for matching organization */ + public static final String PATTERN_IS_ORGANIZATION = "(?i)(organization_?id|viewing_?scope)$"; - /** Pattern for matching funding identifier */ - public static final String PATTERN_IS_FUNDING_IDENTIFIER = - "(?i)(funding_?identifier|grant_?id)$"; + /** Pattern for matching funding identifier */ + public static final String PATTERN_IS_FUNDING_IDENTIFIER = "(?i)(funding_?identifier|grant_?id)$"; - /** Pattern for matching funding identifier not */ - public static final String PATTERN_IS_FUNDING_IDENTIFIER_NOT = - "(?i)(funding_?identifier_?not|grant_?id_?not)$"; + /** Pattern for matching funding identifier not */ + public static final String PATTERN_IS_FUNDING_IDENTIFIER_NOT = + "(?i)(funding_?identifier_?not|grant_?id_?not)$"; - /** Pattern for matching funding identifier should */ - public static final String PATTERN_IS_FUNDING_IDENTIFIER_SHOULD = - "(?i)(funding_?identifier_?should|grant_?id_?should)$"; + /** Pattern for matching funding identifier should */ + public static final String PATTERN_IS_FUNDING_IDENTIFIER_SHOULD = + "(?i)(funding_?identifier_?should|grant_?id_?should)$"; - /** Pattern for matching none or one */ - public static final String PATTERN_IS_NONE_OR_ONE = ".?"; + /** Pattern for matching none or one */ + public static final String PATTERN_IS_NONE_OR_ONE = ".?"; - /** Pattern for matching non empty */ - public static final String PATTERN_IS_NON_EMPTY = ".+"; + /** Pattern for matching non empty */ + public static final String PATTERN_IS_NON_EMPTY = ".+"; - /** Pattern for matching a number */ - public static final String PATTERN_IS_NUMBER = "[0-9]\\d*"; + /** Pattern for matching a number */ + public static final String PATTERN_IS_NUMBER = "[0-9]\\d*"; - /** Pattern for matching search all key */ - public static final String PATTERN_IS_SEARCH_ALL_KEY = "(?i)search_?all|query"; + /** Pattern for matching search all key */ + public static final String PATTERN_IS_SEARCH_ALL_KEY = "(?i)search_?all|query"; - /** Pattern for matching selected group */ - public static final String PATTERN_IS_SELECTED_GROUP = ":$1"; + /** Pattern for matching selected group */ + public static final String PATTERN_IS_SELECTED_GROUP = ":$1"; - /** Pattern for matching size key */ - public static final String PATTERN_IS_SIZE_KEY = "(?i)per_?page|results|limit|size"; + /** Pattern for matching size key */ + public static final String PATTERN_IS_SIZE_KEY = "(?i)per_?page|results|limit|size"; - /** Pattern for matching sort key */ - public static final String PATTERN_IS_SORT_KEY = "(?i)order_?by|sort"; + /** Pattern for matching sort key */ + public static final String PATTERN_IS_SORT_KEY = "(?i)order_?by|sort"; - /** Pattern for matching sort order key */ - public static final String PATTERN_IS_SORT_ORDER_KEY = "(?i)sort_?order|order"; + /** Pattern for matching sort order key */ + public static final String PATTERN_IS_SORT_ORDER_KEY = "(?i)sort_?order|order"; - /** Pattern for matching a URI */ - public static final String PATTERN_IS_URI = "https?://[^\\s/$.?#].[^\\s]*"; + /** Pattern for matching a URI */ + public static final String PATTERN_IS_URI = "https?://[^\\s/$.?#].[^\\s]*"; - /** Pattern for matching a pipe */ - public static final String PATTERN_IS_PIPE = "\\|"; + /** Pattern for matching a pipe */ + public static final String PATTERN_IS_PIPE = "\\|"; - /** - * Pattern for matching a funding string. funding source and project_id together separated by - * ':' - */ - public static final String PATTERN_IS_FUNDING = "[\\w]+[:\\s]{1}.+"; + /** + * Pattern for matching a funding string. funding source and project_id together separated by ':' + */ + public static final String PATTERN_IS_FUNDING = "[\\w]+[:\\s]{1}.+"; - /** Pattern for matching invisible control characters and unused code points. */ - public static final String PATTERN_IS_NON_PRINTABLE_CHARACTERS = "\\p{C}"; + /** Pattern for matching invisible control characters and unused code points. */ + public static final String PATTERN_IS_NON_PRINTABLE_CHARACTERS = "\\p{C}"; - @JacocoGenerated - public Patterns() {} + @JacocoGenerated + public Patterns() {} } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/csv/CsvBindByNameOrder.java b/search-commons/src/main/java/no/unit/nva/search/common/csv/CsvBindByNameOrder.java index 1f8d92faf..b57a016ee 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/csv/CsvBindByNameOrder.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/csv/CsvBindByNameOrder.java @@ -17,5 +17,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CsvBindByNameOrder { - String[] value() default {}; + String[] value() default {}; } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/csv/ExportCsv.java b/search-commons/src/main/java/no/unit/nva/search/common/csv/ExportCsv.java index 655ecf1d9..3329d5151 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/csv/ExportCsv.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/csv/ExportCsv.java @@ -1,12 +1,10 @@ package no.unit.nva.search.common.csv; import com.opencsv.bean.CsvBindByName; - -import nva.commons.core.JacocoGenerated; -import nva.commons.core.StringUtils; - import java.util.List; import java.util.Objects; +import nva.commons.core.JacocoGenerated; +import nva.commons.core.StringUtils; /** * Class for exporting data to CSV. @@ -17,143 +15,139 @@ */ @CsvBindByNameOrder({"url", "title", "publicationDate", "type", "contributors"}) public class ExportCsv { - public static final String DATE_SEPARATOR = "-"; - public static final String DELIMITER = ","; + public static final String DATE_SEPARATOR = "-"; + public static final String DELIMITER = ","; - @CsvBindByName(column = "url") - private String id; - - @CsvBindByName(column = "title") - private String mainTitle; - - @CsvBindByName(column = "publicationDate") - private String publicationDate; - - @CsvBindByName(column = "type") - private String publicationInstance; - - @CsvBindByName(column = "contributors") - private String contributors; - - @JacocoGenerated - public ExportCsv() { - // Bean constructor. - } + @CsvBindByName(column = "url") + private String id; - public ExportCsv( - String id, - String mainTitle, - String year, - String month, - String day, - String publicationInstance, - List contributors) { - this.id = id; - this.mainTitle = mainTitle; - this.publicationDate = createPublicationDate(year, month, day); - this.publicationInstance = publicationInstance; - this.contributors = createContributorsString(contributors); - } - - public String getId() { - return id; - } - - public String getMainTitle() { - return mainTitle; - } - - public String getPublicationDate() { - return publicationDate; - } - - public String getPublicationInstance() { - return publicationInstance; - } - - public String getContributors() { - return contributors; - } + @CsvBindByName(column = "title") + private String mainTitle; + + @CsvBindByName(column = "publicationDate") + private String publicationDate; + + @CsvBindByName(column = "type") + private String publicationInstance; - public ExportCsv withId(String id) { - this.id = id; - return this; - } - - public ExportCsv withMainTitle(String mainTitle) { - this.mainTitle = mainTitle; - return this; - } - - public ExportCsv withPublicationDate(String publicationDate) { - this.publicationDate = publicationDate; - return this; - } - - public ExportCsv withPublicationInstance(String publicationInstance) { - this.publicationInstance = publicationInstance; - return this; - } - - public ExportCsv withContributors(String contributors) { - this.contributors = contributors; - return this; - } - - public final String createPublicationDate(String year, String month, String day) { - var stringBuilder = new StringBuilder(); - - if (StringUtils.isNotBlank(year)) { - stringBuilder.append(year); - } - - if (StringUtils.isNotBlank(month) && StringUtils.isNotBlank(year)) { - stringBuilder.append(DATE_SEPARATOR).append(month); - } - - if (StringUtils.isNotBlank(day) - && StringUtils.isNotBlank(month) - && StringUtils.isNotBlank(year)) { - stringBuilder.append(DATE_SEPARATOR).append(day); - } - return stringBuilder.toString(); - } - - public final String createContributorsString(List contributors) { - return String.join(DELIMITER, contributors); - } - - @Override - @JacocoGenerated - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ExportCsv exportCsv)) { - return false; - } - return Objects.equals(getId(), exportCsv.getId()) - && Objects.equals(getMainTitle(), exportCsv.getMainTitle()) - && Objects.equals(getPublicationDate(), exportCsv.getPublicationDate()) - && Objects.equals(getPublicationInstance(), exportCsv.getPublicationInstance()) - && Objects.equals(getContributors(), exportCsv.getContributors()); - } - - @Override - @JacocoGenerated - public int hashCode() { - return Objects.hash( - getId(), - getMainTitle(), - getPublicationDate(), - getPublicationInstance(), - getContributors()); - } - - @Override - @JacocoGenerated - public String toString() { - return "ExportCsv{id='%s', mainTitle='%s', publicationDate='%s', publicationInstance='%s', contributors='%s'}" - .formatted(id, mainTitle, publicationDate, publicationInstance, contributors); - } + @CsvBindByName(column = "contributors") + private String contributors; + + @JacocoGenerated + public ExportCsv() { + // Bean constructor. + } + + public ExportCsv( + String id, + String mainTitle, + String year, + String month, + String day, + String publicationInstance, + List contributors) { + this.id = id; + this.mainTitle = mainTitle; + this.publicationDate = createPublicationDate(year, month, day); + this.publicationInstance = publicationInstance; + this.contributors = createContributorsString(contributors); + } + + public String getId() { + return id; + } + + public String getMainTitle() { + return mainTitle; + } + + public String getPublicationDate() { + return publicationDate; + } + + public String getPublicationInstance() { + return publicationInstance; + } + + public String getContributors() { + return contributors; + } + + public ExportCsv withId(String id) { + this.id = id; + return this; + } + + public ExportCsv withMainTitle(String mainTitle) { + this.mainTitle = mainTitle; + return this; + } + + public ExportCsv withPublicationDate(String publicationDate) { + this.publicationDate = publicationDate; + return this; + } + + public ExportCsv withPublicationInstance(String publicationInstance) { + this.publicationInstance = publicationInstance; + return this; + } + + public ExportCsv withContributors(String contributors) { + this.contributors = contributors; + return this; + } + + public final String createPublicationDate(String year, String month, String day) { + var stringBuilder = new StringBuilder(); + + if (StringUtils.isNotBlank(year)) { + stringBuilder.append(year); + } + + if (StringUtils.isNotBlank(month) && StringUtils.isNotBlank(year)) { + stringBuilder.append(DATE_SEPARATOR).append(month); + } + + if (StringUtils.isNotBlank(day) + && StringUtils.isNotBlank(month) + && StringUtils.isNotBlank(year)) { + stringBuilder.append(DATE_SEPARATOR).append(day); + } + return stringBuilder.toString(); + } + + public final String createContributorsString(List contributors) { + return String.join(DELIMITER, contributors); + } + + @Override + @JacocoGenerated + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ExportCsv exportCsv)) { + return false; + } + return Objects.equals(getId(), exportCsv.getId()) + && Objects.equals(getMainTitle(), exportCsv.getMainTitle()) + && Objects.equals(getPublicationDate(), exportCsv.getPublicationDate()) + && Objects.equals(getPublicationInstance(), exportCsv.getPublicationInstance()) + && Objects.equals(getContributors(), exportCsv.getContributors()); + } + + @Override + @JacocoGenerated + public int hashCode() { + return Objects.hash( + getId(), getMainTitle(), getPublicationDate(), getPublicationInstance(), getContributors()); + } + + @Override + @JacocoGenerated + public String toString() { + return "ExportCsv{id='%s', mainTitle='%s', publicationDate='%s', publicationInstance='%s', contributors='%s'}" + .formatted(id, mainTitle, publicationDate, publicationInstance, contributors); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/csv/HeaderColumnNameAndOrderMappingStrategy.java b/search-commons/src/main/java/no/unit/nva/search/common/csv/HeaderColumnNameAndOrderMappingStrategy.java index 3aa4b9ef0..037e1144c 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/csv/HeaderColumnNameAndOrderMappingStrategy.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/csv/HeaderColumnNameAndOrderMappingStrategy.java @@ -5,16 +5,14 @@ import com.opencsv.bean.CsvCustomBindByName; import com.opencsv.bean.HeaderColumnNameMappingStrategy; import com.opencsv.exceptions.CsvRequiredFieldEmptyException; - +import java.util.Arrays; +import java.util.stream.Collectors; import org.apache.commons.collections4.comparators.ComparableComparator; import org.apache.commons.collections4.comparators.ComparatorChain; import org.apache.commons.collections4.comparators.FixedOrderComparator; import org.apache.commons.collections4.comparators.NullComparator; import org.apache.commons.lang3.StringUtils; -import java.util.Arrays; -import java.util.stream.Collectors; - /** * Class for exporting data to CSV. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/csv/ResourceCsvTransformer.java b/search-commons/src/main/java/no/unit/nva/search/common/csv/ResourceCsvTransformer.java index a5103d976..4c85efe53 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/csv/ResourceCsvTransformer.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/csv/ResourceCsvTransformer.java @@ -6,7 +6,6 @@ import com.opencsv.bean.StatefulBeanToCsvBuilder; import com.opencsv.exceptions.CsvDataTypeMismatchException; import com.opencsv.exceptions.CsvRequiredFieldEmptyException; - import java.io.StringWriter; import java.util.List; import java.util.stream.Collectors; diff --git a/search-commons/src/main/java/no/unit/nva/search/common/enums/FieldOperator.java b/search-commons/src/main/java/no/unit/nva/search/common/enums/FieldOperator.java index 75c95623a..ef2eb63b8 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/enums/FieldOperator.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/enums/FieldOperator.java @@ -8,24 +8,24 @@ * @author Stig Norland */ public enum FieldOperator { - /** ALL must match in document (Only sensible for collections). */ - ALL_OF, - /** None can match in document (Inverted of MUST). */ - NOT_ALL_OF, - /** One or more must match (Only sensible for unique fields). */ - ANY_OF, - /** Any cannot match (These should be excluded). */ - NOT_ANY_OF, - /** Greater than or equal to. */ - GREATER_THAN_OR_EQUAL_TO, - /** Less than. */ - LESS_THAN, - /** Between. */ - BETWEEN, - /** Not Applicable */ - NA; + /** ALL must match in document (Only sensible for collections). */ + ALL_OF, + /** None can match in document (Inverted of MUST). */ + NOT_ALL_OF, + /** One or more must match (Only sensible for unique fields). */ + ANY_OF, + /** Any cannot match (These should be excluded). */ + NOT_ANY_OF, + /** Greater than or equal to. */ + GREATER_THAN_OR_EQUAL_TO, + /** Less than. */ + LESS_THAN, + /** Between. */ + BETWEEN, + /** Not Applicable */ + NA; - public String asLowerCase() { - return this.name().toLowerCase(Locale.getDefault()); - } + public String asLowerCase() { + return this.name().toLowerCase(Locale.getDefault()); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKey.java b/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKey.java index a99cb348e..e6c087502 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKey.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKey.java @@ -1,5 +1,6 @@ package no.unit.nva.search.common.enums; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.ErrorMessages.INVALID_BOOLEAN; import static no.unit.nva.constants.ErrorMessages.INVALID_DATE; import static no.unit.nva.constants.ErrorMessages.INVALID_NUMBER; @@ -13,16 +14,12 @@ import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_NUMBER; import static no.unit.nva.search.common.enums.ParameterKind.CUSTOM; import static no.unit.nva.search.common.enums.ParameterKind.KEYWORD; - import static nva.commons.core.StringUtils.EMPTY_STRING; -import static java.util.Objects.nonNull; - -import no.unit.nva.constants.Words; - import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; +import no.unit.nva.constants.Words; /** * Interface for defining the keys used in the search service. @@ -32,78 +29,78 @@ * define the parameters that can be used in the query. */ public interface ParameterKey & ParameterKey> { - static Predicate> equalTo(String name) { - return key -> name.matches(key.fieldPattern()); - } - - static int compareAscending(Enum key1, Enum key2) { - return key1.ordinal() - key2.ordinal(); - } - - static ValueEncoding getEncoding(ParameterKind kind) { - return switch (kind) { - case INVALID, FLAG, BOOLEAN, NUMBER -> ValueEncoding.NONE; - default -> ValueEncoding.DECODE; + static Predicate> equalTo(String name) { + return key -> name.matches(key.fieldPattern()); + } + + static int compareAscending(Enum key1, Enum key2) { + return key1.ordinal() - key2.ordinal(); + } + + static ValueEncoding getEncoding(ParameterKind kind) { + return switch (kind) { + case INVALID, FLAG, BOOLEAN, NUMBER -> ValueEncoding.NONE; + default -> ValueEncoding.DECODE; + }; + } + + static String getErrorMessage(ParameterKind kind) { + return switch (kind) { + case DATE -> INVALID_DATE; + case EXISTS -> INVALID_BOOLEAN; + case NUMBER -> INVALID_NUMBER; + case SORT_KEY -> INVALID_VALUE_WITH_SORT; + case INVALID -> "Status INVALID should not raise an exception, Exception"; + default -> INVALID_VALUE; + }; + } + + static String getValuePattern(ParameterKind kind, String pattern) { + return nonNull(pattern) + ? pattern + : switch (kind) { + case EXISTS -> PATTERN_IS_BOOLEAN; + case DATE -> PATTERN_IS_DATE; + case NUMBER -> PATTERN_IS_NUMBER; + case INVALID -> PATTERN_IS_NONE_OR_ONE; + default -> PATTERN_IS_NON_EMPTY; }; - } - - static String getErrorMessage(ParameterKind kind) { - return switch (kind) { - case DATE -> INVALID_DATE; - case EXISTS -> INVALID_BOOLEAN; - case NUMBER -> INVALID_NUMBER; - case SORT_KEY -> INVALID_VALUE_WITH_SORT; - case INVALID -> "Status INVALID should not raise an exception, Exception"; - default -> INVALID_VALUE; - }; - } - - static String getValuePattern(ParameterKind kind, String pattern) { - return nonNull(pattern) - ? pattern - : switch (kind) { - case EXISTS -> PATTERN_IS_BOOLEAN; - case DATE -> PATTERN_IS_DATE; - case NUMBER -> PATTERN_IS_NUMBER; - case INVALID -> PATTERN_IS_NONE_OR_ONE; - default -> PATTERN_IS_NON_EMPTY; - }; - } + } - static Function trimKeyword(ParameterKind parameterKind, boolean... isKeyWord) { - return field -> - isNotKeyword(parameterKind, isKeyWord) - ? field.trim().replace(DOT + Words.KEYWORD, EMPTY_STRING) - : field.trim(); - } + static Function trimKeyword(ParameterKind parameterKind, boolean... isKeyWord) { + return field -> + isNotKeyword(parameterKind, isKeyWord) + ? field.trim().replace(DOT + Words.KEYWORD, EMPTY_STRING) + : field.trim(); + } - static boolean isNotKeyword(ParameterKind parameterKind, boolean... isKeyWord) { - var result = !(parameterKind.equals(KEYWORD) || parameterKind.equals(CUSTOM)); - return isKeyWord.length == 1 ? !isKeyWord[0] : result; - } + static boolean isNotKeyword(ParameterKind parameterKind, boolean... isKeyWord) { + var result = !(parameterKind.equals(KEYWORD) || parameterKind.equals(CUSTOM)); + return isKeyWord.length == 1 ? !isKeyWord[0] : result; + } - String asCamelCase(); + String asCamelCase(); - String asLowerCase(); + String asLowerCase(); - Float fieldBoost(); + Float fieldBoost(); - /** - * @return {@link ParameterKind} - */ - ParameterKind fieldType(); + /** + * @return {@link ParameterKind} + */ + ParameterKind fieldType(); - String fieldPattern(); + String fieldPattern(); - String valuePattern(); + String valuePattern(); - ValueEncoding valueEncoding(); + ValueEncoding valueEncoding(); - Stream searchFields(boolean... isKeyWord); + Stream searchFields(boolean... isKeyWord); - FieldOperator searchOperator(); + FieldOperator searchOperator(); - String errorMessage(); + String errorMessage(); - K subQuery(); + K subQuery(); } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKind.java b/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKind.java index 00ecbb389..d47265a28 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKind.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/enums/ParameterKind.java @@ -10,27 +10,27 @@ * @author Stig Norland */ public enum ParameterKind { - INVALID, - /** - * Ignored parameters are not processed by standard or custom handling. Normally used together - * with other parameters in custom handlers or paging. - */ - FUZZY_KEYWORD, - KEYWORD, - TEXT, - FLAG, - CUSTOM, - NUMBER, - DATE, - ACROSS_FIELDS, - BOOLEAN, - EXISTS, - FREE_TEXT, - HAS_PARTS, - PART_OF, - SORT_KEY; + INVALID, + /** + * Ignored parameters are not processed by standard or custom handling. Normally used together + * with other parameters in custom handlers or paging. + */ + FUZZY_KEYWORD, + KEYWORD, + TEXT, + FLAG, + CUSTOM, + NUMBER, + DATE, + ACROSS_FIELDS, + BOOLEAN, + EXISTS, + FREE_TEXT, + HAS_PARTS, + PART_OF, + SORT_KEY; - public String asCamelCase() { - return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); - } + public String asCamelCase() { + return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/enums/PublicationStatus.java b/search-commons/src/main/java/no/unit/nva/search/common/enums/PublicationStatus.java index 9e7d06be4..3360165a0 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/enums/PublicationStatus.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/enums/PublicationStatus.java @@ -6,11 +6,11 @@ * @author Stig Norland */ public enum PublicationStatus { - NEW, - DRAFT, - PUBLISHED_METADATA, - PUBLISHED, - DELETED, - UNPUBLISHED, - DRAFT_FOR_DELETION + NEW, + DRAFT, + PUBLISHED_METADATA, + PUBLISHED, + DELETED, + UNPUBLISHED, + DRAFT_FOR_DELETION } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/enums/SortKey.java b/search-commons/src/main/java/no/unit/nva/search/common/enums/SortKey.java index 09bf5f7be..6addbab60 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/enums/SortKey.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/enums/SortKey.java @@ -15,28 +15,27 @@ */ public interface SortKey { - static Predicate equalTo(String name) { - return key -> name.matches(key.keyPattern()); - } + static Predicate equalTo(String name) { + return key -> name.matches(key.keyPattern()); + } - static int compareAscending(Enum key1, Enum key2) { - return key1.ordinal() - key2.ordinal(); - } + static int compareAscending(Enum key1, Enum key2) { + return key1.ordinal() - key2.ordinal(); + } - static String getIgnoreCaseAndUnderscoreKeyExpression(String keyName) { - var keyNameIgnoreUnderscoreExpression = - keyName.toLowerCase(Locale.getDefault()) - .replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); - return "%s%s".formatted(PATTERN_IS_IGNORE_CASE, keyNameIgnoreUnderscoreExpression); - } + static String getIgnoreCaseAndUnderscoreKeyExpression(String keyName) { + var keyNameIgnoreUnderscoreExpression = + keyName.toLowerCase(Locale.getDefault()).replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); + return "%s%s".formatted(PATTERN_IS_IGNORE_CASE, keyNameIgnoreUnderscoreExpression); + } - String name(); + String name(); - String keyPattern(); + String keyPattern(); - Stream jsonPaths(); + Stream jsonPaths(); - String asCamelCase(); + String asCamelCase(); - String asLowerCase(); + String asLowerCase(); } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/enums/ValueEncoding.java b/search-commons/src/main/java/no/unit/nva/search/common/enums/ValueEncoding.java index 5aea2596b..7a59478d8 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/enums/ValueEncoding.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/enums/ValueEncoding.java @@ -6,6 +6,6 @@ * @author Stig Norland */ public enum ValueEncoding { - NONE, - DECODE + NONE, + DECODE } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedJwtProvider.java b/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedJwtProvider.java index f9cd62902..29518cad3 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedJwtProvider.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedJwtProvider.java @@ -1,7 +1,6 @@ package no.unit.nva.search.common.jwt; import com.auth0.jwt.interfaces.DecodedJWT; - import java.time.Clock; import java.util.Date; @@ -12,32 +11,32 @@ */ public class CachedJwtProvider extends CachedValueProvider { - private final CognitoAuthenticator cognitoAuthenticator; - private final Clock clock; + private final CognitoAuthenticator cognitoAuthenticator; + private final Clock clock; - public CachedJwtProvider(CognitoAuthenticator cognitoAuthenticator, Clock clock) { - super(); - this.cognitoAuthenticator = cognitoAuthenticator; - this.clock = clock; - } + public CachedJwtProvider(CognitoAuthenticator cognitoAuthenticator, Clock clock) { + super(); + this.cognitoAuthenticator = cognitoAuthenticator; + this.clock = clock; + } - public static CachedJwtProvider prepareWithAuthenticator( - CognitoAuthenticator cognitoAuthenticator) { - return new CachedJwtProvider(cognitoAuthenticator, Clock.systemDefaultZone()); - } + public static CachedJwtProvider prepareWithAuthenticator( + CognitoAuthenticator cognitoAuthenticator) { + return new CachedJwtProvider(cognitoAuthenticator, Clock.systemDefaultZone()); + } - @Override - protected boolean isExpired() { - var in5sec = clock.instant().plusMillis(5000); + @Override + protected boolean isExpired() { + var in5sec = clock.instant().plusMillis(5000); - var expiresAtDate = cachedValue.getExpiresAt(); - var dateIn5Secs = Date.from(in5sec); + var expiresAtDate = cachedValue.getExpiresAt(); + var dateIn5Secs = Date.from(in5sec); - return expiresAtDate.before(dateIn5Secs); - } + return expiresAtDate.before(dateIn5Secs); + } - @Override - protected DecodedJWT getNewValue() { - return cognitoAuthenticator.fetchBearerToken(); - } + @Override + protected DecodedJWT getNewValue() { + return cognitoAuthenticator.fetchBearerToken(); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedValueProvider.java b/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedValueProvider.java index 38e77b970..f89ceb37b 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedValueProvider.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/jwt/CachedValueProvider.java @@ -8,16 +8,16 @@ */ public abstract class CachedValueProvider { - protected T cachedValue; + protected T cachedValue; - public T getValue() { - if (cachedValue == null || isExpired()) { - cachedValue = getNewValue(); - } - return cachedValue; + public T getValue() { + if (cachedValue == null || isExpired()) { + cachedValue = getNewValue(); } + return cachedValue; + } - protected abstract boolean isExpired(); + protected abstract boolean isExpired(); - protected abstract T getNewValue(); + protected abstract T getNewValue(); } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/jwt/CognitoAuthenticator.java b/search-commons/src/main/java/no/unit/nva/search/common/jwt/CognitoAuthenticator.java index 0bfb549f9..b8cb43c78 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/jwt/CognitoAuthenticator.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/jwt/CognitoAuthenticator.java @@ -2,20 +2,12 @@ import static no.unit.nva.auth.AuthorizedBackendClient.APPLICATION_X_WWW_FORM_URLENCODED; import static no.unit.nva.constants.Words.AUTHORIZATION; - import static nva.commons.core.attempt.Try.attempt; - import static org.apache.http.protocol.HTTP.CONTENT_TYPE; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.jr.ob.JSON; - -import no.unit.nva.auth.CognitoCredentials; - -import nva.commons.core.JacocoGenerated; -import nva.commons.core.paths.UriWrapper; - import java.net.HttpURLConnection; import java.net.URI; import java.net.http.HttpClient; @@ -24,6 +16,9 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Objects; +import no.unit.nva.auth.CognitoCredentials; +import nva.commons.core.JacocoGenerated; +import nva.commons.core.paths.UriWrapper; /** * Class for authenticating with Cognito. @@ -32,99 +27,96 @@ */ public class CognitoAuthenticator { - public static final String OAUTH2_PATH_SEGMENT = "oauth2"; - public static final String TOKEN_PATH_SEGMENT = "token"; - public static final String BASIC_AUTH_CREDENTIALS_TEMPLATE = "%s:%s"; - public static final String BASIC_AUTH_HEADER_TEMPLATE = "%s %s"; - public static final String AUTHORIZATION_ERROR_MESSAGE = "Could not authorizer client"; - public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "grant_type=client_credentials"; - public static final String JWT_TOKEN_FIELD = "access_token"; - private final CognitoCredentials credentials; - private final HttpClient httpClient; - - public CognitoAuthenticator(HttpClient httpClient, CognitoCredentials credentials) { - this.httpClient = httpClient; - this.credentials = credentials; - } - - @JacocoGenerated - public static CognitoAuthenticator prepareWithCognitoCredentials( - CognitoCredentials cognitoCredentials) { - return prepareWithCognitoCredentials(HttpClient.newHttpClient(), cognitoCredentials); - } - - public static CognitoAuthenticator prepareWithCognitoCredentials( - HttpClient httpClient, CognitoCredentials cognitoApiClientCredentials) { - return new CognitoAuthenticator(httpClient, cognitoApiClientCredentials); - } - - private static URI standardOauth2TokenEndpoint(URI cognitoHost) { - return UriWrapper.fromUri(cognitoHost) - .addChild(OAUTH2_PATH_SEGMENT) - .addChild(TOKEN_PATH_SEGMENT) - .getUri(); - } - - private static HttpRequest.BodyPublisher clientCredentialsAuthType() { - return HttpRequest.BodyPublishers.ofString(GRANT_TYPE_CLIENT_CREDENTIALS); - } - - public DecodedJWT fetchBearerToken() { - var tokenResponse = fetchTokenResponse(); - return attempt(() -> tokenResponse) - .map(HttpResponse::body) - .map(JSON.std::mapFrom) - .map(json -> json.get(JWT_TOKEN_FIELD)) - .toOptional() - .map(Objects::toString) - .map(JWT::decode) - .orElseThrow(); - } - - private String formatAuthenticationHeaderValue() { - return String.format( - BASIC_AUTH_CREDENTIALS_TEMPLATE, - credentials.getCognitoAppClientId(), - credentials.getCognitoAppClientSecret()); - } - - private String formatBasicAuthenticationHeader() { - return attempt(this::formatAuthenticationHeaderValue) - .map( - str -> - Base64.getEncoder() - .encodeToString(str.getBytes(StandardCharsets.UTF_8))) - .map(credentials -> String.format(BASIC_AUTH_HEADER_TEMPLATE, "Basic", credentials)) - .orElseThrow(); - } - - private HttpRequest createTokenRequest() { - var tokenUri = standardOauth2TokenEndpoint(credentials.getCognitoOAuthServerUri()); - return formatRequestForJwtToken(tokenUri); - } - - private HttpResponse fetchTokenResponse() { - return attempt( - () -> - this.httpClient.send( - createTokenRequest(), - HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))) - .map(this::responseIsSuccessful) - .orElseThrow(); - } - - private HttpResponse responseIsSuccessful(HttpResponse response) { - if (HttpURLConnection.HTTP_OK != response.statusCode()) { - throw new RuntimeException(AUTHORIZATION_ERROR_MESSAGE); - } - return response; - } - - private HttpRequest formatRequestForJwtToken(URI tokenUri) { - return HttpRequest.newBuilder(tokenUri) - .setHeader(AUTHORIZATION, formatBasicAuthenticationHeader()) - .setHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .POST(clientCredentialsAuthType()) - .build(); + public static final String OAUTH2_PATH_SEGMENT = "oauth2"; + public static final String TOKEN_PATH_SEGMENT = "token"; + public static final String BASIC_AUTH_CREDENTIALS_TEMPLATE = "%s:%s"; + public static final String BASIC_AUTH_HEADER_TEMPLATE = "%s %s"; + public static final String AUTHORIZATION_ERROR_MESSAGE = "Could not authorizer client"; + public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "grant_type=client_credentials"; + public static final String JWT_TOKEN_FIELD = "access_token"; + private final CognitoCredentials credentials; + private final HttpClient httpClient; + + public CognitoAuthenticator(HttpClient httpClient, CognitoCredentials credentials) { + this.httpClient = httpClient; + this.credentials = credentials; + } + + @JacocoGenerated + public static CognitoAuthenticator prepareWithCognitoCredentials( + CognitoCredentials cognitoCredentials) { + return prepareWithCognitoCredentials(HttpClient.newHttpClient(), cognitoCredentials); + } + + public static CognitoAuthenticator prepareWithCognitoCredentials( + HttpClient httpClient, CognitoCredentials cognitoApiClientCredentials) { + return new CognitoAuthenticator(httpClient, cognitoApiClientCredentials); + } + + private static URI standardOauth2TokenEndpoint(URI cognitoHost) { + return UriWrapper.fromUri(cognitoHost) + .addChild(OAUTH2_PATH_SEGMENT) + .addChild(TOKEN_PATH_SEGMENT) + .getUri(); + } + + private static HttpRequest.BodyPublisher clientCredentialsAuthType() { + return HttpRequest.BodyPublishers.ofString(GRANT_TYPE_CLIENT_CREDENTIALS); + } + + public DecodedJWT fetchBearerToken() { + var tokenResponse = fetchTokenResponse(); + return attempt(() -> tokenResponse) + .map(HttpResponse::body) + .map(JSON.std::mapFrom) + .map(json -> json.get(JWT_TOKEN_FIELD)) + .toOptional() + .map(Objects::toString) + .map(JWT::decode) + .orElseThrow(); + } + + private String formatAuthenticationHeaderValue() { + return String.format( + BASIC_AUTH_CREDENTIALS_TEMPLATE, + credentials.getCognitoAppClientId(), + credentials.getCognitoAppClientSecret()); + } + + private String formatBasicAuthenticationHeader() { + return attempt(this::formatAuthenticationHeaderValue) + .map(str -> Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))) + .map(credentials -> String.format(BASIC_AUTH_HEADER_TEMPLATE, "Basic", credentials)) + .orElseThrow(); + } + + private HttpRequest createTokenRequest() { + var tokenUri = standardOauth2TokenEndpoint(credentials.getCognitoOAuthServerUri()); + return formatRequestForJwtToken(tokenUri); + } + + private HttpResponse fetchTokenResponse() { + return attempt( + () -> + this.httpClient.send( + createTokenRequest(), + HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))) + .map(this::responseIsSuccessful) + .orElseThrow(); + } + + private HttpResponse responseIsSuccessful(HttpResponse response) { + if (HttpURLConnection.HTTP_OK != response.statusCode()) { + throw new RuntimeException(AUTHORIZATION_ERROR_MESSAGE); } + return response; + } + + private HttpRequest formatRequestForJwtToken(URI tokenUri) { + return HttpRequest.newBuilder(tokenUri) + .setHeader(AUTHORIZATION, formatBasicAuthenticationHeader()) + .setHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .POST(clientCredentialsAuthType()) + .build(); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/jwt/Tools.java b/search-commons/src/main/java/no/unit/nva/search/common/jwt/Tools.java index 979ce7ebc..c89c29cfb 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/jwt/Tools.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/jwt/Tools.java @@ -3,15 +3,13 @@ import static no.unit.nva.constants.Words.SEARCH_INFRASTRUCTURE_CREDENTIALS; import static no.unit.nva.search.common.constant.Functions.readSearchInfrastructureAuthUri; +import java.net.URI; +import java.util.stream.Stream; import no.unit.nva.auth.CognitoCredentials; import no.unit.nva.search.common.records.UsernamePasswordWrapper; - import nva.commons.core.JacocoGenerated; import nva.commons.secrets.SecretsReader; -import java.net.URI; -import java.util.stream.Stream; - /** * Tools for handling JWT. * diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/Facet.java b/search-commons/src/main/java/no/unit/nva/search/common/records/Facet.java index dcd7b5c48..d8aaf69cc 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/Facet.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/Facet.java @@ -2,12 +2,10 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -import no.unit.nva.commons.json.JsonSerializable; - import java.net.URI; import java.util.Map; import java.util.Objects; +import no.unit.nva.commons.json.JsonSerializable; /** * Facet is a class that represents a facet in a search result. diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/FacetsBuilder.java b/search-commons/src/main/java/no/unit/nva/search/common/records/FacetsBuilder.java index 88d0761db..7eadfbcf4 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/FacetsBuilder.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/FacetsBuilder.java @@ -3,16 +3,13 @@ import static nva.commons.core.attempt.Try.attempt; import com.fasterxml.jackson.core.type.TypeReference; - -import no.unit.nva.commons.json.JsonUtils; - -import nva.commons.core.JacocoGenerated; -import nva.commons.core.paths.UriWrapper; - import java.net.URI; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import no.unit.nva.commons.json.JsonUtils; +import nva.commons.core.JacocoGenerated; +import nva.commons.core.paths.UriWrapper; /** * @author Stig Norland diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/FilterBuilder.java b/search-commons/src/main/java/no/unit/nva/search/common/records/FilterBuilder.java index e93ef7d06..7ab0aeb3d 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/FilterBuilder.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/FilterBuilder.java @@ -12,7 +12,7 @@ * @author Stig Norland */ public interface FilterBuilder> { - Q apply() throws UnauthorizedException; + Q apply() throws UnauthorizedException; - Q fromRequestInfo(RequestInfo requestInfo) throws UnauthorizedException; + Q fromRequestInfo(RequestInfo requestInfo) throws UnauthorizedException; } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/HttpResponseFormatter.java b/search-commons/src/main/java/no/unit/nva/search/common/records/HttpResponseFormatter.java index 62909868c..27f7614b6 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/HttpResponseFormatter.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/HttpResponseFormatter.java @@ -1,28 +1,23 @@ package no.unit.nva.search.common.records; import static com.google.common.net.MediaType.CSV_UTF_8; - +import static java.util.Objects.nonNull; import static no.unit.nva.constants.Words.COMMA; import static no.unit.nva.search.common.constant.Functions.hasContent; - import static nva.commons.core.paths.UriWrapper.fromUri; -import static java.util.Objects.nonNull; - import com.google.common.net.MediaType; - -import no.unit.nva.constants.Words; -import no.unit.nva.search.common.AggregationFormat; -import no.unit.nva.search.common.QueryKeys; -import no.unit.nva.search.common.csv.ResourceCsvTransformer; -import no.unit.nva.search.common.enums.ParameterKey; - import java.net.URI; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import no.unit.nva.constants.Words; +import no.unit.nva.search.common.AggregationFormat; +import no.unit.nva.search.common.QueryKeys; +import no.unit.nva.search.common.csv.ResourceCsvTransformer; +import no.unit.nva.search.common.enums.ParameterKey; /** * HttpResponseFormatter is a class that formats a search response. diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/JsonNodeMutator.java b/search-commons/src/main/java/no/unit/nva/search/common/records/JsonNodeMutator.java index 85befdda1..eaca1d4c6 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/JsonNodeMutator.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/JsonNodeMutator.java @@ -3,5 +3,5 @@ import com.fasterxml.jackson.databind.JsonNode; public interface JsonNodeMutator { - JsonNode transform(JsonNode source); + JsonNode transform(JsonNode source); } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearch.java b/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearch.java index fa858fa35..5049ca12c 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearch.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearch.java @@ -1,18 +1,15 @@ package no.unit.nva.search.common.records; -import static no.unit.nva.constants.Defaults.PAGINATED_SEARCH_RESULT_CONTEXT; - import static java.util.Objects.nonNull; +import static no.unit.nva.constants.Defaults.PAGINATED_SEARCH_RESULT_CONTEXT; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; - -import no.unit.nva.commons.json.JsonSerializable; - import java.net.URI; import java.util.List; import java.util.Map; +import no.unit.nva.commons.json.JsonSerializable; /** * PagedSearch is a class that represents a paged search result. @@ -27,22 +24,22 @@ * @param aggregations the aggregations in the search result. */ public record PagedSearch( - URI id, - Integer totalHits, - @JsonInclude() List hits, - URI nextResults, - URI nextSearchAfterResults, - URI previousResults, - Map> aggregations) - implements JsonSerializable { - - @JsonProperty("@context") - public URI context() { - return PAGINATED_SEARCH_RESULT_CONTEXT; - } - - @Override - public Map> aggregations() { - return nonNull(aggregations) ? aggregations : Map.of(); - } + URI id, + Integer totalHits, + @JsonInclude() List hits, + URI nextResults, + URI nextSearchAfterResults, + URI previousResults, + Map> aggregations) + implements JsonSerializable { + + @JsonProperty("@context") + public URI context() { + return PAGINATED_SEARCH_RESULT_CONTEXT; + } + + @Override + public Map> aggregations() { + return nonNull(aggregations) ? aggregations : Map.of(); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearchBuilder.java b/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearchBuilder.java index 8f5e163a9..04c483a35 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearchBuilder.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/PagedSearchBuilder.java @@ -3,15 +3,12 @@ import static java.util.Objects.isNull; import com.fasterxml.jackson.databind.JsonNode; - -import no.unit.nva.constants.Words; - -import nva.commons.core.JacocoGenerated; -import nva.commons.core.paths.UriWrapper; - import java.net.URI; import java.util.List; import java.util.Map; +import no.unit.nva.constants.Words; +import nva.commons.core.JacocoGenerated; +import nva.commons.core.paths.UriWrapper; /** * PagedSearchBuilder is a class that builds diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/ResponseLogInfo.java b/search-commons/src/main/java/no/unit/nva/search/common/records/ResponseLogInfo.java index 5070b4834..343b5d28c 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/ResponseLogInfo.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/ResponseLogInfo.java @@ -15,76 +15,76 @@ * @param query the query. */ public record ResponseLogInfo( - int totalHits, - int hitsReturned, - long queryDuration, - long networkDuration, - long prePostDuration, - long totalDuration, - String query) - implements JsonSerializable { + int totalHits, + int hitsReturned, + long queryDuration, + long networkDuration, + long prePostDuration, + long totalDuration, + String query) + implements JsonSerializable { - public static Builder builder() { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - public static final class Builder { + public static final class Builder { - private int totalHits; - private int hitsReturned; - private long totalTime; - private long fetchTime; - private long searchTime; - private String searchQuery; + private int totalHits; + private int hitsReturned; + private long totalTime; + private long fetchTime; + private long searchTime; + private String searchQuery; - private Builder() {} + private Builder() {} - public Builder withFetchTime(long fetchDuration) { - this.fetchTime = fetchDuration; - return this; - } + public Builder withFetchTime(long fetchDuration) { + this.fetchTime = fetchDuration; + return this; + } - public Builder withTotalTime(long milliseconds) { - this.totalTime = milliseconds; - return this; - } + public Builder withTotalTime(long milliseconds) { + this.totalTime = milliseconds; + return this; + } - public Builder withOpensearchResponseTime(long milliseconds) { - this.searchTime = milliseconds; - return this; - } + public Builder withOpensearchResponseTime(long milliseconds) { + this.searchTime = milliseconds; + return this; + } - public Builder withTotalHits(int totalHits) { - this.totalHits = totalHits; - return this; - } + public Builder withTotalHits(int totalHits) { + this.totalHits = totalHits; + return this; + } - public Builder withHitsReturned(int hitsReturned) { - this.hitsReturned = hitsReturned; - return this; - } + public Builder withHitsReturned(int hitsReturned) { + this.hitsReturned = hitsReturned; + return this; + } - public Builder withSearchQuery(String searchQuery) { - this.searchQuery = searchQuery; - return this; - } + public Builder withSearchQuery(String searchQuery) { + this.searchQuery = searchQuery; + return this; + } - public Builder withSwsResponse(SwsResponse response) { - return this.withOpensearchResponseTime(response.took()) - .withTotalHits(response.getTotalSize()) - .withHitsReturned(response.getSearchHits().size()); - } + public Builder withSwsResponse(SwsResponse response) { + return this.withOpensearchResponseTime(response.took()) + .withTotalHits(response.getTotalSize()) + .withHitsReturned(response.getSearchHits().size()); + } - public String toJsonString() { - return new ResponseLogInfo( - totalHits, - hitsReturned, - searchTime, - fetchTime - searchTime, - totalTime - fetchTime, - totalTime, - searchQuery) - .toJsonString(); - } + public String toJsonString() { + return new ResponseLogInfo( + totalHits, + hitsReturned, + searchTime, + fetchTime - searchTime, + totalTime - fetchTime, + totalTime, + searchQuery) + .toJsonString(); } + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/SwsResponse.java b/search-commons/src/main/java/no/unit/nva/search/common/records/SwsResponse.java index 074313094..9ae83e57e 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/SwsResponse.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/SwsResponse.java @@ -4,14 +4,11 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.JsonNode; - -import no.unit.nva.search.common.records.SwsResponse.HitsInfo.Hit; - -import nva.commons.core.JacocoGenerated; - import java.beans.Transient; import java.util.List; import java.util.Optional; +import no.unit.nva.search.common.records.SwsResponse.HitsInfo.Hit; +import nva.commons.core.JacocoGenerated; /** * Response from SWS, almost identical to Opensearch's response. @@ -39,9 +36,9 @@ public List getSearchHits() { @JacocoGenerated @Transient public List getSort() { - return nonNull(hits) && nonNull(hits.hits) && !hits.hits.isEmpty() - ? Optional.ofNullable(hits.hits.get(hits.hits.size() - 1).sort()).orElse(List.of()) - : List.of(); + return nonNull(hits) && nonNull(hits.hits) && !hits.hits.isEmpty() + ? Optional.ofNullable(hits.hits.getLast().sort()).orElse(List.of()) + : List.of(); } public record ShardsInfo(Long total, Long successful, Long skipped, Long failed) {} diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/UserSettings.java b/search-commons/src/main/java/no/unit/nva/search/common/records/UserSettings.java index 695768f56..118c919ae 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/UserSettings.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/UserSettings.java @@ -3,7 +3,6 @@ import static nva.commons.core.paths.UriWrapper.fromUri; import com.fasterxml.jackson.annotation.JsonInclude; - import java.util.List; /** diff --git a/search-commons/src/main/java/no/unit/nva/search/common/records/UsernamePasswordWrapper.java b/search-commons/src/main/java/no/unit/nva/search/common/records/UsernamePasswordWrapper.java index 31018f5dd..04e99ddf0 100644 --- a/search-commons/src/main/java/no/unit/nva/search/common/records/UsernamePasswordWrapper.java +++ b/search-commons/src/main/java/no/unit/nva/search/common/records/UsernamePasswordWrapper.java @@ -10,28 +10,28 @@ * @author Sondre Vestad */ public class UsernamePasswordWrapper { - @JsonProperty("username") - public String username; - - @JsonProperty("password") - public String password; - - @JacocoGenerated - public UsernamePasswordWrapper() {} - - @JacocoGenerated - public UsernamePasswordWrapper(String username, String password) { - this.username = username; - this.password = password; - } - - @JacocoGenerated - public String getUsername() { - return username; - } - - @JacocoGenerated - public String getPassword() { - return password; - } + @JsonProperty("username") + public String username; + + @JsonProperty("password") + public String password; + + @JacocoGenerated + public UsernamePasswordWrapper() {} + + @JacocoGenerated + public UsernamePasswordWrapper(String username, String password) { + this.username = username; + this.password = password; + } + + @JacocoGenerated + public String getUsername() { + return username; + } + + @JacocoGenerated + public String getPassword() { + return password; + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/importcandidate/Constants.java b/search-commons/src/main/java/no/unit/nva/search/importcandidate/Constants.java index e367e6f9a..13e788273 100644 --- a/search-commons/src/main/java/no/unit/nva/search/importcandidate/Constants.java +++ b/search-commons/src/main/java/no/unit/nva/search/importcandidate/Constants.java @@ -20,17 +20,15 @@ import static no.unit.nva.search.common.constant.Functions.multipleFields; import static no.unit.nva.search.common.constant.Functions.nestedBranchBuilder; +import java.util.List; +import java.util.Locale; +import java.util.Map; import nva.commons.core.JacocoGenerated; - import org.opensearch.script.Script; import org.opensearch.script.ScriptType; import org.opensearch.search.aggregations.AggregationBuilder; import org.opensearch.search.aggregations.bucket.nested.NestedAggregationBuilder; -import java.util.List; -import java.util.Locale; -import java.util.Map; - /** * Constants for the import candidates search. * diff --git a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateClient.java b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateClient.java index 98442c91a..02bd1622a 100644 --- a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateClient.java +++ b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateClient.java @@ -4,19 +4,16 @@ import static no.unit.nva.search.common.jwt.Tools.getCachedJwtProvider; import com.fasterxml.jackson.core.JsonProcessingException; - +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.function.BinaryOperator; import no.unit.nva.search.common.OpenSearchClient; import no.unit.nva.search.common.jwt.CachedJwtProvider; import no.unit.nva.search.common.records.SwsResponse; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.FunctionWithException; import nva.commons.secrets.SecretsReader; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; -import java.util.function.BinaryOperator; - /** * ImportCandidateClient is a class that sends a request to the search index. * diff --git a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateParameter.java b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateParameter.java index a7ac33799..442d024e4 100644 --- a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateParameter.java +++ b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateParameter.java @@ -1,5 +1,6 @@ package no.unit.nva.search.importcandidate; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.ErrorMessages.NOT_IMPLEMENTED_FOR; import static no.unit.nva.constants.Words.CHAR_UNDERSCORE; import static no.unit.nva.constants.Words.COLON; @@ -41,27 +42,22 @@ import static no.unit.nva.search.importcandidate.Constants.PUBLISHER_ID_KEYWORD; import static no.unit.nva.search.resource.Constants.ASSOCIATED_ARTIFACTS_LICENSE; -import static java.util.Objects.nonNull; - +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.constants.Words; import no.unit.nva.search.common.enums.FieldOperator; import no.unit.nva.search.common.enums.ParameterKey; import no.unit.nva.search.common.enums.ParameterKind; import no.unit.nva.search.common.enums.ValueEncoding; - import nva.commons.core.JacocoGenerated; - import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.text.CaseUtils; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Enum for all the parameters that can be used to query the search index. This enum needs to * implement these parameters cristin diff --git a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSearchQuery.java b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSearchQuery.java index 203b3d2c4..de8e9f1e9 100644 --- a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSearchQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSearchQuery.java @@ -32,33 +32,28 @@ import static no.unit.nva.search.importcandidate.ImportCandidateParameter.SEARCH_AFTER; import static no.unit.nva.search.importcandidate.ImportCandidateParameter.SIZE; import static no.unit.nva.search.importcandidate.ImportCandidateParameter.SORT; - import static nva.commons.core.paths.UriWrapper.fromUri; - import static org.opensearch.index.query.QueryBuilders.boolQuery; import static org.opensearch.index.query.QueryBuilders.termQuery; +import java.net.URI; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; import no.unit.nva.search.common.AsType; import no.unit.nva.search.common.ParameterValidator; import no.unit.nva.search.common.SearchQuery; import no.unit.nva.search.common.constant.Functions; import no.unit.nva.search.common.enums.SortKey; - import nva.commons.core.JacocoGenerated; - import org.apache.lucene.search.join.ScoreMode; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.search.aggregations.AggregationBuilder; import org.opensearch.search.sort.SortOrder; -import java.net.URI; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * ImportCandidateSearchQuery is a class that represents a search query for import candidates. * diff --git a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSort.java b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSort.java index e31074c4f..aa35422fd 100644 --- a/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSort.java +++ b/search-commons/src/main/java/no/unit/nva/search/importcandidate/ImportCandidateSort.java @@ -2,19 +2,16 @@ import static no.unit.nva.constants.Words.CHAR_UNDERSCORE; import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_PIPE; - import static nva.commons.core.StringUtils.EMPTY_STRING; -import no.unit.nva.constants.Words; -import no.unit.nva.search.common.enums.SortKey; - -import org.apache.commons.text.CaseUtils; - import java.util.Arrays; import java.util.Collection; import java.util.Locale; import java.util.stream.Collectors; import java.util.stream.Stream; +import no.unit.nva.constants.Words; +import no.unit.nva.search.common.enums.SortKey; +import org.apache.commons.text.CaseUtils; /** * ImportCandidateSort is an enum for sorting import candidates. diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/Constants.java b/search-commons/src/main/java/no/unit/nva/search/resource/Constants.java index 0974344cd..714105203 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/Constants.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/Constants.java @@ -69,8 +69,11 @@ import static no.unit.nva.search.common.constant.Functions.multipleFields; import static no.unit.nva.search.common.constant.Functions.nestedBranchBuilder; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import nva.commons.core.JacocoGenerated; - import org.opensearch.search.aggregations.AggregationBuilder; import org.opensearch.search.aggregations.AggregationBuilders; import org.opensearch.search.aggregations.bucket.filter.FilterAggregationBuilder; @@ -79,11 +82,6 @@ import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.opensearch.search.aggregations.metrics.CardinalityAggregationBuilder; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - /** * Constants for the Resource Search. * diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/LegacyMutator.java b/search-commons/src/main/java/no/unit/nva/search/resource/LegacyMutator.java index b3e4a9bbb..f1403fcfd 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/LegacyMutator.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/LegacyMutator.java @@ -2,22 +2,21 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; - import no.unit.nva.search.common.records.JsonNodeMutator; public class LegacyMutator implements JsonNodeMutator { - public static final String CONTRIBUTORS_PREVIEW = "contributorsPreview"; - public static final String ENTITY_DESCRIPTION = "entityDescription"; - public static final String CONTRIBUTORS = "contributors"; + public static final String CONTRIBUTORS_PREVIEW = "contributorsPreview"; + public static final String ENTITY_DESCRIPTION = "entityDescription"; + public static final String CONTRIBUTORS = "contributors"; - @Override - public JsonNode transform(JsonNode source) { - var contributorsPreview = source.path(ENTITY_DESCRIPTION).path(CONTRIBUTORS_PREVIEW); - if (!contributorsPreview.isMissingNode()) { - var entityDescription = (ObjectNode) source.path(ENTITY_DESCRIPTION); - entityDescription.put(CONTRIBUTORS, contributorsPreview); - } - return source; + @Override + public JsonNode transform(JsonNode source) { + var contributorsPreview = source.path(ENTITY_DESCRIPTION).path(CONTRIBUTORS_PREVIEW); + if (!contributorsPreview.isMissingNode()) { + var entityDescription = (ObjectNode) source.path(ENTITY_DESCRIPTION); + entityDescription.put(CONTRIBUTORS, contributorsPreview); } + return source; + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceAccessFilter.java b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceAccessFilter.java index 8091a6860..58ce47431 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceAccessFilter.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceAccessFilter.java @@ -10,23 +10,19 @@ import static no.unit.nva.search.resource.Constants.CONTRIBUTOR_ORG_KEYWORD; import static no.unit.nva.search.resource.Constants.STATUS_KEYWORD; import static no.unit.nva.search.resource.ResourceParameter.STATISTICS; - import static nva.commons.apigateway.AccessRight.MANAGE_CUSTOMERS; import static nva.commons.apigateway.AccessRight.MANAGE_RESOURCES_ALL; +import java.net.URI; +import java.util.Arrays; import no.unit.nva.search.common.enums.PublicationStatus; import no.unit.nva.search.common.records.FilterBuilder; - import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.UnauthorizedException; - import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.index.query.TermsQueryBuilder; -import java.net.URI; -import java.util.Arrays; - /** * ResourceAccessFilter is a class that filters tickets based on access rights. * diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceClient.java b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceClient.java index 38fecfd6d..070df7890 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceClient.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceClient.java @@ -5,21 +5,18 @@ import static no.unit.nva.search.common.records.SwsResponse.SwsResponseBuilder.swsResponseBuilder; import com.fasterxml.jackson.core.JsonProcessingException; - -import no.unit.nva.search.common.OpenSearchClient; -import no.unit.nva.search.common.jwt.CachedJwtProvider; -import no.unit.nva.search.common.records.SwsResponse; - -import nva.commons.core.JacocoGenerated; -import nva.commons.core.attempt.FunctionWithException; -import nva.commons.secrets.SecretsReader; - import java.net.http.HttpClient; import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BinaryOperator; +import no.unit.nva.search.common.OpenSearchClient; +import no.unit.nva.search.common.jwt.CachedJwtProvider; +import no.unit.nva.search.common.records.SwsResponse; +import nva.commons.core.JacocoGenerated; +import nva.commons.core.attempt.FunctionWithException; +import nva.commons.secrets.SecretsReader; /** * Client for searching resources. diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceParameter.java b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceParameter.java index a55c6dd47..46d5955eb 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceParameter.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceParameter.java @@ -118,320 +118,311 @@ */ @SuppressWarnings({"PMD.ExcessivePublicCount"}) public enum ResourceParameter implements ParameterKey { - INVALID(ParameterKind.INVALID), - // Parameters used for filtering - ABSTRACT(TEXT, ALL_OF, ENTITY_ABSTRACT), - ABSTRACT_OF_CHILD(HAS_PARTS, ALL_OF, ABSTRACT), - ABSTRACT_NOT(TEXT, NOT_ALL_OF, ENTITY_ABSTRACT), - ABSTRACT_SHOULD(TEXT, ANY_OF, ENTITY_ABSTRACT), - CONTEXT_TYPE(KEYWORD, ALL_OF, PUBLICATION_CONTEXT_TYPE_KEYWORD), - CONTEXT_TYPE_OF_NO_CHILD(HAS_PARTS, NOT_ANY_OF, null, null, null, null, CONTEXT_TYPE), - CONTEXT_TYPE_NOT(KEYWORD, NOT_ALL_OF, PUBLICATION_CONTEXT_TYPE_KEYWORD), - CONTEXT_TYPE_SHOULD(KEYWORD, ANY_OF, PUBLICATION_CONTEXT_TYPE_KEYWORD), - CONTRIBUTORS(ACROSS_FIELDS, ALL_OF, CONTRIBUTORS_FIELDS), - CONTRIBUTORS_OF_CHILD(HAS_PARTS, ALL_OF, CONTRIBUTORS), - CONTRIBUTOR(KEYWORD, ALL_OF, CONTRIBUTORS_IDENTITY_ID, null, PATTERN_IS_URI), - CONTRIBUTOR_COUNT(NUMBER, BETWEEN, CONTRIBUTOR_COUNT_NO_KEYWORD), - CONTRIBUTOR_NOT(KEYWORD, NOT_ALL_OF, CONTRIBUTORS_IDENTITY_ID, null, PATTERN_IS_URI), - CONTRIBUTOR_SHOULD(KEYWORD, ANY_OF, CONTRIBUTORS_IDENTITY_ID, null, PATTERN_IS_URI), - CONTRIBUTOR_NAME(FUZZY_KEYWORD, ALL_OF, CONTRIBUTORS_IDENTITY_NAME_KEYWORD), - CONTRIBUTOR_NAME_NOT(FUZZY_KEYWORD, NOT_ALL_OF, CONTRIBUTORS_IDENTITY_NAME_KEYWORD), - CONTRIBUTOR_NAME_SHOULD(TEXT, ANY_OF, CONTRIBUTORS_IDENTITY_NAME_KEYWORD), - COURSE(TEXT, ALL_OF, COURSE_CODE_KEYWORD), - COURSE_NOT(TEXT, NOT_ALL_OF, COURSE_CODE_KEYWORD), - COURSE_SHOULD(TEXT, ANY_OF, COURSE_CODE_KEYWORD), - CREATED_BEFORE(DATE, LESS_THAN, CREATED_DATE), - CREATED_SINCE(DATE, GREATER_THAN_OR_EQUAL_TO, CREATED_DATE), - CRISTIN_IDENTIFIER(CUSTOM), - DOI(FUZZY_KEYWORD, REFERENCE_DOI_KEYWORD), - DOI_NOT(FUZZY_KEYWORD, NOT_ALL_OF, REFERENCE_DOI_KEYWORD), - DOI_SHOULD(TEXT, ANY_OF, REFERENCE_DOI_KEYWORD), - /** excludeSubUnits holds path to hierarchical search, used by several keys. */ - EXCLUDE_SUBUNITS(FLAG, Constants.CONTRIBUTOR_ORG_KEYWORD), - FUNDING( - CUSTOM, - ALL_OF, - FUNDINGS_IDENTIFIER_FUNDINGS_SOURCE_IDENTIFIER, - null, - PATTERN_IS_FUNDING), - FUNDING_IDENTIFIER(KEYWORD, ALL_OF, FUNDING_IDENTIFIER_KEYWORD, PATTERN_IS_FUNDING_IDENTIFIER), - FUNDING_IDENTIFIER_NOT( - KEYWORD, NOT_ALL_OF, FUNDING_IDENTIFIER_KEYWORD, PATTERN_IS_FUNDING_IDENTIFIER_NOT), - FUNDING_IDENTIFIER_SHOULD( - FUZZY_KEYWORD, - ANY_OF, - FUNDING_IDENTIFIER_KEYWORD, - PATTERN_IS_FUNDING_IDENTIFIER_SHOULD), - FUNDING_SOURCE(TEXT, ALL_OF, FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS), - FUNDING_SOURCE_NOT(TEXT, NOT_ALL_OF, FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS), - FUNDING_SOURCE_SHOULD(TEXT, ANY_OF, FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS), - HANDLE(FUZZY_KEYWORD, ANY_OF, HANDLE_KEYWORD, PHI), - HANDLE_OF_PARENT(PART_OF, ALL_OF, HANDLE), - HANDLE_NOT(FUZZY_KEYWORD, NOT_ANY_OF, HANDLE_KEYWORD, PHI), - FILES(KEYWORD, ALL_OF, FILES_STATUS_KEYWORD), - ID(KEYWORD, ANY_OF, IDENTIFIER_KEYWORD), - ID_NOT(KEYWORD, NOT_ANY_OF, IDENTIFIER_KEYWORD), - ID_SHOULD(TEXT, ANY_OF, IDENTIFIER_KEYWORD), - INSTANCE_TYPE(KEYWORD, ANY_OF, PUBLICATION_INSTANCE_TYPE, PATTERN_IS_CATEGORY_KEYS), - INSTANCE_TYPE_NOT(KEYWORD, NOT_ANY_OF, PUBLICATION_INSTANCE_TYPE, PATTERN_IS_CATEGORY_NOT_KEYS), - INSTANCE_TYPE_OF_PARENT(PART_OF, ALL_OF, INSTANCE_TYPE), - INSTANCE_TYPE_OF_CHILD(HAS_PARTS, ALL_OF, INSTANCE_TYPE), - INSTITUTION(TEXT, ALL_OF, ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION), - INSTITUTION_NOT(TEXT, NOT_ALL_OF, ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION), - INSTITUTION_SHOULD(TEXT, ANY_OF, ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION), - ISBN(KEYWORD, ANY_OF, PUBLICATION_CONTEXT_ISBN_LIST), - ISBN_NOT(KEYWORD, NOT_ANY_OF, PUBLICATION_CONTEXT_ISBN_LIST), - ISBN_SHOULD(FUZZY_KEYWORD, ANY_OF, PUBLICATION_CONTEXT_ISBN_LIST), - ISSN(KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN), - ISSN_NOT(KEYWORD, NOT_ANY_OF, ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN), - ISSN_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN), - JOURNAL(FUZZY_KEYWORD, ALL_OF, ENTITY_DESCRIPTION_REFERENCE_JOURNAL), - JOURNAL_NOT(FUZZY_KEYWORD, NOT_ALL_OF, ENTITY_DESCRIPTION_REFERENCE_JOURNAL), - JOURNAL_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_JOURNAL), - LICENSE(FUZZY_KEYWORD, ALL_OF, ASSOCIATED_ARTIFACTS_LICENSE), - LICENSE_NOT(FUZZY_KEYWORD, NOT_ALL_OF, ASSOCIATED_ARTIFACTS_LICENSE), - MODIFIED_BEFORE(DATE, LESS_THAN, MODIFIED_DATE), - MODIFIED_SINCE(DATE, GREATER_THAN_OR_EQUAL_TO, MODIFIED_DATE), - ORCID(KEYWORD, ALL_OF, CONTRIBUTORS_IDENTITY_ORC_ID_KEYWORD), - ORCID_NOT(KEYWORD, NOT_ALL_OF, CONTRIBUTORS_IDENTITY_ORC_ID_KEYWORD), - ORCID_SHOULD(TEXT, ANY_OF, CONTRIBUTORS_IDENTITY_ORC_ID_KEYWORD), - PARENT_PUBLICATION(KEYWORD, ANY_OF, PARENT_PUBLICATION_ID), - PARENT_PUBLICATION_EXIST(EXISTS, ANY_OF, PARENT_PUBLICATION_ID), - PROJECT(KEYWORD, ANY_OF, PROJECTS_ID), - PROJECT_NOT(KEYWORD, NOT_ANY_OF, PROJECTS_ID), - PROJECT_SHOULD(FUZZY_KEYWORD, ANY_OF, PROJECTS_ID, PHI), - PUBLICATION_LANGUAGE(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_LANGUAGE), - PUBLICATION_LANGUAGE_NOT(FUZZY_KEYWORD, NOT_ANY_OF, ENTITY_DESCRIPTION_LANGUAGE), - PUBLICATION_LANGUAGE_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_LANGUAGE), - PUBLICATION_PAGES(NUMBER, BETWEEN, ENTITY_DESCRIPTION_PUBLICATION_PAGES), - PUBLICATION_PAGES_EXISTS(EXISTS, ANY_OF, ENTITY_DESCRIPTION_PUBLICATION_PAGES), - PUBLICATION_YEAR(NUMBER, BETWEEN, ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR), - PUBLICATION_YEAR_BEFORE(NUMBER, LESS_THAN, ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR), - PUBLICATION_YEAR_SINCE( - NUMBER, GREATER_THAN_OR_EQUAL_TO, ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR), - PUBLISHED_BEFORE(DATE, LESS_THAN, PUBLISHED_DATE), - PUBLISHED_BETWEEN(DATE, BETWEEN, PUBLISHED_DATE), - PUBLISHED_SINCE(DATE, GREATER_THAN_OR_EQUAL_TO, PUBLISHED_DATE), - PUBLISHER(FUZZY_KEYWORD, ALL_OF, PUBLICATION_CONTEXT_PUBLISHER), - PUBLISHER_NOT(FUZZY_KEYWORD, NOT_ALL_OF, PUBLICATION_CONTEXT_PUBLISHER), - PUBLISHER_SHOULD(FUZZY_KEYWORD, ANY_OF, PUBLICATION_CONTEXT_PUBLISHER), - PUBLISHER_ID(FUZZY_KEYWORD, ANY_OF, PUBLISHER_ID_KEYWORD), - PUBLISHER_ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLISHER_ID_KEYWORD), - PUBLISHER_ID_SHOULD(TEXT, ANY_OF, PUBLISHER_ID_KEYWORD), - REFERENCE_CONTEXT_REFERENCE_EXISTS( - EXISTS, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_CONTEXT_REFERENCE), - REFERENCE_EXISTS(CUSTOM, ALL_OF, ENTITY_DESCRIPTION_REFERENCE_CONTEXT_REFERENCE), - REFERENCED_ID(FUZZY_KEYWORD, ANY_OF, REFERENCE_PUBLICATION_CONTEXT_ID_KEYWORD), - SCIENTIFIC_VALUE(CUSTOM, ANY_OF, SCIENTIFIC_LEVEL_SEARCH_FIELD), - // SCIENTIFIC_VALUE_OF_PARENT(PART_OF, ANY_OF, SCIENTIFIC_VALUE), - SCIENTIFIC_INDEX_STATUS(KEYWORD, ANY_OF, SCIENTIFIC_INDEX_STATUS_KEYWORD), - SCIENTIFIC_INDEX_STATUS_NOT(KEYWORD, NOT_ANY_OF, SCIENTIFIC_INDEX_STATUS_KEYWORD), - SCIENTIFIC_REPORT_PERIOD(NUMBER, BETWEEN, SCIENTIFIC_INDEX_YEAR), - SCIENTIFIC_REPORT_PERIOD_SINCE(NUMBER, GREATER_THAN_OR_EQUAL_TO, SCIENTIFIC_INDEX_YEAR), - SCIENTIFIC_REPORT_PERIOD_BEFORE(NUMBER, LESS_THAN, SCIENTIFIC_INDEX_YEAR), - SCOPUS_IDENTIFIER(CUSTOM), - SERIES(FUZZY_KEYWORD, ALL_OF, ENTITY_DESCRIPTION_REFERENCE_SERIES), - SERIES_NOT(FUZZY_KEYWORD, NOT_ALL_OF, ENTITY_DESCRIPTION_REFERENCE_SERIES), - SERIES_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_SERIES), - STATISTICS(FLAG), - STATUS(KEYWORD, ANY_OF, STATUS_KEYWORD), - STATUS_NOT(KEYWORD, NOT_ANY_OF, STATUS_KEYWORD), - TAGS(TEXT, ALL_OF, ENTITY_TAGS), - TAGS_NOT(TEXT, NOT_ALL_OF, ENTITY_TAGS), - TAGS_SHOULD(TEXT, ANY_OF, ENTITY_TAGS), - TAGS_EXISTS(EXISTS, ANY_OF, ENTITY_TAGS), - TITLE(TEXT, ALL_OF, ENTITY_DESCRIPTION_MAIN_TITLE, PI), - TITLE_NOT(TEXT, NOT_ALL_OF, ENTITY_DESCRIPTION_MAIN_TITLE), - TITLE_SHOULD(TEXT, ANY_OF, ENTITY_DESCRIPTION_MAIN_TITLE), - TOP_LEVEL_ORGANIZATION(CUSTOM, ANY_OF, TOP_LEVEL_ORG_ID), - UNIDENTIFIED_CONTRIBUTOR_INSTITUTION(CUSTOM, ALL_OF, CONTRIBUTORS_FIELDS), - UNIDENTIFIED_NORWEGIAN(CUSTOM, ALL_OF, CONTRIBUTORS_FIELDS), - UNIT(CUSTOM, ALL_OF, CONTRIBUTORS_AFFILIATION_ID_KEYWORD), - UNIT_NOT(CUSTOM, NOT_ALL_OF, CONTRIBUTORS_AFFILIATION_ID_KEYWORD), - UNIT_SHOULD(TEXT, ANY_OF, CONTRIBUTORS_AFFILIATION_ID_KEYWORD), - USER(KEYWORD, ANY_OF, RESOURCE_OWNER_OWNER_KEYWORD), - USER_NOT(KEYWORD, NOT_ANY_OF, RESOURCE_OWNER_OWNER_KEYWORD), - USER_SHOULD(TEXT, ANY_OF, RESOURCE_OWNER_OWNER_KEYWORD), - USER_AFFILIATION(KEYWORD, ANY_OF, RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD), - USER_AFFILIATION_NOT(KEYWORD, NOT_ANY_OF, RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD), - USER_AFFILIATION_SHOULD(TEXT, ANY_OF, RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD), - VOCABULARY(FUZZY_KEYWORD, ALL_OF, SUBJECTS), - VOCABULARY_EXISTS(EXISTS, ANY_OF, SUBJECTS), - SEARCH_ALL(CUSTOM, ANY_OF, Q, PATTERN_IS_SEARCH_ALL_KEY), - GET_ALL(FREE_TEXT), - HAS_CHILDREN(HAS_PARTS, ALL_OF, GET_ALL), - HAS_NO_CHILDREN(HAS_PARTS, NOT_ALL_OF, GET_ALL), - HAS_PARENT(PART_OF, ALL_OF, GET_ALL), - HAS_NO_PARENT(PART_OF, NOT_ALL_OF, GET_ALL), - // Query parameters passed to SWS/Opensearch - AGGREGATION(FLAG), - NODES_SEARCHED(FLAG, null, null, PATTERN_IS_FIELDS_SEARCHED), - NODES_INCLUDED(FLAG), - NODES_EXCLUDED(FLAG), - // Pagination parameters - PAGE(NUMBER), - FROM(NUMBER, null, null, PATTERN_IS_FROM_KEY), - SIZE(NUMBER, null, null, PATTERN_IS_SIZE_KEY), - SEARCH_AFTER(FLAG), - SORT(ParameterKind.SORT_KEY, null, null, PATTERN_IS_SORT_KEY), - SORT_ORDER(FLAG, ALL_OF, null, PATTERN_IS_SORT_ORDER_KEY, PATTERN_IS_ASC_DESC_VALUE); - - public static final int IGNORE_PARAMETER_INDEX = 0; - - public static final Set RESOURCE_PARAMETER_SET = - Arrays.stream(values()) - .filter(ResourceParameter::isSearchField) - .sorted(ParameterKey::compareAscending) - .collect(Collectors.toCollection(LinkedHashSet::new)); - private final ValueEncoding encoding; - private final String keyPattern; - private final String validValuePattern; - private final String[] fieldsToSearch; - private final FieldOperator fieldOperator; - private final String errorMsg; - private final ParameterKind paramkind; - private final Float boost; - private final ResourceParameter subQueryReference; - - ResourceParameter(ParameterKind kind) { - this(kind, ALL_OF, null, null, null, null, null); - } - - ResourceParameter( - ParameterKind kind, - FieldOperator operator, - String fieldsToSearch, - String keyPattern, - String valuePattern, - Float boost, - ResourceParameter subquery) { - - this.fieldOperator = nonNull(operator) ? operator : NA; - this.boost = nonNull(boost) ? boost : 1F; - this.fieldsToSearch = - nonNull(fieldsToSearch) ? fieldsToSearch.split("\\|") : new String[] {name()}; - this.validValuePattern = ParameterKey.getValuePattern(kind, valuePattern); - this.errorMsg = ParameterKey.getErrorMessage(kind); - this.encoding = ParameterKey.getEncoding(kind); - this.keyPattern = - nonNull(keyPattern) - ? keyPattern - : PATTERN_IS_IGNORE_CASE - + name().replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); - this.paramkind = kind; - this.subQueryReference = subquery; - } - - ResourceParameter(ParameterKind kind, String fieldsToSearch) { - this(kind, ALL_OF, fieldsToSearch, null, null, null, null); - } - - ResourceParameter(ParameterKind kind, FieldOperator operator, String fieldsToSearch) { - this(kind, operator, fieldsToSearch, null, null, null, null); - } - - ResourceParameter(ParameterKind kind, FieldOperator operator, ResourceParameter keys) { - this(kind, operator, null, null, null, null, keys); - } - - ResourceParameter( - ParameterKind kind, FieldOperator operator, String fieldsToSearch, Float boost) { - this(kind, operator, fieldsToSearch, null, null, boost, null); - } - - ResourceParameter( - ParameterKind kind, FieldOperator operator, String fieldsToSearch, String keyPattern) { - this(kind, operator, fieldsToSearch, keyPattern, null, null, null); - } - - ResourceParameter( - ParameterKind kind, - FieldOperator operator, - String fieldsToSearch, - String keyPattern, - String validValuePattern) { - this(kind, operator, fieldsToSearch, keyPattern, validValuePattern, null, null); - } - - public static ResourceParameter keyFromString(String paramName) { - var result = - Arrays.stream(values()) - .filter(ResourceParameter::ignoreInvalidKey) - .filter(ParameterKey.equalTo(paramName)) - .collect(Collectors.toSet()); - return result.size() == 1 ? result.stream().findFirst().get() : INVALID; - } - - private static boolean ignoreInvalidKey(ResourceParameter enumParameter) { - return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX; - } - - private static boolean isSearchField(ResourceParameter enumParameter) { - return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX - && enumParameter.ordinal() < AGGREGATION.ordinal(); - } - - @Override - @JacocoGenerated - public String toString() { - return new StringJoiner(COLON, "Key[", "]") - .add(String.valueOf(ordinal())) - .add(asCamelCase()) - .toString(); - } - - @Override - public String asCamelCase() { - return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); - } - - @Override - public String asLowerCase() { - return this.name().toLowerCase(Locale.getDefault()); - } - - @Override - public Float fieldBoost() { - return boost; - } - - @Override - public ParameterKind fieldType() { - return paramkind; - } - - @Override - public String fieldPattern() { - return keyPattern; - } - - @Override - public String valuePattern() { - return validValuePattern; - } - - @Override - public ValueEncoding valueEncoding() { - return encoding; - } - - @Override - public Stream searchFields(boolean... isKeyWord) { - return Arrays.stream(fieldsToSearch).map(ParameterKey.trimKeyword(fieldType(), isKeyWord)); - } - - @Override - public FieldOperator searchOperator() { - return fieldOperator; - } - - @Override - public String errorMessage() { - return errorMsg; - } - - @Override - public ResourceParameter subQuery() { - return subQueryReference; - } + INVALID(ParameterKind.INVALID), + // Parameters used for filtering + ABSTRACT(TEXT, ALL_OF, ENTITY_ABSTRACT), + ABSTRACT_OF_CHILD(HAS_PARTS, ALL_OF, ABSTRACT), + ABSTRACT_NOT(TEXT, NOT_ALL_OF, ENTITY_ABSTRACT), + ABSTRACT_SHOULD(TEXT, ANY_OF, ENTITY_ABSTRACT), + CONTEXT_TYPE(KEYWORD, ALL_OF, PUBLICATION_CONTEXT_TYPE_KEYWORD), + CONTEXT_TYPE_OF_NO_CHILD(HAS_PARTS, NOT_ANY_OF, null, null, null, null, CONTEXT_TYPE), + CONTEXT_TYPE_NOT(KEYWORD, NOT_ALL_OF, PUBLICATION_CONTEXT_TYPE_KEYWORD), + CONTEXT_TYPE_SHOULD(KEYWORD, ANY_OF, PUBLICATION_CONTEXT_TYPE_KEYWORD), + CONTRIBUTORS(ACROSS_FIELDS, ALL_OF, CONTRIBUTORS_FIELDS), + CONTRIBUTORS_OF_CHILD(HAS_PARTS, ALL_OF, CONTRIBUTORS), + CONTRIBUTOR(KEYWORD, ALL_OF, CONTRIBUTORS_IDENTITY_ID, null, PATTERN_IS_URI), + CONTRIBUTOR_COUNT(NUMBER, BETWEEN, CONTRIBUTOR_COUNT_NO_KEYWORD), + CONTRIBUTOR_NOT(KEYWORD, NOT_ALL_OF, CONTRIBUTORS_IDENTITY_ID, null, PATTERN_IS_URI), + CONTRIBUTOR_SHOULD(KEYWORD, ANY_OF, CONTRIBUTORS_IDENTITY_ID, null, PATTERN_IS_URI), + CONTRIBUTOR_NAME(FUZZY_KEYWORD, ALL_OF, CONTRIBUTORS_IDENTITY_NAME_KEYWORD), + CONTRIBUTOR_NAME_NOT(FUZZY_KEYWORD, NOT_ALL_OF, CONTRIBUTORS_IDENTITY_NAME_KEYWORD), + CONTRIBUTOR_NAME_SHOULD(TEXT, ANY_OF, CONTRIBUTORS_IDENTITY_NAME_KEYWORD), + COURSE(TEXT, ALL_OF, COURSE_CODE_KEYWORD), + COURSE_NOT(TEXT, NOT_ALL_OF, COURSE_CODE_KEYWORD), + COURSE_SHOULD(TEXT, ANY_OF, COURSE_CODE_KEYWORD), + CREATED_BEFORE(DATE, LESS_THAN, CREATED_DATE), + CREATED_SINCE(DATE, GREATER_THAN_OR_EQUAL_TO, CREATED_DATE), + CRISTIN_IDENTIFIER(CUSTOM), + DOI(FUZZY_KEYWORD, REFERENCE_DOI_KEYWORD), + DOI_NOT(FUZZY_KEYWORD, NOT_ALL_OF, REFERENCE_DOI_KEYWORD), + DOI_SHOULD(TEXT, ANY_OF, REFERENCE_DOI_KEYWORD), + /** excludeSubUnits holds path to hierarchical search, used by several keys. */ + EXCLUDE_SUBUNITS(FLAG, Constants.CONTRIBUTOR_ORG_KEYWORD), + FUNDING(CUSTOM, ALL_OF, FUNDINGS_IDENTIFIER_FUNDINGS_SOURCE_IDENTIFIER, null, PATTERN_IS_FUNDING), + FUNDING_IDENTIFIER(KEYWORD, ALL_OF, FUNDING_IDENTIFIER_KEYWORD, PATTERN_IS_FUNDING_IDENTIFIER), + FUNDING_IDENTIFIER_NOT( + KEYWORD, NOT_ALL_OF, FUNDING_IDENTIFIER_KEYWORD, PATTERN_IS_FUNDING_IDENTIFIER_NOT), + FUNDING_IDENTIFIER_SHOULD( + FUZZY_KEYWORD, ANY_OF, FUNDING_IDENTIFIER_KEYWORD, PATTERN_IS_FUNDING_IDENTIFIER_SHOULD), + FUNDING_SOURCE(TEXT, ALL_OF, FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS), + FUNDING_SOURCE_NOT(TEXT, NOT_ALL_OF, FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS), + FUNDING_SOURCE_SHOULD(TEXT, ANY_OF, FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS), + HANDLE(FUZZY_KEYWORD, ANY_OF, HANDLE_KEYWORD, PHI), + HANDLE_OF_PARENT(PART_OF, ALL_OF, HANDLE), + HANDLE_NOT(FUZZY_KEYWORD, NOT_ANY_OF, HANDLE_KEYWORD, PHI), + FILES(KEYWORD, ALL_OF, FILES_STATUS_KEYWORD), + ID(KEYWORD, ANY_OF, IDENTIFIER_KEYWORD), + ID_NOT(KEYWORD, NOT_ANY_OF, IDENTIFIER_KEYWORD), + ID_SHOULD(TEXT, ANY_OF, IDENTIFIER_KEYWORD), + INSTANCE_TYPE(KEYWORD, ANY_OF, PUBLICATION_INSTANCE_TYPE, PATTERN_IS_CATEGORY_KEYS), + INSTANCE_TYPE_NOT(KEYWORD, NOT_ANY_OF, PUBLICATION_INSTANCE_TYPE, PATTERN_IS_CATEGORY_NOT_KEYS), + INSTANCE_TYPE_OF_PARENT(PART_OF, ALL_OF, INSTANCE_TYPE), + INSTANCE_TYPE_OF_CHILD(HAS_PARTS, ALL_OF, INSTANCE_TYPE), + INSTITUTION(TEXT, ALL_OF, ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION), + INSTITUTION_NOT(TEXT, NOT_ALL_OF, ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION), + INSTITUTION_SHOULD(TEXT, ANY_OF, ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION), + ISBN(KEYWORD, ANY_OF, PUBLICATION_CONTEXT_ISBN_LIST), + ISBN_NOT(KEYWORD, NOT_ANY_OF, PUBLICATION_CONTEXT_ISBN_LIST), + ISBN_SHOULD(FUZZY_KEYWORD, ANY_OF, PUBLICATION_CONTEXT_ISBN_LIST), + ISSN(KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN), + ISSN_NOT(KEYWORD, NOT_ANY_OF, ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN), + ISSN_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN), + JOURNAL(FUZZY_KEYWORD, ALL_OF, ENTITY_DESCRIPTION_REFERENCE_JOURNAL), + JOURNAL_NOT(FUZZY_KEYWORD, NOT_ALL_OF, ENTITY_DESCRIPTION_REFERENCE_JOURNAL), + JOURNAL_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_JOURNAL), + LICENSE(FUZZY_KEYWORD, ALL_OF, ASSOCIATED_ARTIFACTS_LICENSE), + LICENSE_NOT(FUZZY_KEYWORD, NOT_ALL_OF, ASSOCIATED_ARTIFACTS_LICENSE), + MODIFIED_BEFORE(DATE, LESS_THAN, MODIFIED_DATE), + MODIFIED_SINCE(DATE, GREATER_THAN_OR_EQUAL_TO, MODIFIED_DATE), + ORCID(KEYWORD, ALL_OF, CONTRIBUTORS_IDENTITY_ORC_ID_KEYWORD), + ORCID_NOT(KEYWORD, NOT_ALL_OF, CONTRIBUTORS_IDENTITY_ORC_ID_KEYWORD), + ORCID_SHOULD(TEXT, ANY_OF, CONTRIBUTORS_IDENTITY_ORC_ID_KEYWORD), + PARENT_PUBLICATION(KEYWORD, ANY_OF, PARENT_PUBLICATION_ID), + PARENT_PUBLICATION_EXIST(EXISTS, ANY_OF, PARENT_PUBLICATION_ID), + PROJECT(KEYWORD, ANY_OF, PROJECTS_ID), + PROJECT_NOT(KEYWORD, NOT_ANY_OF, PROJECTS_ID), + PROJECT_SHOULD(FUZZY_KEYWORD, ANY_OF, PROJECTS_ID, PHI), + PUBLICATION_LANGUAGE(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_LANGUAGE), + PUBLICATION_LANGUAGE_NOT(FUZZY_KEYWORD, NOT_ANY_OF, ENTITY_DESCRIPTION_LANGUAGE), + PUBLICATION_LANGUAGE_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_LANGUAGE), + PUBLICATION_PAGES(NUMBER, BETWEEN, ENTITY_DESCRIPTION_PUBLICATION_PAGES), + PUBLICATION_PAGES_EXISTS(EXISTS, ANY_OF, ENTITY_DESCRIPTION_PUBLICATION_PAGES), + PUBLICATION_YEAR(NUMBER, BETWEEN, ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR), + PUBLICATION_YEAR_BEFORE(NUMBER, LESS_THAN, ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR), + PUBLICATION_YEAR_SINCE( + NUMBER, GREATER_THAN_OR_EQUAL_TO, ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR), + PUBLISHED_BEFORE(DATE, LESS_THAN, PUBLISHED_DATE), + PUBLISHED_BETWEEN(DATE, BETWEEN, PUBLISHED_DATE), + PUBLISHED_SINCE(DATE, GREATER_THAN_OR_EQUAL_TO, PUBLISHED_DATE), + PUBLISHER(FUZZY_KEYWORD, ALL_OF, PUBLICATION_CONTEXT_PUBLISHER), + PUBLISHER_NOT(FUZZY_KEYWORD, NOT_ALL_OF, PUBLICATION_CONTEXT_PUBLISHER), + PUBLISHER_SHOULD(FUZZY_KEYWORD, ANY_OF, PUBLICATION_CONTEXT_PUBLISHER), + PUBLISHER_ID(FUZZY_KEYWORD, ANY_OF, PUBLISHER_ID_KEYWORD), + PUBLISHER_ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLISHER_ID_KEYWORD), + PUBLISHER_ID_SHOULD(TEXT, ANY_OF, PUBLISHER_ID_KEYWORD), + REFERENCE_CONTEXT_REFERENCE_EXISTS( + EXISTS, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_CONTEXT_REFERENCE), + REFERENCE_EXISTS(CUSTOM, ALL_OF, ENTITY_DESCRIPTION_REFERENCE_CONTEXT_REFERENCE), + REFERENCED_ID(FUZZY_KEYWORD, ANY_OF, REFERENCE_PUBLICATION_CONTEXT_ID_KEYWORD), + SCIENTIFIC_VALUE(CUSTOM, ANY_OF, SCIENTIFIC_LEVEL_SEARCH_FIELD), + // SCIENTIFIC_VALUE_OF_PARENT(PART_OF, ANY_OF, SCIENTIFIC_VALUE), + SCIENTIFIC_INDEX_STATUS(KEYWORD, ANY_OF, SCIENTIFIC_INDEX_STATUS_KEYWORD), + SCIENTIFIC_INDEX_STATUS_NOT(KEYWORD, NOT_ANY_OF, SCIENTIFIC_INDEX_STATUS_KEYWORD), + SCIENTIFIC_REPORT_PERIOD(NUMBER, BETWEEN, SCIENTIFIC_INDEX_YEAR), + SCIENTIFIC_REPORT_PERIOD_SINCE(NUMBER, GREATER_THAN_OR_EQUAL_TO, SCIENTIFIC_INDEX_YEAR), + SCIENTIFIC_REPORT_PERIOD_BEFORE(NUMBER, LESS_THAN, SCIENTIFIC_INDEX_YEAR), + SCOPUS_IDENTIFIER(CUSTOM), + SERIES(FUZZY_KEYWORD, ALL_OF, ENTITY_DESCRIPTION_REFERENCE_SERIES), + SERIES_NOT(FUZZY_KEYWORD, NOT_ALL_OF, ENTITY_DESCRIPTION_REFERENCE_SERIES), + SERIES_SHOULD(FUZZY_KEYWORD, ANY_OF, ENTITY_DESCRIPTION_REFERENCE_SERIES), + STATISTICS(FLAG), + STATUS(KEYWORD, ANY_OF, STATUS_KEYWORD), + STATUS_NOT(KEYWORD, NOT_ANY_OF, STATUS_KEYWORD), + TAGS(TEXT, ALL_OF, ENTITY_TAGS), + TAGS_NOT(TEXT, NOT_ALL_OF, ENTITY_TAGS), + TAGS_SHOULD(TEXT, ANY_OF, ENTITY_TAGS), + TAGS_EXISTS(EXISTS, ANY_OF, ENTITY_TAGS), + TITLE(TEXT, ALL_OF, ENTITY_DESCRIPTION_MAIN_TITLE, PI), + TITLE_NOT(TEXT, NOT_ALL_OF, ENTITY_DESCRIPTION_MAIN_TITLE), + TITLE_SHOULD(TEXT, ANY_OF, ENTITY_DESCRIPTION_MAIN_TITLE), + TOP_LEVEL_ORGANIZATION(CUSTOM, ANY_OF, TOP_LEVEL_ORG_ID), + UNIDENTIFIED_CONTRIBUTOR_INSTITUTION(CUSTOM, ALL_OF, CONTRIBUTORS_FIELDS), + UNIDENTIFIED_NORWEGIAN(CUSTOM, ALL_OF, CONTRIBUTORS_FIELDS), + UNIT(CUSTOM, ALL_OF, CONTRIBUTORS_AFFILIATION_ID_KEYWORD), + UNIT_NOT(CUSTOM, NOT_ALL_OF, CONTRIBUTORS_AFFILIATION_ID_KEYWORD), + UNIT_SHOULD(TEXT, ANY_OF, CONTRIBUTORS_AFFILIATION_ID_KEYWORD), + USER(KEYWORD, ANY_OF, RESOURCE_OWNER_OWNER_KEYWORD), + USER_NOT(KEYWORD, NOT_ANY_OF, RESOURCE_OWNER_OWNER_KEYWORD), + USER_SHOULD(TEXT, ANY_OF, RESOURCE_OWNER_OWNER_KEYWORD), + USER_AFFILIATION(KEYWORD, ANY_OF, RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD), + USER_AFFILIATION_NOT(KEYWORD, NOT_ANY_OF, RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD), + USER_AFFILIATION_SHOULD(TEXT, ANY_OF, RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD), + VOCABULARY(FUZZY_KEYWORD, ALL_OF, SUBJECTS), + VOCABULARY_EXISTS(EXISTS, ANY_OF, SUBJECTS), + SEARCH_ALL(CUSTOM, ANY_OF, Q, PATTERN_IS_SEARCH_ALL_KEY), + GET_ALL(FREE_TEXT), + HAS_CHILDREN(HAS_PARTS, ALL_OF, GET_ALL), + HAS_NO_CHILDREN(HAS_PARTS, NOT_ALL_OF, GET_ALL), + HAS_PARENT(PART_OF, ALL_OF, GET_ALL), + HAS_NO_PARENT(PART_OF, NOT_ALL_OF, GET_ALL), + // Query parameters passed to SWS/Opensearch + AGGREGATION(FLAG), + NODES_SEARCHED(FLAG, null, null, PATTERN_IS_FIELDS_SEARCHED), + NODES_INCLUDED(FLAG), + NODES_EXCLUDED(FLAG), + // Pagination parameters + PAGE(NUMBER), + FROM(NUMBER, null, null, PATTERN_IS_FROM_KEY), + SIZE(NUMBER, null, null, PATTERN_IS_SIZE_KEY), + SEARCH_AFTER(FLAG), + SORT(ParameterKind.SORT_KEY, null, null, PATTERN_IS_SORT_KEY), + SORT_ORDER(FLAG, ALL_OF, null, PATTERN_IS_SORT_ORDER_KEY, PATTERN_IS_ASC_DESC_VALUE); + + public static final int IGNORE_PARAMETER_INDEX = 0; + + public static final Set RESOURCE_PARAMETER_SET = + Arrays.stream(values()) + .filter(ResourceParameter::isSearchField) + .sorted(ParameterKey::compareAscending) + .collect(Collectors.toCollection(LinkedHashSet::new)); + private final ValueEncoding encoding; + private final String keyPattern; + private final String validValuePattern; + private final String[] fieldsToSearch; + private final FieldOperator fieldOperator; + private final String errorMsg; + private final ParameterKind paramkind; + private final Float boost; + private final ResourceParameter subQueryReference; + + ResourceParameter(ParameterKind kind) { + this(kind, ALL_OF, null, null, null, null, null); + } + + ResourceParameter( + ParameterKind kind, + FieldOperator operator, + String fieldsToSearch, + String keyPattern, + String valuePattern, + Float boost, + ResourceParameter subquery) { + + this.fieldOperator = nonNull(operator) ? operator : NA; + this.boost = nonNull(boost) ? boost : 1F; + this.fieldsToSearch = + nonNull(fieldsToSearch) ? fieldsToSearch.split("\\|") : new String[] {name()}; + this.validValuePattern = ParameterKey.getValuePattern(kind, valuePattern); + this.errorMsg = ParameterKey.getErrorMessage(kind); + this.encoding = ParameterKey.getEncoding(kind); + this.keyPattern = + nonNull(keyPattern) + ? keyPattern + : PATTERN_IS_IGNORE_CASE + name().replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); + this.paramkind = kind; + this.subQueryReference = subquery; + } + + ResourceParameter(ParameterKind kind, String fieldsToSearch) { + this(kind, ALL_OF, fieldsToSearch, null, null, null, null); + } + + ResourceParameter(ParameterKind kind, FieldOperator operator, String fieldsToSearch) { + this(kind, operator, fieldsToSearch, null, null, null, null); + } + + ResourceParameter(ParameterKind kind, FieldOperator operator, ResourceParameter keys) { + this(kind, operator, null, null, null, null, keys); + } + + ResourceParameter( + ParameterKind kind, FieldOperator operator, String fieldsToSearch, Float boost) { + this(kind, operator, fieldsToSearch, null, null, boost, null); + } + + ResourceParameter( + ParameterKind kind, FieldOperator operator, String fieldsToSearch, String keyPattern) { + this(kind, operator, fieldsToSearch, keyPattern, null, null, null); + } + + ResourceParameter( + ParameterKind kind, + FieldOperator operator, + String fieldsToSearch, + String keyPattern, + String validValuePattern) { + this(kind, operator, fieldsToSearch, keyPattern, validValuePattern, null, null); + } + + public static ResourceParameter keyFromString(String paramName) { + var result = + Arrays.stream(values()) + .filter(ResourceParameter::ignoreInvalidKey) + .filter(ParameterKey.equalTo(paramName)) + .collect(Collectors.toSet()); + return result.size() == 1 ? result.stream().findFirst().get() : INVALID; + } + + private static boolean ignoreInvalidKey(ResourceParameter enumParameter) { + return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX; + } + + private static boolean isSearchField(ResourceParameter enumParameter) { + return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX + && enumParameter.ordinal() < AGGREGATION.ordinal(); + } + + @Override + @JacocoGenerated + public String toString() { + return new StringJoiner(COLON, "Key[", "]") + .add(String.valueOf(ordinal())) + .add(asCamelCase()) + .toString(); + } + + @Override + public String asCamelCase() { + return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); + } + + @Override + public String asLowerCase() { + return this.name().toLowerCase(Locale.getDefault()); + } + + @Override + public Float fieldBoost() { + return boost; + } + + @Override + public ParameterKind fieldType() { + return paramkind; + } + + @Override + public String fieldPattern() { + return keyPattern; + } + + @Override + public String valuePattern() { + return validValuePattern; + } + + @Override + public ValueEncoding valueEncoding() { + return encoding; + } + + @Override + public Stream searchFields(boolean... isKeyWord) { + return Arrays.stream(fieldsToSearch).map(ParameterKey.trimKeyword(fieldType(), isKeyWord)); + } + + @Override + public FieldOperator searchOperator() { + return fieldOperator; + } + + @Override + public String errorMessage() { + return errorMsg; + } + + @Override + public ResourceParameter subQuery() { + return subQueryReference; + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSearchQuery.java b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSearchQuery.java index 6677387b8..292701342 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSearchQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSearchQuery.java @@ -1,5 +1,6 @@ package no.unit.nva.search.resource; +import static java.lang.String.format; import static no.unit.nva.constants.Defaults.DEFAULT_OFFSET; import static no.unit.nva.constants.Defaults.DEFAULT_VALUE_PER_PAGE; import static no.unit.nva.constants.ErrorMessages.INVALID_VALUE_WITH_SORT; @@ -34,14 +35,21 @@ import static no.unit.nva.search.resource.ResourceParameter.SIZE; import static no.unit.nva.search.resource.ResourceParameter.SORT; import static no.unit.nva.search.resource.ResourceSort.INVALID; - import static nva.commons.core.attempt.Try.attempt; import static nva.commons.core.paths.UriWrapper.fromUri; - import static org.opensearch.index.query.QueryBuilders.matchQuery; -import static java.lang.String.format; - +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.constants.Words; import no.unit.nva.search.common.AsType; import no.unit.nva.search.common.OpenSearchClient; @@ -50,27 +58,13 @@ import no.unit.nva.search.common.SearchQuery; import no.unit.nva.search.common.enums.SortKey; import no.unit.nva.search.common.records.HttpResponseFormatter; - import nva.commons.core.JacocoGenerated; - import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.TermsQueryBuilder; import org.opensearch.search.aggregations.AggregationBuilder; import org.opensearch.search.sort.SortOrder; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * ResourceSearchQuery is a query for searching resources. * diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSort.java b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSort.java index 6d30e083a..b372404b2 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSort.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceSort.java @@ -7,20 +7,17 @@ import static no.unit.nva.constants.Words.YEAR; import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_PIPE; import static no.unit.nva.search.resource.Constants.IDENTIFIER_KEYWORD; - import static nva.commons.core.StringUtils.EMPTY_STRING; -import no.unit.nva.constants.Words; -import no.unit.nva.search.common.enums.SortKey; - -import org.apache.commons.text.CaseUtils; - import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Locale; import java.util.stream.Collectors; import java.util.stream.Stream; +import no.unit.nva.constants.Words; +import no.unit.nva.search.common.enums.SortKey; +import org.apache.commons.text.CaseUtils; /** * Enum for sorting resources. @@ -28,66 +25,63 @@ * @author Stig Norland */ public enum ResourceSort implements SortKey { - INVALID(EMPTY_STRING), - IDENTIFIER(IDENTIFIER_KEYWORD), - RELEVANCE(Words.SCORE), - CATEGORY(Constants.PUBLICATION_INSTANCE_TYPE), - INSTANCE_TYPE(Constants.PUBLICATION_INSTANCE_TYPE), - CREATED_DATE(Words.CREATED_DATE), - MODIFIED_DATE(Words.MODIFIED_DATE), - PUBLISHED_DATE(Words.PUBLISHED_DATE), - PUBLICATION_DATE( - ENTITY_DESCRIPTION + DOT + Words.PUBLICATION_DATE + DOT + YEAR + DOT + KEYWORD), - TITLE(Constants.ENTITY_DESCRIPTION_MAIN_TITLE_KEYWORD), - UNIT_ID(Constants.CONTRIBUTORS_AFFILIATION_ID_KEYWORD), - USER("(?i)(user)|(owner)", Constants.RESOURCE_OWNER_OWNER_KEYWORD); + INVALID(EMPTY_STRING), + IDENTIFIER(IDENTIFIER_KEYWORD), + RELEVANCE(Words.SCORE), + CATEGORY(Constants.PUBLICATION_INSTANCE_TYPE), + INSTANCE_TYPE(Constants.PUBLICATION_INSTANCE_TYPE), + CREATED_DATE(Words.CREATED_DATE), + MODIFIED_DATE(Words.MODIFIED_DATE), + PUBLISHED_DATE(Words.PUBLISHED_DATE), + PUBLICATION_DATE(ENTITY_DESCRIPTION + DOT + Words.PUBLICATION_DATE + DOT + YEAR + DOT + KEYWORD), + TITLE(Constants.ENTITY_DESCRIPTION_MAIN_TITLE_KEYWORD), + UNIT_ID(Constants.CONTRIBUTORS_AFFILIATION_ID_KEYWORD), + USER("(?i)(user)|(owner)", Constants.RESOURCE_OWNER_OWNER_KEYWORD); - private final String keyValidationRegEx; - private final String path; + private final String keyValidationRegEx; + private final String path; - ResourceSort(String pattern, String jsonPath) { - this.keyValidationRegEx = pattern; - this.path = jsonPath; - } + ResourceSort(String pattern, String jsonPath) { + this.keyValidationRegEx = pattern; + this.path = jsonPath; + } - ResourceSort(String jsonPath) { - this.keyValidationRegEx = SortKey.getIgnoreCaseAndUnderscoreKeyExpression(this.name()); - this.path = jsonPath; - } + ResourceSort(String jsonPath) { + this.keyValidationRegEx = SortKey.getIgnoreCaseAndUnderscoreKeyExpression(this.name()); + this.path = jsonPath; + } - public static ResourceSort fromSortKey(String keyName) { - var result = - Arrays.stream(values()) - .filter(SortKey.equalTo(keyName)) - .collect(Collectors.toSet()); - return result.size() == 1 ? result.stream().findFirst().get() : INVALID; - } + public static ResourceSort fromSortKey(String keyName) { + var result = + Arrays.stream(values()).filter(SortKey.equalTo(keyName)).collect(Collectors.toSet()); + return result.size() == 1 ? result.stream().findFirst().get() : INVALID; + } - public static Collection validSortKeys() { - return Arrays.stream(values()) - .sorted(SortKey::compareAscending) - .skip(1) - .map(SortKey::asLowerCase) - .collect(Collectors.toCollection(LinkedHashSet::new)); - } + public static Collection validSortKeys() { + return Arrays.stream(values()) + .sorted(SortKey::compareAscending) + .skip(1) + .map(SortKey::asLowerCase) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } - @Override - public String asCamelCase() { - return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); - } + @Override + public String asCamelCase() { + return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); + } - @Override - public String asLowerCase() { - return this.name().toLowerCase(Locale.getDefault()); - } + @Override + public String asLowerCase() { + return this.name().toLowerCase(Locale.getDefault()); + } - @Override - public String keyPattern() { - return keyValidationRegEx; - } + @Override + public String keyPattern() { + return keyValidationRegEx; + } - @Override - public Stream jsonPaths() { - return Arrays.stream(path.split(PATTERN_IS_PIPE)); - } + @Override + public Stream jsonPaths() { + return Arrays.stream(path.split(PATTERN_IS_PIPE)); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceStreamBuilders.java b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceStreamBuilders.java index 12c3ea9aa..f06f70f54 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/ResourceStreamBuilders.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/ResourceStreamBuilders.java @@ -35,7 +35,6 @@ import static no.unit.nva.search.resource.ResourceParameter.EXCLUDE_SUBUNITS; import static no.unit.nva.search.resource.ResourceParameter.SEARCH_ALL; import static no.unit.nva.search.resource.ResourceParameter.TITLE; - import static org.opensearch.index.query.QueryBuilders.boolQuery; import static org.opensearch.index.query.QueryBuilders.existsQuery; import static org.opensearch.index.query.QueryBuilders.matchPhrasePrefixQuery; @@ -45,20 +44,18 @@ import static org.opensearch.index.query.QueryBuilders.termQuery; import static org.opensearch.index.query.QueryBuilders.termsQuery; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.search.common.QueryKeys; import no.unit.nva.search.common.builder.FuzzyKeywordQuery; import no.unit.nva.search.common.constant.Functions; - import org.apache.lucene.search.join.ScoreMode; import org.opensearch.index.query.MultiMatchQueryBuilder; import org.opensearch.index.query.Operator; import org.opensearch.index.query.QueryBuilder; import org.opensearch.join.query.HasParentQueryBuilder; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Stream builders for resource queries. * diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/SimplifiedMutator.java b/search-commons/src/main/java/no/unit/nva/search/resource/SimplifiedMutator.java index 876c03433..7876980c4 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/SimplifiedMutator.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/SimplifiedMutator.java @@ -1,8 +1,8 @@ package no.unit.nva.search.resource; import static no.unit.nva.commons.json.JsonUtils.dtoObjectMapper; - import static no.unit.nva.constants.Words.DOT; + import static nva.commons.core.attempt.Try.attempt; import com.fasterxml.jackson.annotation.JsonInclude; @@ -29,7 +29,6 @@ import java.io.IOException; import java.net.URI; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -40,360 +39,333 @@ public class SimplifiedMutator implements JsonNodeMutator { - public static final String ID = "id"; - public static final String IDENTIFIER = "identifier"; - public static final String AFFILIATIONS = "affiliations"; - public static final String PUBLICATION_INSTANCE = "publicationInstance"; - public static final String TYPE = "type"; - public static final String ABSTRACT = "abstract"; - public static final String MAIN_TITLE = "mainTitle"; - public static final String DESCRIPTION = "description"; - public static final String ALTERNATIVE_TITLES = "alternativeTitles"; - public static final String PUBLICATION_DATE = "publicationDate"; - public static final String YEAR = "year"; - public static final String MONTH = "month"; - public static final String DAY = "day"; - public static final String CONTRIBUTORS_COUNT = "contributorsCount"; - public static final String ONLINE_ISSN = "onlineIssn"; - public static final String PRINT_ISSN = "printIssn"; - public static final String ISBN_LIST = "isbnList"; - public static final String ADDITIONAL_IDENTIFIERS = "additionalIdentifiers"; - public static final String HANDLE_IDENTIFIER = "HandleIdentifier"; - public static final String VALUE = "value"; - public static final String SCOPUS_IDENTIFIER = "ScopusIdentifier"; - public static final String CRISTIN_IDENTIFIER = "CristinIdentifier"; - public static final String STATUS = "status"; - public static final String CREATED_DATE = "createdDate"; - public static final String MODIFIED_DATE = "modifiedDate"; - public static final String PUBLISHED_DATE = "publishedDate"; - public static final String NAME = "name"; - public static final String DOI = "doi"; - public static final String CONTRIBUTORS_PREVIEW = "contributorsPreview"; - public static final String CORRESPONDING_AUTHOR = "correspondingAuthor"; - public static final String IDENTITY = "identity"; - public static final String ORCID_ID = "orcId"; - public static final String SEQUENCE = "sequence"; - public static final String ROLE = "role"; - public static final String MANIFESTATIONS = "manifestations"; - public static final String SCIENTIFIC_VALUE = "scientificValue"; - public static final String ENTITY_DESCRIPTION = "entityDescription"; - public static final String REFERENCE = "reference"; - public static final String PUBLICATION_CONTEXT = "publicationContext"; - public static final String SERIES = "series"; - public static final String PUBLISHER = "publisher"; - private final ObjectMapper objectMapper = dtoObjectMapper.copy(); - - public SimplifiedMutator() { - objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - } - - public static String path(String... path) { - return String.join(DOT, path); - } - - public static List getIncludedFields() { - return List.of( - ID, - IDENTIFIER, - STATUS, - CREATED_DATE, - MODIFIED_DATE, - PUBLISHED_DATE, - path(ENTITY_DESCRIPTION, REFERENCE, PUBLICATION_INSTANCE, TYPE), - path( - ENTITY_DESCRIPTION, - REFERENCE, - PUBLICATION_INSTANCE, - MANIFESTATIONS, - ISBN_LIST), - path(ENTITY_DESCRIPTION, REFERENCE, DOI), - path( - ENTITY_DESCRIPTION, - REFERENCE, - PUBLICATION_CONTEXT), // TODO: Narrow down further? - path(ENTITY_DESCRIPTION, DESCRIPTION), - path(ENTITY_DESCRIPTION, MAIN_TITLE), - path(ENTITY_DESCRIPTION, ABSTRACT), - path(ENTITY_DESCRIPTION, ALTERNATIVE_TITLES), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_COUNT), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, AFFILIATIONS, ID), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, AFFILIATIONS, TYPE), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, CORRESPONDING_AUTHOR), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, IDENTITY, ID), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, IDENTITY, NAME), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, IDENTITY, ORCID_ID), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, ROLE, TYPE), - path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, SEQUENCE), - path(ENTITY_DESCRIPTION, PUBLICATION_DATE), - path(ADDITIONAL_IDENTIFIERS, TYPE), - path(ADDITIONAL_IDENTIFIERS, VALUE)); - } - - @NotNull - private static PublicationDate mutatePublicationDate(JsonNode source) { - return new PublicationDate( - source.path(ENTITY_DESCRIPTION).path(PUBLICATION_DATE).path(YEAR).textValue(), - source.path(ENTITY_DESCRIPTION).path(PUBLICATION_DATE).path(MONTH).textValue(), - source.path(ENTITY_DESCRIPTION).path(PUBLICATION_DATE).path(DAY).textValue()); - } - - @Override - public JsonNode transform(JsonNode source) { - return (JsonNode) - attempt(() -> objectMapper.valueToTree(transformToDto(source))).orElseThrow(); - } - - private ResourceSearchResponse transformToDto(JsonNode source) throws IOException { - return new Builder() - .withId(uriFromText(source.path(ID).textValue())) - .withIdentifier(source.path(IDENTIFIER).textValue()) - .withType( - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_INSTANCE) - .path(TYPE) - .textValue()) - .withMainTitle(source.path(ENTITY_DESCRIPTION).path(MAIN_TITLE).textValue()) - .withMainLanguageAbstract( - source.path(ENTITY_DESCRIPTION).path(ABSTRACT).textValue()) - .withDescription(source.path(ENTITY_DESCRIPTION).path(DESCRIPTION).textValue()) - .withAlternativeTitles(mutateAlternativeTitles(source)) - .withPublicationDate(mutatePublicationDate(source)) - .withContributorsPreview(mutateContributorsPreview(source)) - .withContributorsCount( - source.path(ENTITY_DESCRIPTION).path(CONTRIBUTORS_COUNT).asInt()) - .withPublishingDetails(mutatePublishingDetails(source)) - .withOtherIdentifiers(mutateOtherIdentifiers(source)) - .withRecordMetadata(mutateRecordMetadata(source)) - .build(); - } - - @Nullable - private Map mutateAlternativeTitles(JsonNode source) throws IOException { - return source.path(ENTITY_DESCRIPTION).has(ALTERNATIVE_TITLES) - ? jsonNodeMapToMap(source.path(ENTITY_DESCRIPTION).path(ALTERNATIVE_TITLES)) - : null; - } - - private Map jsonNodeMapToMap(JsonNode source) throws IOException { - return objectMapper.convertValue(source, Map.class); - } - - private OtherIdentifiers mutateOtherIdentifiers(JsonNode source) throws IOException { - var issns = - Stream.of( - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(ONLINE_ISSN) - .textValue(), - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(PRINT_ISSN) - .textValue(), - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(SERIES) - .path(ONLINE_ISSN) - .textValue(), - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(SERIES) - .path(PRINT_ISSN) - .textValue()) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - var isbnsInSourceNode = - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(ISBN_LIST); - - List isbnsInManifestations = new ArrayList<>(); - var manifestations = - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_INSTANCE) - .path(MANIFESTATIONS); - - if (!manifestations.isMissingNode() && manifestations.isArray()) { - manifestations - .iterator() - .forEachRemaining( - manifest -> { - if (!manifest.get(ISBN_LIST).isMissingNode()) { - List isbns = - (List) - attempt( - () -> - objectMapper - .readerForListOf( - String - .class) - .readValue( - manifest - .get( - ISBN_LIST))) - .orElseThrow(); - isbnsInManifestations.addAll(isbns); - } - }); - } - - List isbnsInSource = - isbnsInSourceNode.isMissingNode() - ? Collections.emptyList() - : objectMapper.readerForListOf(String.class).readValue(isbnsInSourceNode); - - var isbns = Stream.concat(isbnsInSource.stream(), isbnsInManifestations.stream()).toList(); - - var handleIdentifiers = new ArrayList(); - var cristinIdentifiers = new ArrayList(); - var scopusIdentifiers = new ArrayList(); - source.path(ADDITIONAL_IDENTIFIERS) - .iterator() - .forEachRemaining( - i -> { - switch (i.path(TYPE).textValue()) { - case HANDLE_IDENTIFIER: - handleIdentifiers.add(i.path(VALUE).textValue()); - break; - case SCOPUS_IDENTIFIER: - scopusIdentifiers.add(i.path(VALUE).textValue()); - break; - case CRISTIN_IDENTIFIER: - cristinIdentifiers.add(i.path(VALUE).textValue()); - break; - default: - break; - } - }); - - return new OtherIdentifiers( - new HashSet<>(scopusIdentifiers), - new HashSet<>(cristinIdentifiers), - new HashSet<>(handleIdentifiers), - new HashSet<>(issns), - new HashSet<>(isbns)); - } - - private RecordMetadata mutateRecordMetadata(JsonNode source) { - return new RecordMetadata( - source.path(STATUS).textValue(), source.path(CREATED_DATE).textValue(), - source.path(MODIFIED_DATE).textValue(), source.path(PUBLISHED_DATE).textValue()); - } - - private PublishingDetails mutatePublishingDetails(JsonNode source) { - return new PublishingDetails( - uriFromText( - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(ID) - .textValue()), - mutatePublicationContextType(source), - mutateSeries(source), - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(NAME) - .textValue(), - uriFromText(source.path(ENTITY_DESCRIPTION).path(REFERENCE).path(DOI).textValue()), - mutatePublisher(source)); - } - - private String mutatePublicationContextType(JsonNode source) { - return source.path(ENTITY_DESCRIPTION) + public static final String ID = "id"; + public static final String IDENTIFIER = "identifier"; + public static final String AFFILIATIONS = "affiliations"; + public static final String PUBLICATION_INSTANCE = "publicationInstance"; + public static final String TYPE = "type"; + public static final String ABSTRACT = "abstract"; + public static final String MAIN_TITLE = "mainTitle"; + public static final String DESCRIPTION = "description"; + public static final String ALTERNATIVE_TITLES = "alternativeTitles"; + public static final String PUBLICATION_DATE = "publicationDate"; + public static final String YEAR = "year"; + public static final String MONTH = "month"; + public static final String DAY = "day"; + public static final String CONTRIBUTORS_COUNT = "contributorsCount"; + public static final String ONLINE_ISSN = "onlineIssn"; + public static final String PRINT_ISSN = "printIssn"; + public static final String ISBN_LIST = "isbnList"; + public static final String ADDITIONAL_IDENTIFIERS = "additionalIdentifiers"; + public static final String HANDLE_IDENTIFIER = "HandleIdentifier"; + public static final String VALUE = "value"; + public static final String SCOPUS_IDENTIFIER = "ScopusIdentifier"; + public static final String CRISTIN_IDENTIFIER = "CristinIdentifier"; + public static final String STATUS = "status"; + public static final String CREATED_DATE = "createdDate"; + public static final String MODIFIED_DATE = "modifiedDate"; + public static final String PUBLISHED_DATE = "publishedDate"; + public static final String NAME = "name"; + public static final String DOI = "doi"; + public static final String CONTRIBUTORS_PREVIEW = "contributorsPreview"; + public static final String CORRESPONDING_AUTHOR = "correspondingAuthor"; + public static final String IDENTITY = "identity"; + public static final String ORCID_ID = "orcId"; + public static final String SEQUENCE = "sequence"; + public static final String ROLE = "role"; + public static final String MANIFESTATIONS = "manifestations"; + public static final String SCIENTIFIC_VALUE = "scientificValue"; + public static final String ENTITY_DESCRIPTION = "entityDescription"; + public static final String REFERENCE = "reference"; + public static final String PUBLICATION_CONTEXT = "publicationContext"; + public static final String SERIES = "series"; + public static final String PUBLISHER = "publisher"; + private final ObjectMapper objectMapper = dtoObjectMapper.copy(); + + public SimplifiedMutator() { + objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + + public static String path(String... path) { + return String.join(DOT, path); + } + + public static List getIncludedFields() { + return List.of( + ID, + IDENTIFIER, + STATUS, + CREATED_DATE, + MODIFIED_DATE, + PUBLISHED_DATE, + path(ENTITY_DESCRIPTION, REFERENCE, PUBLICATION_INSTANCE, TYPE), + path(ENTITY_DESCRIPTION, REFERENCE, PUBLICATION_INSTANCE, MANIFESTATIONS, ISBN_LIST), + path(ENTITY_DESCRIPTION, REFERENCE, DOI), + path(ENTITY_DESCRIPTION, REFERENCE, PUBLICATION_CONTEXT), // TODO: Narrow down further? + path(ENTITY_DESCRIPTION, DESCRIPTION), + path(ENTITY_DESCRIPTION, MAIN_TITLE), + path(ENTITY_DESCRIPTION, ABSTRACT), + path(ENTITY_DESCRIPTION, ALTERNATIVE_TITLES), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_COUNT), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, AFFILIATIONS, ID), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, AFFILIATIONS, TYPE), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, CORRESPONDING_AUTHOR), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, IDENTITY, ID), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, IDENTITY, NAME), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, IDENTITY, ORCID_ID), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, ROLE, TYPE), + path(ENTITY_DESCRIPTION, CONTRIBUTORS_PREVIEW, SEQUENCE), + path(ENTITY_DESCRIPTION, PUBLICATION_DATE), + path(ADDITIONAL_IDENTIFIERS, TYPE), + path(ADDITIONAL_IDENTIFIERS, VALUE)); + } + + @NotNull + private static PublicationDate mutatePublicationDate(JsonNode source) { + return new PublicationDate( + source.path(ENTITY_DESCRIPTION).path(PUBLICATION_DATE).path(YEAR).textValue(), + source.path(ENTITY_DESCRIPTION).path(PUBLICATION_DATE).path(MONTH).textValue(), + source.path(ENTITY_DESCRIPTION).path(PUBLICATION_DATE).path(DAY).textValue()); + } + + @Override + public JsonNode transform(JsonNode source) { + return (JsonNode) attempt(() -> objectMapper.valueToTree(transformToDto(source))).orElseThrow(); + } + + private ResourceSearchResponse transformToDto(JsonNode source) throws IOException { + return new Builder() + .withId(uriFromText(source.path(ID).textValue())) + .withIdentifier(source.path(IDENTIFIER).textValue()) + .withType( + source + .path(ENTITY_DESCRIPTION) .path(REFERENCE) - .path(PUBLICATION_CONTEXT) + .path(PUBLICATION_INSTANCE) .path(TYPE) - .textValue(); + .textValue()) + .withMainTitle(source.path(ENTITY_DESCRIPTION).path(MAIN_TITLE).textValue()) + .withMainLanguageAbstract(source.path(ENTITY_DESCRIPTION).path(ABSTRACT).textValue()) + .withDescription(source.path(ENTITY_DESCRIPTION).path(DESCRIPTION).textValue()) + .withAlternativeTitles(mutateAlternativeTitles(source)) + .withPublicationDate(mutatePublicationDate(source)) + .withContributorsPreview(mutateContributorsPreview(source)) + .withContributorsCount(source.path(ENTITY_DESCRIPTION).path(CONTRIBUTORS_COUNT).asInt()) + .withPublishingDetails(mutatePublishingDetails(source)) + .withOtherIdentifiers(mutateOtherIdentifiers(source)) + .withRecordMetadata(mutateRecordMetadata(source)) + .build(); + } + + @Nullable + private Map mutateAlternativeTitles(JsonNode source) { + return source.path(ENTITY_DESCRIPTION).has(ALTERNATIVE_TITLES) + ? jsonNodeMapToMap(source.path(ENTITY_DESCRIPTION).path(ALTERNATIVE_TITLES)) + : null; + } + + private Map jsonNodeMapToMap(JsonNode source) { + return objectMapper.convertValue(source, Map.class); + } + + private OtherIdentifiers mutateOtherIdentifiers(JsonNode source) throws IOException { + var issns = + Stream.of( + source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_CONTEXT) + .path(ONLINE_ISSN) + .textValue(), + source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_CONTEXT) + .path(PRINT_ISSN) + .textValue(), + source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_CONTEXT) + .path(SERIES) + .path(ONLINE_ISSN) + .textValue(), + source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_CONTEXT) + .path(SERIES) + .path(PRINT_ISSN) + .textValue()) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + var isbnsInSourceNode = + source.path(ENTITY_DESCRIPTION).path(REFERENCE).path(PUBLICATION_CONTEXT).path(ISBN_LIST); + + List isbnsInManifestations = new ArrayList<>(); + var manifestations = + source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_INSTANCE) + .path(MANIFESTATIONS); + + if (!manifestations.isMissingNode() && manifestations.isArray()) { + manifestations + .iterator() + .forEachRemaining( + manifest -> { + if (!manifest.get(ISBN_LIST).isMissingNode()) { + List isbns = + (List) + attempt( + () -> + objectMapper + .readerForListOf(String.class) + .readValue(manifest.get(ISBN_LIST))) + .orElseThrow(); + isbnsInManifestations.addAll(isbns); + } + }); } - private Series mutateSeries(JsonNode source) { - var series = - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(SERIES); - - if (series.isMissingNode()) { - return null; - } - return new Series( - uriFromText(series.path(ID).textValue()), - series.path(NAME).textValue(), - series.path(SCIENTIFIC_VALUE).textValue()); - } - - private Publisher mutatePublisher(JsonNode source) { - var publisher = - source.path(ENTITY_DESCRIPTION) - .path(REFERENCE) - .path(PUBLICATION_CONTEXT) - .path(PUBLISHER); - - if (publisher.isMissingNode()) { - return null; - } - return new Publisher( - uriFromText(publisher.path(ID).textValue()), - publisher.path(NAME).textValue(), - publisher.path(SCIENTIFIC_VALUE).textValue()); - } - - private List mutateContributorsPreview(JsonNode source) { - var contributors = new ArrayList(); - source.path(ENTITY_DESCRIPTION) - .path(CONTRIBUTORS_PREVIEW) - .iterator() - .forEachRemaining( - contributorNode -> { - var affiliationNode = contributorNode.path(AFFILIATIONS); - var affiliations = new HashSet(); - if (!affiliationNode.isMissingNode()) { - affiliationNode - .iterator() - .forEachRemaining( - aff -> - affiliations.add( - new Affiliation( - aff.path(ID).textValue(), - aff.path(TYPE) - .textValue()))); - } - - contributors.add( - new Contributor( - affiliations, - contributorNode.path(CORRESPONDING_AUTHOR).asBoolean(), - new Identity( - uriFromText( - contributorNode - .path(IDENTITY) - .path(ID) - .textValue()), - contributorNode - .path(IDENTITY) - .path(NAME) - .textValue(), - uriFromText( - contributorNode - .path(IDENTITY) - .path(ORCID_ID) - .textValue())), - contributorNode.path(ROLE).path(TYPE).textValue(), - contributorNode.path(SEQUENCE).asInt())); - }); - return contributors; + List isbnsInSource = + isbnsInSourceNode.isMissingNode() + ? Collections.emptyList() + : objectMapper.readerForListOf(String.class).readValue(isbnsInSourceNode); + + var isbns = Stream.concat(isbnsInSource.stream(), isbnsInManifestations.stream()).toList(); + + var handleIdentifiers = new ArrayList(); + var cristinIdentifiers = new ArrayList(); + var scopusIdentifiers = new ArrayList(); + source + .path(ADDITIONAL_IDENTIFIERS) + .iterator() + .forEachRemaining( + i -> { + switch (i.path(TYPE).textValue()) { + case HANDLE_IDENTIFIER: + handleIdentifiers.add(i.path(VALUE).textValue()); + break; + case SCOPUS_IDENTIFIER: + scopusIdentifiers.add(i.path(VALUE).textValue()); + break; + case CRISTIN_IDENTIFIER: + cristinIdentifiers.add(i.path(VALUE).textValue()); + break; + default: + break; + } + }); + + return new OtherIdentifiers( + new HashSet<>(scopusIdentifiers), + new HashSet<>(cristinIdentifiers), + new HashSet<>(handleIdentifiers), + new HashSet<>(issns), + new HashSet<>(isbns)); + } + + private RecordMetadata mutateRecordMetadata(JsonNode source) { + return new RecordMetadata( + source.path(STATUS).textValue(), source.path(CREATED_DATE).textValue(), + source.path(MODIFIED_DATE).textValue(), source.path(PUBLISHED_DATE).textValue()); + } + + private PublishingDetails mutatePublishingDetails(JsonNode source) { + return new PublishingDetails( + uriFromText( + source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_CONTEXT) + .path(ID) + .textValue()), + mutatePublicationContextType(source), + mutateSeries(source), + source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_CONTEXT) + .path(NAME) + .textValue(), + uriFromText(source.path(ENTITY_DESCRIPTION).path(REFERENCE).path(DOI).textValue()), + mutatePublisher(source)); + } + + private String mutatePublicationContextType(JsonNode source) { + return source + .path(ENTITY_DESCRIPTION) + .path(REFERENCE) + .path(PUBLICATION_CONTEXT) + .path(TYPE) + .textValue(); + } + + private Series mutateSeries(JsonNode source) { + var series = + source.path(ENTITY_DESCRIPTION).path(REFERENCE).path(PUBLICATION_CONTEXT).path(SERIES); + + if (series.isMissingNode()) { + return null; } - - private URI uriFromText(String text) { - return Objects.isNull(text) ? null : URI.create(text); + return new Series( + uriFromText(series.path(ID).textValue()), + series.path(NAME).textValue(), + series.path(SCIENTIFIC_VALUE).textValue()); + } + + private Publisher mutatePublisher(JsonNode source) { + var publisher = + source.path(ENTITY_DESCRIPTION).path(REFERENCE).path(PUBLICATION_CONTEXT).path(PUBLISHER); + + if (publisher.isMissingNode()) { + return null; } + return new Publisher( + uriFromText(publisher.path(ID).textValue()), + publisher.path(NAME).textValue(), + publisher.path(SCIENTIFIC_VALUE).textValue()); + } + + private List mutateContributorsPreview(JsonNode source) { + var contributors = new ArrayList(); + source + .path(ENTITY_DESCRIPTION) + .path(CONTRIBUTORS_PREVIEW) + .iterator() + .forEachRemaining( + contributorNode -> { + var affiliationNode = contributorNode.path(AFFILIATIONS); + var affiliations = new HashSet(); + if (!affiliationNode.isMissingNode()) { + affiliationNode + .iterator() + .forEachRemaining( + aff -> + affiliations.add( + new Affiliation( + aff.path(ID).textValue(), aff.path(TYPE).textValue()))); + } + + contributors.add( + new Contributor( + affiliations, + contributorNode.path(CORRESPONDING_AUTHOR).asBoolean(), + new Identity( + uriFromText(contributorNode.path(IDENTITY).path(ID).textValue()), + contributorNode.path(IDENTITY).path(NAME).textValue(), + uriFromText(contributorNode.path(IDENTITY).path(ORCID_ID).textValue())), + contributorNode.path(ROLE).path(TYPE).textValue(), + contributorNode.path(SEQUENCE).asInt())); + }); + return contributors; + } + + private URI uriFromText(String text) { + return Objects.isNull(text) ? null : URI.create(text); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/UserSettingsClient.java b/search-commons/src/main/java/no/unit/nva/search/resource/UserSettingsClient.java index ed4aca69e..9280a364b 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/UserSettingsClient.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/UserSettingsClient.java @@ -8,20 +8,10 @@ import static no.unit.nva.search.common.constant.Functions.readApiHost; import static no.unit.nva.search.resource.Constants.PERSON_PREFERENCES; import static no.unit.nva.search.resource.ResourceParameter.CONTRIBUTOR; - import static nva.commons.core.StringUtils.EMPTY_STRING; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.net.MediaType; - -import no.unit.nva.commons.json.JsonSerializable; -import no.unit.nva.search.common.OpenSearchClient; -import no.unit.nva.search.common.jwt.CachedJwtProvider; -import no.unit.nva.search.common.records.UserSettings; - -import nva.commons.core.JacocoGenerated; -import nva.commons.core.attempt.FunctionWithException; - import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; @@ -32,6 +22,12 @@ import java.util.List; import java.util.function.BinaryOperator; import java.util.stream.Stream; +import no.unit.nva.commons.json.JsonSerializable; +import no.unit.nva.search.common.OpenSearchClient; +import no.unit.nva.search.common.jwt.CachedJwtProvider; +import no.unit.nva.search.common.records.UserSettings; +import nva.commons.core.JacocoGenerated; +import nva.commons.core.attempt.FunctionWithException; /** * Client for searching user settings. diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/response/Contributor.java b/search-commons/src/main/java/no/unit/nva/search/resource/response/Contributor.java index 2bf1fd4d6..0e71c7417 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/response/Contributor.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/response/Contributor.java @@ -3,8 +3,8 @@ import java.util.Set; public record Contributor( - Set affiliations, - boolean correspondingAuthor, - Identity identity, - String role, - int sequence) {} + Set affiliations, + boolean correspondingAuthor, + Identity identity, + String role, + int sequence) {} diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/response/OtherIdentifiers.java b/search-commons/src/main/java/no/unit/nva/search/resource/response/OtherIdentifiers.java index 1251839bb..d7a4bf0d3 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/response/OtherIdentifiers.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/response/OtherIdentifiers.java @@ -3,8 +3,8 @@ import java.util.Set; public record OtherIdentifiers( - Set scopus, - Set cristin, - Set handle, - Set issn, - Set isbn) {} + Set scopus, + Set cristin, + Set handle, + Set issn, + Set isbn) {} diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/response/PublishingDetails.java b/search-commons/src/main/java/no/unit/nva/search/resource/response/PublishingDetails.java index e87ed680f..8ac6014d1 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/response/PublishingDetails.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/response/PublishingDetails.java @@ -3,4 +3,4 @@ import java.net.URI; public record PublishingDetails( - URI id, String type, Series series, String name, URI doi, Publisher publisher) {} + URI id, String type, Series series, String name, URI doi, Publisher publisher) {} diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/response/RecordMetadata.java b/search-commons/src/main/java/no/unit/nva/search/resource/response/RecordMetadata.java index 957637348..1691b1dfa 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/response/RecordMetadata.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/response/RecordMetadata.java @@ -1,4 +1,4 @@ package no.unit.nva.search.resource.response; public record RecordMetadata( - String status, String createdDate, String modifiedDate, String publishedDate) {} + String status, String createdDate, String modifiedDate, String publishedDate) {} diff --git a/search-commons/src/main/java/no/unit/nva/search/resource/response/ResourceSearchResponse.java b/search-commons/src/main/java/no/unit/nva/search/resource/response/ResourceSearchResponse.java index af78b994d..52e823456 100644 --- a/search-commons/src/main/java/no/unit/nva/search/resource/response/ResourceSearchResponse.java +++ b/search-commons/src/main/java/no/unit/nva/search/resource/response/ResourceSearchResponse.java @@ -7,122 +7,122 @@ import java.util.Map; public record ResourceSearchResponse( - URI id, - String identifier, - String type, - OtherIdentifiers otherIdentifiers, - RecordMetadata recordMetadata, - String mainTitle, - @JsonProperty("abstract") String mainLanguageAbstract, - String description, - Map alternativeTitles, - PublicationDate publicationDate, - List contributorsPreview, - int contributorsCount, - PublishingDetails publishingDetails) { - - public static final class Builder { - - private URI id; - private String identifier; - private String type; - private OtherIdentifiers otherIdentifiers; - private RecordMetadata recordMetadata; - private String mainTitle; - private String mainLanguageAbstract; - private String description; - private Map alternativeTitles; - private PublicationDate publicationDate; - private List contributorsPreview; - private int contributorsCount; - private PublishingDetails publishingDetails; - - public Builder() {} - - public static Builder aResourceSearchResponse() { - return new Builder(); - } - - public Builder withId(URI id) { - this.id = id; - return this; - } - - public Builder withIdentifier(String identifier) { - this.identifier = identifier; - return this; - } - - public Builder withType(String type) { - this.type = type; - return this; - } - - public Builder withOtherIdentifiers(OtherIdentifiers otherIdentifiers) { - this.otherIdentifiers = otherIdentifiers; - return this; - } - - public Builder withRecordMetadata(RecordMetadata recordMetadata) { - this.recordMetadata = recordMetadata; - return this; - } - - public Builder withMainTitle(String mainTitle) { - this.mainTitle = mainTitle; - return this; - } - - public Builder withMainLanguageAbstract(String mainLanguageAbstract) { - this.mainLanguageAbstract = mainLanguageAbstract; - return this; - } - - public Builder withDescription(String description) { - this.description = description; - return this; - } - - public Builder withAlternativeTitles(Map alternativeTitles) { - this.alternativeTitles = alternativeTitles; - return this; - } - - public Builder withPublicationDate(PublicationDate publicationDate) { - this.publicationDate = publicationDate; - return this; - } - - public Builder withContributorsPreview(List contributorsPreview) { - this.contributorsPreview = contributorsPreview; - return this; - } - - public Builder withContributorsCount(int contributorsCount) { - this.contributorsCount = contributorsCount; - return this; - } - - public Builder withPublishingDetails(PublishingDetails publishingDetails) { - this.publishingDetails = publishingDetails; - return this; - } - - public ResourceSearchResponse build() { - return new ResourceSearchResponse( - id, - identifier, - type, - otherIdentifiers, - recordMetadata, - mainTitle, - mainLanguageAbstract, - description, - alternativeTitles, - publicationDate, - contributorsPreview, - contributorsCount, - publishingDetails); - } + URI id, + String identifier, + String type, + OtherIdentifiers otherIdentifiers, + RecordMetadata recordMetadata, + String mainTitle, + @JsonProperty("abstract") String mainLanguageAbstract, + String description, + Map alternativeTitles, + PublicationDate publicationDate, + List contributorsPreview, + int contributorsCount, + PublishingDetails publishingDetails) { + + public static final class Builder { + + private URI id; + private String identifier; + private String type; + private OtherIdentifiers otherIdentifiers; + private RecordMetadata recordMetadata; + private String mainTitle; + private String mainLanguageAbstract; + private String description; + private Map alternativeTitles; + private PublicationDate publicationDate; + private List contributorsPreview; + private int contributorsCount; + private PublishingDetails publishingDetails; + + public Builder() {} + + public static Builder aResourceSearchResponse() { + return new Builder(); } + + public Builder withId(URI id) { + this.id = id; + return this; + } + + public Builder withIdentifier(String identifier) { + this.identifier = identifier; + return this; + } + + public Builder withType(String type) { + this.type = type; + return this; + } + + public Builder withOtherIdentifiers(OtherIdentifiers otherIdentifiers) { + this.otherIdentifiers = otherIdentifiers; + return this; + } + + public Builder withRecordMetadata(RecordMetadata recordMetadata) { + this.recordMetadata = recordMetadata; + return this; + } + + public Builder withMainTitle(String mainTitle) { + this.mainTitle = mainTitle; + return this; + } + + public Builder withMainLanguageAbstract(String mainLanguageAbstract) { + this.mainLanguageAbstract = mainLanguageAbstract; + return this; + } + + public Builder withDescription(String description) { + this.description = description; + return this; + } + + public Builder withAlternativeTitles(Map alternativeTitles) { + this.alternativeTitles = alternativeTitles; + return this; + } + + public Builder withPublicationDate(PublicationDate publicationDate) { + this.publicationDate = publicationDate; + return this; + } + + public Builder withContributorsPreview(List contributorsPreview) { + this.contributorsPreview = contributorsPreview; + return this; + } + + public Builder withContributorsCount(int contributorsCount) { + this.contributorsCount = contributorsCount; + return this; + } + + public Builder withPublishingDetails(PublishingDetails publishingDetails) { + this.publishingDetails = publishingDetails; + return this; + } + + public ResourceSearchResponse build() { + return new ResourceSearchResponse( + id, + identifier, + type, + otherIdentifiers, + recordMetadata, + mainTitle, + mainLanguageAbstract, + description, + alternativeTitles, + publicationDate, + contributorsPreview, + contributorsCount, + publishingDetails); + } + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollClient.java b/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollClient.java index 1b4dd2b52..4e56b5501 100644 --- a/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollClient.java +++ b/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollClient.java @@ -4,19 +4,16 @@ import static no.unit.nva.search.common.jwt.Tools.getCachedJwtProvider; import com.fasterxml.jackson.core.JsonProcessingException; - +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.function.BinaryOperator; import no.unit.nva.search.common.OpenSearchClient; import no.unit.nva.search.common.jwt.CachedJwtProvider; import no.unit.nva.search.common.records.SwsResponse; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.FunctionWithException; import nva.commons.secrets.SecretsReader; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; -import java.util.function.BinaryOperator; - /** * ScrollClient is a class that sends a request to the search index. * @@ -24,47 +21,47 @@ */ public class ScrollClient extends OpenSearchClient { - public ScrollClient(HttpClient client, CachedJwtProvider cachedJwtProvider) { - super(client, cachedJwtProvider); - } + public ScrollClient(HttpClient client, CachedJwtProvider cachedJwtProvider) { + super(client, cachedJwtProvider); + } - @JacocoGenerated - public static ScrollClient defaultClient() { - var cachedJwtProvider = getCachedJwtProvider(new SecretsReader()); - return new ScrollClient(HttpClient.newHttpClient(), cachedJwtProvider); - } + @JacocoGenerated + public static ScrollClient defaultClient() { + var cachedJwtProvider = getCachedJwtProvider(new SecretsReader()); + return new ScrollClient(HttpClient.newHttpClient(), cachedJwtProvider); + } - @Override - public SwsResponse doSearch(ScrollQuery query) { - queryBuilderStart = query.getStartTime(); - return query.assemble() - .map(this::createRequest) - .map(this::fetch) - .map(this::handleResponse) - .findFirst() - .orElseThrow() - .join(); - } + @Override + public SwsResponse doSearch(ScrollQuery query) { + queryBuilderStart = query.getStartTime(); + return query + .assemble() + .map(this::createRequest) + .map(this::fetch) + .map(this::handleResponse) + .findFirst() + .orElseThrow() + .join(); + } - @Override - protected SwsResponse jsonToResponse(HttpResponse response) - throws JsonProcessingException { - return singleLineObjectMapper.readValue(response.body(), SwsResponse.class); - } + @Override + protected SwsResponse jsonToResponse(HttpResponse response) + throws JsonProcessingException { + return singleLineObjectMapper.readValue(response.body(), SwsResponse.class); + } - @Override - @JacocoGenerated - protected BinaryOperator responseAccumulator() { - // not in use - return null; - } + @Override + @JacocoGenerated + protected BinaryOperator responseAccumulator() { + // not in use + return null; + } - @Override - protected FunctionWithException - logAndReturnResult() { - return result -> { - logger.info(buildLogInfo(result)); - return result; - }; - } + @Override + protected FunctionWithException logAndReturnResult() { + return result -> { + logger.info(buildLogInfo(result)); + return result; + }; + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollParameter.java b/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollParameter.java index b4e86505c..ce59eea5e 100644 --- a/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollParameter.java +++ b/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollParameter.java @@ -7,23 +7,19 @@ import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_IGNORE_CASE; import static no.unit.nva.search.common.constant.Patterns.PATTERN_IS_NONE_OR_ONE; import static no.unit.nva.search.common.enums.FieldOperator.NA; - import static nva.commons.core.StringUtils.EMPTY_STRING; +import java.util.Locale; +import java.util.StringJoiner; +import java.util.stream.Stream; import no.unit.nva.search.common.enums.FieldOperator; import no.unit.nva.search.common.enums.ParameterKey; import no.unit.nva.search.common.enums.ParameterKind; import no.unit.nva.search.common.enums.ValueEncoding; - import nva.commons.core.JacocoGenerated; - import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.text.CaseUtils; -import java.util.Locale; -import java.util.StringJoiner; -import java.util.stream.Stream; - /** * Enum for all the parameters that can be used to query the search index. This enum needs to * implement these parameters cristin @@ -32,76 +28,76 @@ * @author Sondre Vestad */ public enum ScrollParameter implements ParameterKey { - INVALID(ParameterKind.INVALID); - - private final ParameterKind paramkind; - - ScrollParameter(ParameterKind kind) { - this.paramkind = kind; - } - - @Override - public String asCamelCase() { - return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); - } - - @Override - public String asLowerCase() { - return this.name().toLowerCase(Locale.getDefault()); - } - - @Override - public Float fieldBoost() { - return 1F; - } - - @Override - public ParameterKind fieldType() { - return paramkind; - } - - @Override - public String fieldPattern() { - return PATTERN_IS_IGNORE_CASE + name().replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); - } - - @Override - public String valuePattern() { - return ParameterKey.getValuePattern(paramkind, null); - } - - @Override - public ValueEncoding valueEncoding() { - return ParameterKey.getEncoding(paramkind); - } - - @Override - public Stream searchFields(boolean... isKeyWord) { - return Stream.of(EMPTY_STRING); - } - - @Override - public FieldOperator searchOperator() { - return NA; - } - - @Override - public String errorMessage() { - return ParameterKey.getErrorMessage(paramkind); - } - - @Override - @JacocoGenerated - public ScrollParameter subQuery() { - throw new NotImplementedException(NOT_IMPLEMENTED_FOR + this.getClass().getName()); - } - - @Override - @JacocoGenerated - public String toString() { - return new StringJoiner(COLON, "Key[", "]") - .add(String.valueOf(ordinal())) - .add(asCamelCase()) - .toString(); - } + INVALID(ParameterKind.INVALID); + + private final ParameterKind paramkind; + + ScrollParameter(ParameterKind kind) { + this.paramkind = kind; + } + + @Override + public String asCamelCase() { + return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); + } + + @Override + public String asLowerCase() { + return this.name().toLowerCase(Locale.getDefault()); + } + + @Override + public Float fieldBoost() { + return 1F; + } + + @Override + public ParameterKind fieldType() { + return paramkind; + } + + @Override + public String fieldPattern() { + return PATTERN_IS_IGNORE_CASE + name().replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); + } + + @Override + public String valuePattern() { + return ParameterKey.getValuePattern(paramkind, null); + } + + @Override + public ValueEncoding valueEncoding() { + return ParameterKey.getEncoding(paramkind); + } + + @Override + public Stream searchFields(boolean... isKeyWord) { + return Stream.of(EMPTY_STRING); + } + + @Override + public FieldOperator searchOperator() { + return NA; + } + + @Override + public String errorMessage() { + return ParameterKey.getErrorMessage(paramkind); + } + + @Override + @JacocoGenerated + public ScrollParameter subQuery() { + throw new NotImplementedException(NOT_IMPLEMENTED_FOR + this.getClass().getName()); + } + + @Override + @JacocoGenerated + public String toString() { + return new StringJoiner(COLON, "Key[", "]") + .add(String.valueOf(ordinal())) + .add(asCamelCase()) + .toString(); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollQuery.java b/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollQuery.java index 794929d70..ff1a6c0e9 100644 --- a/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/scroll/ScrollQuery.java @@ -32,118 +32,111 @@ */ public final class ScrollQuery extends Query { - private static final int MAX_PAGES = 4; - private static final String SEARCH_SCROLL = "_search/scroll"; - private final String ttl; - private String scrollId; - private SwsResponse firstResponse; - - public ScrollQuery(String scrollId, String ttl) { - super(); - this.ttl = ttl; - this.scrollId = scrollId; - } - - ScrollQuery(String ttl, SwsResponse firstResponse) { - this(firstResponse._scroll_id(), ttl); - this.firstResponse = firstResponse; - } - - public static ScrollBuilder builder() { - return new ScrollBuilder(); - } - - @Override - protected URI openSearchUri() { - return fromUri(infrastructureApiUri).addChild(SEARCH_SCROLL).getUri(); - } - - private ScrollQuery withOpenSearchUri(final URI uri) { - if (nonNull(uri)) { - infrastructureApiUri = uri; - } - return this; + private static final int MAX_PAGES = 4; + private static final String SEARCH_SCROLL = "_search/scroll"; + private final String ttl; + private String scrollId; + private SwsResponse firstResponse; + + public ScrollQuery(String scrollId, String ttl) { + super(); + this.ttl = ttl; + this.scrollId = scrollId; + } + + ScrollQuery(String ttl, SwsResponse firstResponse) { + this(firstResponse._scroll_id(), ttl); + this.firstResponse = firstResponse; + } + + public static ScrollBuilder builder() { + return new ScrollBuilder(); + } + + @Override + protected URI openSearchUri() { + return fromUri(infrastructureApiUri).addChild(SEARCH_SCROLL).getUri(); + } + + private ScrollQuery withOpenSearchUri(final URI uri) { + if (nonNull(uri)) { + infrastructureApiUri = uri; } - - @Override - public Stream assemble() { - var scrollRequest = new SearchScrollRequest(scrollId).scroll(ttl); - return Stream.of( - new QueryContentWrapper( - scrollRequestToString(scrollRequest), this.openSearchUri())); - } - - @Override - public > HttpResponseFormatter doSearch( - OpenSearchClient queryClient) { - var response = buildSwsResponse(scrollFetch(firstResponse, 0, (ScrollClient) queryClient)); - return new HttpResponseFormatter<>(response, CSV_UTF_8); - } - - private Stream scrollFetch( - SwsResponse previousResponse, int level, ScrollClient scrollClient) { - - if (shouldStopRecursion(level + 1, previousResponse)) { - return previousResponse.getSearchHits().stream(); - } - scrollId = previousResponse._scroll_id(); - var currentResponse = scrollClient.doSearch(this); - - return Stream.concat( - previousResponse.getSearchHits().stream(), - scrollFetch(currentResponse, level + 1, scrollClient)); + return this; + } + + @Override + public Stream assemble() { + var scrollRequest = new SearchScrollRequest(scrollId).scroll(ttl); + return Stream.of( + new QueryContentWrapper(scrollRequestToString(scrollRequest), this.openSearchUri())); + } + + @Override + public > HttpResponseFormatter doSearch( + OpenSearchClient queryClient) { + var response = buildSwsResponse(scrollFetch(firstResponse, 0, (ScrollClient) queryClient)); + return new HttpResponseFormatter<>(response, CSV_UTF_8); + } + + private Stream scrollFetch( + SwsResponse previousResponse, int level, ScrollClient scrollClient) { + + if (shouldStopRecursion(level + 1, previousResponse)) { + return previousResponse.getSearchHits().stream(); } + scrollId = previousResponse._scroll_id(); + var currentResponse = scrollClient.doSearch(this); + + return Stream.concat( + previousResponse.getSearchHits().stream(), + scrollFetch(currentResponse, level + 1, scrollClient)); + } + + private SwsResponse buildSwsResponse(Stream results) { + var hits = + results + .map(hit -> new SwsResponse.HitsInfo.Hit(null, null, null, 0, hit, null, null)) + .toList(); + return new SwsResponse(0, false, null, new SwsResponse.HitsInfo(null, 0, hits), null, ""); + } + + private boolean shouldStopRecursion(Integer level, SwsResponse previousResponse) { + return Objects.isNull(previousResponse._scroll_id()) + || previousResponse.getSearchHits().isEmpty() + || level >= MAX_PAGES; + } + + private String scrollRequestToString(SearchScrollRequest request) { + return attempt( + () -> + toXContent(request, XContentType.JSON, ToXContent.EMPTY_PARAMS, true) + .utf8ToString()) + .orElseThrow(); + } + + public static class ScrollBuilder { + private SwsResponse firstResponse; + private URI openSearchUri; + private String ttl; - private SwsResponse buildSwsResponse(Stream results) { - var hits = - results.map( - hit -> - new SwsResponse.HitsInfo.Hit( - null, null, null, 0, hit, null, null)) - .toList(); - return new SwsResponse(0, false, null, new SwsResponse.HitsInfo(null, 0, hits), null, ""); + public ScrollQuery build() { + return new ScrollQuery(ttl, firstResponse).withOpenSearchUri(openSearchUri); } - private boolean shouldStopRecursion(Integer level, SwsResponse previousResponse) { - return Objects.isNull(previousResponse._scroll_id()) - || previousResponse.getSearchHits().isEmpty() - || level >= MAX_PAGES; + public ScrollBuilder withDockerHostUri(URI uri) { + this.openSearchUri = uri; + return this; } - private String scrollRequestToString(SearchScrollRequest request) { - return attempt( - () -> - toXContent( - request, - XContentType.JSON, - ToXContent.EMPTY_PARAMS, - true) - .utf8ToString()) - .orElseThrow(); + public ScrollBuilder withInitialResponse(SwsResponse initialResponse) { + this.firstResponse = initialResponse; + return this; } - public static class ScrollBuilder { - private SwsResponse firstResponse; - private URI openSearchUri; - private String ttl; - - public ScrollQuery build() { - return new ScrollQuery(ttl, firstResponse).withOpenSearchUri(openSearchUri); - } - - public ScrollBuilder withDockerHostUri(URI uri) { - this.openSearchUri = uri; - return this; - } - - public ScrollBuilder withInitialResponse(SwsResponse initialResponse) { - this.firstResponse = initialResponse; - return this; - } - - public ScrollBuilder withScrollTime(String scrollTtl) { - this.ttl = scrollTtl; - return this; - } + public ScrollBuilder withScrollTime(String scrollTtl) { + this.ttl = scrollTtl; + return this; } + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/Constants.java b/search-commons/src/main/java/no/unit/nva/search/ticket/Constants.java index d9d4fe003..a89d2fc32 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/Constants.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/Constants.java @@ -45,101 +45,101 @@ */ public final class Constants { - static final String ASSIGNEE_ASTERISK = "assignee.*"; - static final String BY_USER_PENDING = "byUserPending"; - static final String CANNOT_SEARCH_AS_BOTH_ASSIGNEE_AND_OWNER_AT_THE_SAME_TIME = - "Cannot search as both assignee and owner at the same time"; - static final String FILTER = "filter"; - static final String FILTER_BY_ASSIGNEE = "filterByAssignee"; - static final String FILTER_BY_ORGANIZATION = "filterByOrganization"; - static final String FILTER_BY_OWNER = "filterByOwner"; - static final String FILTER_BY_TICKET_TYPES = "filterByTicketTypes"; - static final String FILTER_BY_UN_PUBLISHED = "filterByUnPublished"; - static final String FILTER_BY_USER_AND_TICKET_TYPES = "filterByUserAndTicketTypes"; - static final String ORGANIZATION_IS_REQUIRED = "Organization is required"; - static final String USER_IS_NOT_ALLOWED_TO_SEARCH_FOR_TICKETS_NOT_OWNED_BY_THEMSELVES = - "User is not allowed to search for tickets not owned by themselves"; - static final String USER_IS_REQUIRED = "User is required"; - static final Map facetTicketsPaths = - Map.of( - BY_USER_PENDING, "/withAppliedFilter/byUserPending/type", - STATUS, "/withAppliedFilter/status", - TYPE, "/withAppliedFilter/type", - PUBLICATION_STATUS, "/withAppliedFilter/publicationStatus"); - static final String ASSIGNEE = "assignee"; - static final String ASSIGNEE_FIELDS = - multipleFields( - jsonPath(ASSIGNEE, FIRST_NAME, KEYWORD), - jsonPath(ASSIGNEE, LAST_NAME, KEYWORD), - jsonPath(ASSIGNEE, USERNAME, KEYWORD)); - static final String CUSTOMER_ID_KEYWORD = CUSTOMER_ID + DOT + KEYWORD; - static final String ID_KEYWORD = ID + DOT + KEYWORD; - static final String MESSAGE_FIELDS = - jsonPath(MESSAGES, "text", KEYWORD) + PIPE + jsonPath(MESSAGES, STATUS, KEYWORD); - static final String ORGANIZATION_IDENTIFIER_KEYWORD = - ORGANIZATION + DOT + IDENTIFIER + DOT + KEYWORD; - static final String ORGANIZATION_ID_KEYWORD = ORGANIZATION + DOT + ID_KEYWORD; - static final String ORGANIZATION_PATHS = ORGANIZATION + DOT + ASTERISK; - static final String OWNER_USERNAME = OWNER + DOT + USERNAME + DOT + KEYWORD; - static final String OWNER_FIELDS = - multipleFields( - OWNER_USERNAME, - jsonPath(OWNER, FIRST_NAME, KEYWORD), - jsonPath(OWNER, LAST_NAME, KEYWORD)); - static final String OWNER_KEYWORD = OWNER + DOT + KEYWORD; - static final String PUBLICATION_ID_OR_IDENTIFIER_KEYWORD = - jsonPath(PUBLICATION, ID, KEYWORD) + PIPE + jsonPath(PUBLICATION, IDENTIFIER, KEYWORD); - static final String PUBLICATION_INSTANCE_KEYWORD = - jsonPath(PUBLICATION, PUBLICATION_INSTANCE, TYPE, KEYWORD); - static final String PUBLICATION_MAIN_TITLE_KEYWORD = jsonPath(PUBLICATION, MAIN_TITLE, KEYWORD); - static final String PUBLICATION_MODIFIED_DATE = PUBLICATION + DOT + MODIFIED_DATE; - static final String PUBLICATION_OWNER_KEYWORD = PUBLICATION + DOT + OWNER_KEYWORD; - static final String STATUS_KEYWORD = STATUS + DOT + KEYWORD; - static final String PUBLICATION_STATUS_KEYWORD = PUBLICATION + DOT + STATUS_KEYWORD; - static final String TYPE_KEYWORD = TYPE + DOT + KEYWORD; - static final String UNHANDLED_KEY = "unhandled key -> "; - static final String VIEWED_BY_FIELDS = - multipleFields( - jsonPath(VIEWED_BY, USERNAME, KEYWORD), - jsonPath(VIEWED_BY, FIRST_NAME, KEYWORD), - jsonPath(VIEWED_BY, LAST_NAME, KEYWORD)); - private static final String FINALIZED_BY = "finalizedBy"; - static final String FINALIZED_BY_FIELDS = - multipleFields( - jsonPath(FINALIZED_BY, USERNAME, KEYWORD), - jsonPath(FINALIZED_BY, FIRST_NAME, KEYWORD), - jsonPath(FINALIZED_BY, LAST_NAME, KEYWORD)); + static final String ASSIGNEE_ASTERISK = "assignee.*"; + static final String BY_USER_PENDING = "byUserPending"; + static final String CANNOT_SEARCH_AS_BOTH_ASSIGNEE_AND_OWNER_AT_THE_SAME_TIME = + "Cannot search as both assignee and owner at the same time"; + static final String FILTER = "filter"; + static final String FILTER_BY_ASSIGNEE = "filterByAssignee"; + static final String FILTER_BY_ORGANIZATION = "filterByOrganization"; + static final String FILTER_BY_OWNER = "filterByOwner"; + static final String FILTER_BY_TICKET_TYPES = "filterByTicketTypes"; + static final String FILTER_BY_UN_PUBLISHED = "filterByUnPublished"; + static final String FILTER_BY_USER_AND_TICKET_TYPES = "filterByUserAndTicketTypes"; + static final String ORGANIZATION_IS_REQUIRED = "Organization is required"; + static final String USER_IS_NOT_ALLOWED_TO_SEARCH_FOR_TICKETS_NOT_OWNED_BY_THEMSELVES = + "User is not allowed to search for tickets not owned by themselves"; + static final String USER_IS_REQUIRED = "User is required"; + static final Map facetTicketsPaths = + Map.of( + BY_USER_PENDING, "/withAppliedFilter/byUserPending/type", + STATUS, "/withAppliedFilter/status", + TYPE, "/withAppliedFilter/type", + PUBLICATION_STATUS, "/withAppliedFilter/publicationStatus"); + static final String ASSIGNEE = "assignee"; + static final String ASSIGNEE_FIELDS = + multipleFields( + jsonPath(ASSIGNEE, FIRST_NAME, KEYWORD), + jsonPath(ASSIGNEE, LAST_NAME, KEYWORD), + jsonPath(ASSIGNEE, USERNAME, KEYWORD)); + static final String CUSTOMER_ID_KEYWORD = CUSTOMER_ID + DOT + KEYWORD; + static final String ID_KEYWORD = ID + DOT + KEYWORD; + static final String MESSAGE_FIELDS = + jsonPath(MESSAGES, "text", KEYWORD) + PIPE + jsonPath(MESSAGES, STATUS, KEYWORD); + static final String ORGANIZATION_IDENTIFIER_KEYWORD = + ORGANIZATION + DOT + IDENTIFIER + DOT + KEYWORD; + static final String ORGANIZATION_ID_KEYWORD = ORGANIZATION + DOT + ID_KEYWORD; + static final String ORGANIZATION_PATHS = ORGANIZATION + DOT + ASTERISK; + static final String OWNER_USERNAME = OWNER + DOT + USERNAME + DOT + KEYWORD; + static final String OWNER_FIELDS = + multipleFields( + OWNER_USERNAME, + jsonPath(OWNER, FIRST_NAME, KEYWORD), + jsonPath(OWNER, LAST_NAME, KEYWORD)); + static final String OWNER_KEYWORD = OWNER + DOT + KEYWORD; + static final String PUBLICATION_ID_OR_IDENTIFIER_KEYWORD = + jsonPath(PUBLICATION, ID, KEYWORD) + PIPE + jsonPath(PUBLICATION, IDENTIFIER, KEYWORD); + static final String PUBLICATION_INSTANCE_KEYWORD = + jsonPath(PUBLICATION, PUBLICATION_INSTANCE, TYPE, KEYWORD); + static final String PUBLICATION_MAIN_TITLE_KEYWORD = jsonPath(PUBLICATION, MAIN_TITLE, KEYWORD); + static final String PUBLICATION_MODIFIED_DATE = PUBLICATION + DOT + MODIFIED_DATE; + static final String PUBLICATION_OWNER_KEYWORD = PUBLICATION + DOT + OWNER_KEYWORD; + static final String STATUS_KEYWORD = STATUS + DOT + KEYWORD; + static final String PUBLICATION_STATUS_KEYWORD = PUBLICATION + DOT + STATUS_KEYWORD; + static final String TYPE_KEYWORD = TYPE + DOT + KEYWORD; + static final String UNHANDLED_KEY = "unhandled key -> "; + static final String VIEWED_BY_FIELDS = + multipleFields( + jsonPath(VIEWED_BY, USERNAME, KEYWORD), + jsonPath(VIEWED_BY, FIRST_NAME, KEYWORD), + jsonPath(VIEWED_BY, LAST_NAME, KEYWORD)); + private static final String FINALIZED_BY = "finalizedBy"; + static final String FINALIZED_BY_FIELDS = + multipleFields( + jsonPath(FINALIZED_BY, USERNAME, KEYWORD), + jsonPath(FINALIZED_BY, FIRST_NAME, KEYWORD), + jsonPath(FINALIZED_BY, LAST_NAME, KEYWORD)); - @JacocoGenerated - public Constants() {} + @JacocoGenerated + public Constants() {} - public static List getTicketsAggregations( - String username, String... deniedTypes) { - return List.of( - branchBuilder(STATUS, STATUS_KEYWORD), - branchBuilder(TYPE, TYPE_KEYWORD), - branchBuilder(PUBLICATION_STATUS, PUBLICATION_STATUS_KEYWORD), - notificationsAsCurator(username, Set.of(deniedTypes))); - } + public static List getTicketsAggregations( + String username, String... deniedTypes) { + return List.of( + branchBuilder(STATUS, STATUS_KEYWORD), + branchBuilder(TYPE, TYPE_KEYWORD), + branchBuilder(PUBLICATION_STATUS, PUBLICATION_STATUS_KEYWORD), + notificationsAsCurator(username, Set.of(deniedTypes))); + } - private static QueryBuilder filterByAssignee(String userName) { - return QueryBuilders.multiMatchQuery(userName, ASSIGNEE_ASTERISK) - .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) - .operator(Operator.AND) - .queryName(FILTER_BY_ASSIGNEE); - } + private static QueryBuilder filterByAssignee(String userName) { + return QueryBuilders.multiMatchQuery(userName, ASSIGNEE_ASTERISK) + .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) + .operator(Operator.AND) + .queryName(FILTER_BY_ASSIGNEE); + } - private static AggregationBuilder notificationsAsCurator( - String username, Set deniedTypes) { - var pending = TicketStatus.PENDING.toString(); - var queryFilter = - boolQuery() - .mustNot(new TermsQueryBuilder(TYPE_KEYWORD, deniedTypes)) - .must(new TermsQueryBuilder(STATUS_KEYWORD, pending)) - .must(filterByAssignee(username)) - .queryName(BY_USER_PENDING + FILTER); + private static AggregationBuilder notificationsAsCurator( + String username, Set deniedTypes) { + var pending = TicketStatus.PENDING.toString(); + var queryFilter = + boolQuery() + .mustNot(new TermsQueryBuilder(TYPE_KEYWORD, deniedTypes)) + .must(new TermsQueryBuilder(STATUS_KEYWORD, pending)) + .must(filterByAssignee(username)) + .queryName(BY_USER_PENDING + FILTER); - return filterBranchBuilder(BY_USER_PENDING, queryFilter) - .subAggregation(branchBuilder(TYPE, TYPE_KEYWORD)); - } + return filterBranchBuilder(BY_USER_PENDING, queryFilter) + .subAggregation(branchBuilder(TYPE, TYPE_KEYWORD)); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketAccessFilter.java b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketAccessFilter.java index 1ef9c2aba..fdf442b16 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketAccessFilter.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketAccessFilter.java @@ -1,5 +1,6 @@ package no.unit.nva.search.ticket; +import static java.util.Objects.isNull; import static no.unit.nva.search.ticket.Constants.CANNOT_SEARCH_AS_BOTH_ASSIGNEE_AND_OWNER_AT_THE_SAME_TIME; import static no.unit.nva.search.ticket.Constants.FILTER_BY_ORGANIZATION; import static no.unit.nva.search.ticket.Constants.FILTER_BY_OWNER; @@ -14,33 +15,26 @@ import static no.unit.nva.search.ticket.TicketParameter.ASSIGNEE; import static no.unit.nva.search.ticket.TicketParameter.OWNER; import static no.unit.nva.search.ticket.TicketParameter.STATISTICS; - import static nva.commons.apigateway.AccessRight.MANAGE_CUSTOMERS; import static nva.commons.apigateway.AccessRight.MANAGE_DOI; import static nva.commons.apigateway.AccessRight.MANAGE_PUBLISHING_REQUESTS; - import static org.opensearch.index.query.QueryBuilders.boolQuery; -import static java.util.Objects.isNull; - +import java.net.URI; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import no.unit.nva.search.common.records.FilterBuilder; - import nva.commons.apigateway.AccessRight; import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.UnauthorizedException; - import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.MultiMatchQueryBuilder; import org.opensearch.index.query.Operator; import org.opensearch.index.query.QueryBuilders; import org.opensearch.index.query.TermsQueryBuilder; -import java.net.URI; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - /** * TicketAccessFilter is a class that filters tickets based on access rights. * diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketClient.java b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketClient.java index 56ce01943..b5379b908 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketClient.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketClient.java @@ -5,19 +5,16 @@ import static no.unit.nva.search.common.records.SwsResponse.SwsResponseBuilder.swsResponseBuilder; import com.fasterxml.jackson.core.JsonProcessingException; - +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.function.BinaryOperator; import no.unit.nva.search.common.OpenSearchClient; import no.unit.nva.search.common.jwt.CachedJwtProvider; import no.unit.nva.search.common.records.SwsResponse; - import nva.commons.core.JacocoGenerated; import nva.commons.core.attempt.FunctionWithException; import nva.commons.secrets.SecretsReader; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; -import java.util.function.BinaryOperator; - /** * TicketClient is a class that sends a request to the search index. * diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketParameter.java b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketParameter.java index d9fc7b70f..53acbd1c4 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketParameter.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketParameter.java @@ -1,5 +1,6 @@ package no.unit.nva.search.ticket; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.ErrorMessages.NOT_IMPLEMENTED_FOR; import static no.unit.nva.constants.Words.CHAR_UNDERSCORE; import static no.unit.nva.constants.Words.COLON; @@ -50,27 +51,22 @@ import static no.unit.nva.search.ticket.Constants.TYPE_KEYWORD; import static no.unit.nva.search.ticket.Constants.VIEWED_BY_FIELDS; -import static java.util.Objects.nonNull; - +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.constants.Words; import no.unit.nva.search.common.enums.FieldOperator; import no.unit.nva.search.common.enums.ParameterKey; import no.unit.nva.search.common.enums.ParameterKind; import no.unit.nva.search.common.enums.ValueEncoding; - import nva.commons.core.JacocoGenerated; - import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.text.CaseUtils; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Enum for all the parameters that can be used to query the search index. This enum needs to * implement these parameters cristin @@ -80,192 +76,189 @@ */ @SuppressWarnings({"PMD.ExcessivePublicCount"}) public enum TicketParameter implements ParameterKey { - INVALID(ParameterKind.INVALID), - // Parameters used for filtering - ASSIGNEE(CUSTOM, ALL_OF, ASSIGNEE_FIELDS), - ASSIGNEE_NOT(ACROSS_FIELDS, NOT_ALL_OF, ASSIGNEE_FIELDS), - BY_USER_PENDING(FLAG), - CREATED_DATE(DATE, BETWEEN, Words.CREATED_DATE), - CUSTOMER_ID(FUZZY_KEYWORD, ANY_OF, CUSTOMER_ID_KEYWORD), - CUSTOMER_ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, CUSTOMER_ID_KEYWORD), - EXCLUDE_SUBUNITS( - FLAG, ANY_OF, ORGANIZATION_ID_KEYWORD + PIPE + ORGANIZATION_IDENTIFIER_KEYWORD), - FINALIZED_BY(ACROSS_FIELDS, ALL_OF, FINALIZED_BY_FIELDS), - FINALIZED_BY_NOT(ACROSS_FIELDS, NOT_ALL_OF, FINALIZED_BY_FIELDS), - ID(FUZZY_KEYWORD, ANY_OF, ID_KEYWORD), - ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, ID_KEYWORD), - MESSAGES(TEXT, ALL_OF, MESSAGE_FIELDS), - MESSAGES_NOT(TEXT, NOT_ALL_OF, MESSAGE_FIELDS), - MODIFIED_DATE(DATE, BETWEEN, Words.MODIFIED_DATE), - ORGANIZATION_ID(CUSTOM, ANY_OF, ORGANIZATION_PATHS, PATTERN_IS_ORGANIZATION, null, null), - ORGANIZATION_ID_NOT(CUSTOM, NOT_ANY_OF, ORGANIZATION_PATHS), - OWNER(ACROSS_FIELDS, ANY_OF, OWNER_FIELDS), - OWNER_NOT(ACROSS_FIELDS, NOT_ANY_OF, OWNER_FIELDS), - PUBLICATION_ID(FUZZY_KEYWORD, ANY_OF, PUBLICATION_ID_OR_IDENTIFIER_KEYWORD), - PUBLICATION_ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLICATION_ID_OR_IDENTIFIER_KEYWORD), - PUBLICATION_MODIFIED_DATE(DATE, BETWEEN, Constants.PUBLICATION_MODIFIED_DATE), - PUBLICATION_OWNER(FUZZY_KEYWORD, ANY_OF, PUBLICATION_OWNER_KEYWORD), - PUBLICATION_OWNER_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLICATION_OWNER_KEYWORD), - PUBLICATION_STATUS(KEYWORD, ANY_OF, PUBLICATION_STATUS_KEYWORD), - PUBLICATION_STATUS_NOT(KEYWORD, NOT_ANY_OF, PUBLICATION_STATUS_KEYWORD), - PUBLICATION_TITLE(TEXT, ALL_OF, PUBLICATION_MAIN_TITLE_KEYWORD, PHI), - PUBLICATION_TYPE(FUZZY_KEYWORD, ANY_OF, PUBLICATION_INSTANCE_KEYWORD), - PUBLICATION_TYPE_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLICATION_INSTANCE_KEYWORD), - STATISTICS(FLAG), - STATUS(CUSTOM, ANY_OF, STATUS_KEYWORD), - STATUS_NOT(CUSTOM, NOT_ANY_OF, STATUS_KEYWORD), - TYPE(KEYWORD, ANY_OF, TYPE_KEYWORD), - TYPE_NOT(KEYWORD, NOT_ANY_OF, TYPE_KEYWORD), - VIEWED_BY(ACROSS_FIELDS, ALL_OF, VIEWED_BY_FIELDS), - VIEWED_BY_NOT(ACROSS_FIELDS, NOT_ALL_OF, VIEWED_BY_FIELDS), + INVALID(ParameterKind.INVALID), + // Parameters used for filtering + ASSIGNEE(CUSTOM, ALL_OF, ASSIGNEE_FIELDS), + ASSIGNEE_NOT(ACROSS_FIELDS, NOT_ALL_OF, ASSIGNEE_FIELDS), + BY_USER_PENDING(FLAG), + CREATED_DATE(DATE, BETWEEN, Words.CREATED_DATE), + CUSTOMER_ID(FUZZY_KEYWORD, ANY_OF, CUSTOMER_ID_KEYWORD), + CUSTOMER_ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, CUSTOMER_ID_KEYWORD), + EXCLUDE_SUBUNITS(FLAG, ANY_OF, ORGANIZATION_ID_KEYWORD + PIPE + ORGANIZATION_IDENTIFIER_KEYWORD), + FINALIZED_BY(ACROSS_FIELDS, ALL_OF, FINALIZED_BY_FIELDS), + FINALIZED_BY_NOT(ACROSS_FIELDS, NOT_ALL_OF, FINALIZED_BY_FIELDS), + ID(FUZZY_KEYWORD, ANY_OF, ID_KEYWORD), + ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, ID_KEYWORD), + MESSAGES(TEXT, ALL_OF, MESSAGE_FIELDS), + MESSAGES_NOT(TEXT, NOT_ALL_OF, MESSAGE_FIELDS), + MODIFIED_DATE(DATE, BETWEEN, Words.MODIFIED_DATE), + ORGANIZATION_ID(CUSTOM, ANY_OF, ORGANIZATION_PATHS, PATTERN_IS_ORGANIZATION, null, null), + ORGANIZATION_ID_NOT(CUSTOM, NOT_ANY_OF, ORGANIZATION_PATHS), + OWNER(ACROSS_FIELDS, ANY_OF, OWNER_FIELDS), + OWNER_NOT(ACROSS_FIELDS, NOT_ANY_OF, OWNER_FIELDS), + PUBLICATION_ID(FUZZY_KEYWORD, ANY_OF, PUBLICATION_ID_OR_IDENTIFIER_KEYWORD), + PUBLICATION_ID_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLICATION_ID_OR_IDENTIFIER_KEYWORD), + PUBLICATION_MODIFIED_DATE(DATE, BETWEEN, Constants.PUBLICATION_MODIFIED_DATE), + PUBLICATION_OWNER(FUZZY_KEYWORD, ANY_OF, PUBLICATION_OWNER_KEYWORD), + PUBLICATION_OWNER_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLICATION_OWNER_KEYWORD), + PUBLICATION_STATUS(KEYWORD, ANY_OF, PUBLICATION_STATUS_KEYWORD), + PUBLICATION_STATUS_NOT(KEYWORD, NOT_ANY_OF, PUBLICATION_STATUS_KEYWORD), + PUBLICATION_TITLE(TEXT, ALL_OF, PUBLICATION_MAIN_TITLE_KEYWORD, PHI), + PUBLICATION_TYPE(FUZZY_KEYWORD, ANY_OF, PUBLICATION_INSTANCE_KEYWORD), + PUBLICATION_TYPE_NOT(FUZZY_KEYWORD, NOT_ANY_OF, PUBLICATION_INSTANCE_KEYWORD), + STATISTICS(FLAG), + STATUS(CUSTOM, ANY_OF, STATUS_KEYWORD), + STATUS_NOT(CUSTOM, NOT_ANY_OF, STATUS_KEYWORD), + TYPE(KEYWORD, ANY_OF, TYPE_KEYWORD), + TYPE_NOT(KEYWORD, NOT_ANY_OF, TYPE_KEYWORD), + VIEWED_BY(ACROSS_FIELDS, ALL_OF, VIEWED_BY_FIELDS), + VIEWED_BY_NOT(ACROSS_FIELDS, NOT_ALL_OF, VIEWED_BY_FIELDS), - // Query parameters passed to SWS/Opensearch - SEARCH_ALL(FREE_TEXT, ALL_OF, Q, PATTERN_IS_SEARCH_ALL_KEY, null, null), - NODES_SEARCHED(FLAG, null, null, PATTERN_IS_FIELDS_SEARCHED, null, null), - NODES_INCLUDED(FLAG), - NODES_EXCLUDED(FLAG), - // Pagination parameters - AGGREGATION(FLAG), - PAGE(NUMBER), - FROM(NUMBER, null, null, PATTERN_IS_FROM_KEY, null, null), - SIZE(NUMBER, null, null, PATTERN_IS_SIZE_KEY, null, null), - SEARCH_AFTER(FLAG), - SORT(ParameterKind.SORT_KEY, null, null, PATTERN_IS_SORT_KEY, null, null), - SORT_ORDER(FLAG, ALL_OF, null, PATTERN_IS_SORT_ORDER_KEY, PATTERN_IS_ASC_DESC_VALUE, null), - ; + // Query parameters passed to SWS/Opensearch + SEARCH_ALL(FREE_TEXT, ALL_OF, Q, PATTERN_IS_SEARCH_ALL_KEY, null, null), + NODES_SEARCHED(FLAG, null, null, PATTERN_IS_FIELDS_SEARCHED, null, null), + NODES_INCLUDED(FLAG), + NODES_EXCLUDED(FLAG), + // Pagination parameters + AGGREGATION(FLAG), + PAGE(NUMBER), + FROM(NUMBER, null, null, PATTERN_IS_FROM_KEY, null, null), + SIZE(NUMBER, null, null, PATTERN_IS_SIZE_KEY, null, null), + SEARCH_AFTER(FLAG), + SORT(ParameterKind.SORT_KEY, null, null, PATTERN_IS_SORT_KEY, null, null), + SORT_ORDER(FLAG, ALL_OF, null, PATTERN_IS_SORT_ORDER_KEY, PATTERN_IS_ASC_DESC_VALUE, null), + ; - public static final int IGNORE_PARAMETER_INDEX = 0; + public static final int IGNORE_PARAMETER_INDEX = 0; - public static final Set TICKET_PARAMETER_SET = - Arrays.stream(values()) - .filter(TicketParameter::isSearchField) - .sorted(ParameterKey::compareAscending) - .collect(Collectors.toCollection(LinkedHashSet::new)); - private final ValueEncoding encoding; - private final String keyPattern; - private final String validValuePattern; - private final String[] fieldsToSearch; - private final FieldOperator fieldOperator; - private final String errorMsg; - private final ParameterKind paramkind; - private final Float boost; + public static final Set TICKET_PARAMETER_SET = + Arrays.stream(values()) + .filter(TicketParameter::isSearchField) + .sorted(ParameterKey::compareAscending) + .collect(Collectors.toCollection(LinkedHashSet::new)); + private final ValueEncoding encoding; + private final String keyPattern; + private final String validValuePattern; + private final String[] fieldsToSearch; + private final FieldOperator fieldOperator; + private final String errorMsg; + private final ParameterKind paramkind; + private final Float boost; - TicketParameter(ParameterKind kind) { - this(kind, ALL_OF, null, null, null, null); - } + TicketParameter(ParameterKind kind) { + this(kind, ALL_OF, null, null, null, null); + } - TicketParameter(ParameterKind kind, FieldOperator operator, String fieldsToSearch) { - this(kind, operator, fieldsToSearch, null, null, null); - } + TicketParameter(ParameterKind kind, FieldOperator operator, String fieldsToSearch) { + this(kind, operator, fieldsToSearch, null, null, null); + } - TicketParameter( - ParameterKind kind, FieldOperator operator, String fieldsToSearch, Float boost) { - this(kind, operator, fieldsToSearch, null, null, boost); - } + TicketParameter(ParameterKind kind, FieldOperator operator, String fieldsToSearch, Float boost) { + this(kind, operator, fieldsToSearch, null, null, boost); + } - TicketParameter( - ParameterKind kind, - FieldOperator operator, - String fieldsToSearch, - String keyPattern, - String valuePattern, - Float boost) { + TicketParameter( + ParameterKind kind, + FieldOperator operator, + String fieldsToSearch, + String keyPattern, + String valuePattern, + Float boost) { - this.fieldOperator = nonNull(operator) ? operator : NA; - this.boost = nonNull(boost) ? boost : 1F; - this.fieldsToSearch = - nonNull(fieldsToSearch) ? fieldsToSearch.split("\\|") : new String[] {name()}; - this.validValuePattern = ParameterKey.getValuePattern(kind, valuePattern); - this.errorMsg = ParameterKey.getErrorMessage(kind); - this.encoding = ParameterKey.getEncoding(kind); - this.keyPattern = - nonNull(keyPattern) - ? keyPattern - : PATTERN_IS_IGNORE_CASE - + name().replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); - this.paramkind = kind; - } + this.fieldOperator = nonNull(operator) ? operator : NA; + this.boost = nonNull(boost) ? boost : 1F; + this.fieldsToSearch = + nonNull(fieldsToSearch) ? fieldsToSearch.split("\\|") : new String[] {name()}; + this.validValuePattern = ParameterKey.getValuePattern(kind, valuePattern); + this.errorMsg = ParameterKey.getErrorMessage(kind); + this.encoding = ParameterKey.getEncoding(kind); + this.keyPattern = + nonNull(keyPattern) + ? keyPattern + : PATTERN_IS_IGNORE_CASE + name().replace(UNDERSCORE, PATTERN_IS_NONE_OR_ONE); + this.paramkind = kind; + } - public static TicketParameter keyFromString(String paramName) { - var result = - Arrays.stream(values()) - .filter(TicketParameter::ignoreInvalidKey) - .filter(ParameterKey.equalTo(paramName)) - .collect(Collectors.toSet()); - return result.size() == 1 ? result.stream().findFirst().get() : INVALID; - } + public static TicketParameter keyFromString(String paramName) { + var result = + Arrays.stream(values()) + .filter(TicketParameter::ignoreInvalidKey) + .filter(ParameterKey.equalTo(paramName)) + .collect(Collectors.toSet()); + return result.size() == 1 ? result.stream().findFirst().get() : INVALID; + } - private static boolean ignoreInvalidKey(TicketParameter enumParameter) { - return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX; - } + private static boolean ignoreInvalidKey(TicketParameter enumParameter) { + return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX; + } - private static boolean isSearchField(TicketParameter enumParameter) { - return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX - && enumParameter.ordinal() < SEARCH_ALL.ordinal(); - } + private static boolean isSearchField(TicketParameter enumParameter) { + return enumParameter.ordinal() > IGNORE_PARAMETER_INDEX + && enumParameter.ordinal() < SEARCH_ALL.ordinal(); + } - @Override - public String asCamelCase() { - return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); - } + @Override + public String asCamelCase() { + return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); + } - @Override - public String asLowerCase() { - return this.name().toLowerCase(Locale.getDefault()); - } + @Override + public String asLowerCase() { + return this.name().toLowerCase(Locale.getDefault()); + } - @Override - public Float fieldBoost() { - return boost; - } + @Override + public Float fieldBoost() { + return boost; + } - @Override - public ParameterKind fieldType() { - return paramkind; - } + @Override + public ParameterKind fieldType() { + return paramkind; + } - @Override - public String fieldPattern() { - return keyPattern; - } + @Override + public String fieldPattern() { + return keyPattern; + } - @Override - public String valuePattern() { - return validValuePattern; - } + @Override + public String valuePattern() { + return validValuePattern; + } - @Override - public ValueEncoding valueEncoding() { - return encoding; - } + @Override + public ValueEncoding valueEncoding() { + return encoding; + } - @Override - public Stream searchFields(boolean... isKeyWord) { - return Arrays.stream(fieldsToSearch).map(ParameterKey.trimKeyword(fieldType(), isKeyWord)); - } + @Override + public Stream searchFields(boolean... isKeyWord) { + return Arrays.stream(fieldsToSearch).map(ParameterKey.trimKeyword(fieldType(), isKeyWord)); + } - @Override - public FieldOperator searchOperator() { - return fieldOperator; - } + @Override + public FieldOperator searchOperator() { + return fieldOperator; + } - @Override - public String errorMessage() { - return errorMsg; - } + @Override + public String errorMessage() { + return errorMsg; + } - @Override - @JacocoGenerated - public TicketParameter subQuery() { - throw new NotImplementedException(NOT_IMPLEMENTED_FOR + this.getClass().getName()); - } + @Override + @JacocoGenerated + public TicketParameter subQuery() { + throw new NotImplementedException(NOT_IMPLEMENTED_FOR + this.getClass().getName()); + } - @Override - @JacocoGenerated - public String toString() { - return new StringJoiner(COLON, "Key[", "]") - .add(String.valueOf(ordinal())) - .add(asCamelCase()) - .toString(); - } + @Override + @JacocoGenerated + public String toString() { + return new StringJoiner(COLON, "Key[", "]") + .add(String.valueOf(ordinal())) + .add(asCamelCase()) + .toString(); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSearchQuery.java b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSearchQuery.java index e9c2d9b08..8a4a40e8d 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSearchQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSearchQuery.java @@ -79,283 +79,277 @@ */ public final class TicketSearchQuery extends SearchQuery { - private final TicketAccessFilter accessFilter; - - private TicketSearchQuery() { - super(); - applyImpossibleWhiteListFilters(); - accessFilter = new TicketAccessFilter(this); - } - - public static TicketParameterValidator builder() { - return new TicketParameterValidator(); - } - - @Override - protected TicketParameter keyAggregation() { - return AGGREGATION; - } - - @Override - protected TicketParameter keyFields() { - return NODES_SEARCHED; + private final TicketAccessFilter accessFilter; + + private TicketSearchQuery() { + super(); + applyImpossibleWhiteListFilters(); + accessFilter = new TicketAccessFilter(this); + } + + public static TicketParameterValidator builder() { + return new TicketParameterValidator(); + } + + @Override + protected TicketParameter keyAggregation() { + return AGGREGATION; + } + + @Override + protected TicketParameter keyFields() { + return NODES_SEARCHED; + } + + @Override + protected TicketParameter keySearchAfter() { + return SEARCH_AFTER; + } + + @Override + protected TicketParameter toKey(String keyName) { + return TicketParameter.keyFromString(keyName); + } + + @Override + protected SortKey toSortKey(String sortName) { + return TicketSort.fromSortKey(sortName); + } + + @Override + protected AsType from() { + return parameters().get(FROM); + } + + @Override + protected AsType size() { + return parameters().get(SIZE); + } + + @Override + public AsType sort() { + return parameters().get(SORT); + } + + @Override + protected String[] exclude() { + return parameters().get(NODES_EXCLUDED).split(COMMA); + } + + @Override + protected String[] include() { + return parameters().get(NODES_INCLUDED).split(COMMA); + } + + @Override + public URI openSearchUri() { + return fromUri(infrastructureApiUri).addChild(TICKETS, SEARCH).getUri(); + } + + @Override + protected Map facetPaths() { + return facetTicketsPaths; + } + + @Override + protected List builderAggregations() { + var denySet = + Arrays.stream(TicketType.values()) + .filter(item -> !accessFilter.getAccessRightAsTicketTypes().contains(item)) + .map(Enum::toString) + .toArray(String[]::new); + return getTicketsAggregations(accessFilter.getCurrentUser(), denySet); + } + + @JacocoGenerated // default value shouldn't happen, (developer have forgotten to handle a key) + @Override + protected Stream> builderCustomQueryStream( + TicketParameter key) { + return switch (key) { + case ASSIGNEE -> builderStreamByAssignee(); + case ORGANIZATION_ID, ORGANIZATION_ID_NOT -> builderStreamByOrganization(key); + case STATUS, STATUS_NOT -> builderStreamByStatus(key); + default -> throw new IllegalArgumentException(UNHANDLED_KEY + key.name()); + }; + } + + private Stream> builderStreamByStatus(TicketParameter key) { + // we cannot query status New here, it is done together with assignee. + return hasAssigneeAndStatusNew() + ? Stream.empty() + : new KeywordQuery().buildQuery(key, parameters().get(key).toString()); + } + + public TicketAccessFilter withFilter() { + return accessFilter; + } + + private Stream> builderStreamByAssignee() { + var searchByUserName = // override assignee if is used + parameters().isPresent(BY_USER_PENDING) + ? accessFilter.getCurrentUser() + : parameters().get(ASSIGNEE).toString(); + + var returnedQuery = + new BoolQueryBuilder().minimumShouldMatch(1).queryName("builderStreamByAssignee"); + + if (hasStatusNew()) { // we'll query assignee and status New here.... + returnedQuery.should(new TermQueryBuilder(STATUS_KEYWORD, NEW.toString())); } - - @Override - protected TicketParameter keySearchAfter() { - return SEARCH_AFTER; - } - - @Override - protected TicketParameter toKey(String keyName) { - return TicketParameter.keyFromString(keyName); + var assigneeWithStatus = new BoolQueryBuilder().must(assigneeQuery(searchByUserName)); + if (hasMoreThanOneStatus()) { + assigneeWithStatus.must(new TermsQueryBuilder(STATUS_KEYWORD, statusWithoutNew())); } - @Override - protected SortKey toSortKey(String sortName) { - return TicketSort.fromSortKey(sortName); - } - - @Override - protected AsType from() { - return parameters().get(FROM); + returnedQuery.should(assigneeWithStatus); + + return Functions.queryToEntry(ASSIGNEE, returnedQuery); + } + + private boolean hasMoreThanOneStatus() { + return parameters().get(STATUS).asSplitStream(COMMA).count() > 1; + } + + private String[] statusWithoutNew() { + return parameters() + .get(STATUS) + .asSplitStream(COMMA) + .filter(s -> !NEW.toString().equals(s)) + .toArray(String[]::new); + } + + private MultiMatchQueryBuilder assigneeQuery(String searchByUserName) { + return multiMatchQuery( + searchByUserName, ASSIGNEE.searchFields(KEYWORD_FALSE).toArray(String[]::new)) + .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) + .autoGenerateSynonymsPhraseQuery(false) + .fuzzyTranspositions(false) + .operator(Operator.AND); + } + + private Stream> builderStreamByOrganization( + TicketParameter key) { + if (parameters().get(EXCLUDE_SUBUNITS).asBoolean()) { + return new KeywordQuery() + .buildQuery(EXCLUDE_SUBUNITS, parameters().get(key).toString()) + .map(query -> Map.entry(key, query.getValue())); + } else { + return new AcrossFieldsQuery() + .buildQuery(key, parameters().get(key).toString()); } - - @Override - protected AsType size() { - return parameters().get(SIZE); + } + + private boolean hasStatusNew() { + return parameters().get(STATUS).contains(NEW); + } + + private boolean hasAssigneeAndStatusNew() { + return hasStatusNew() && parameters().isPresent(ASSIGNEE); + } + + /** + * Add a (default) filter to the query that will never match any document. + * + *

This whitelist the Query from any forgetful developer (me) + * + *

i.e.In order to return any results, withFilter* must be set + */ + private void applyImpossibleWhiteListFilters() { + var randomUri = URI.create("https://www.example.com/" + UUID.randomUUID()); + final var filterId = + new TermQueryBuilder(ORGANIZATION_ID_KEYWORD, randomUri) + .queryName(ORGANIZATION_ID.asCamelCase() + POST_FILTER); + filters().set(filterId); + } + + public static class TicketParameterValidator + extends ParameterValidator { + + TicketParameterValidator() { + super(new TicketSearchQuery()); } @Override - public AsType sort() { - return parameters().get(SORT); + protected void assignDefaultValues() { + requiredMissing() + .forEach( + key -> { + switch (key) { + case FROM -> setValue(key.name(), DEFAULT_OFFSET); + case SIZE -> setValue(key.name(), DEFAULT_VALUE_PER_PAGE); + case SORT -> setValue(key.name(), RELEVANCE_KEY_NAME); + case AGGREGATION -> setValue(key.name(), NONE); + default -> { + /* ignore and continue */ + } + } + }); } @Override - protected String[] exclude() { - return parameters().get(NODES_EXCLUDED).split(COMMA); - } + protected void applyRulesAfterValidation() { + // convert page to offset if offset is not set + if (query.parameters().isPresent(PAGE)) { + if (query.parameters().isPresent(FROM)) { + var page = query.parameters().get(PAGE).as().longValue(); + var perPage = query.parameters().get(SIZE).as().longValue(); + query.parameters().set(FROM, String.valueOf(page * perPage)); + } + query.parameters().remove(PAGE); + } + if (query.parameters().isPresent(BY_USER_PENDING)) { + query.parameters().set(STATUS, PENDING.toString()); - @Override - protected String[] include() { - return parameters().get(NODES_INCLUDED).split(COMMA); + if (!query.parameters().get(BY_USER_PENDING).asBoolean()) { + query.parameters().set(TYPE, query.parameters().get(BY_USER_PENDING).toString()); + } + if (!query.parameters().isPresent(ASSIGNEE)) { + // business rule: need to set assignee to search by user pending + // (value is ignored) + query.parameters().set(ASSIGNEE, "👁"); + } + } } @Override - public URI openSearchUri() { - return fromUri(infrastructureApiUri).addChild(TICKETS, SEARCH).getUri(); + protected Collection validKeys() { + return TICKET_PARAMETER_SET.stream().map(Enum::name).toList(); } @Override - protected Map facetPaths() { - return facetTicketsPaths; + protected void validateSortKeyName(String name) { + var nameSort = name.split(COLON_OR_SPACE); + if (nameSort.length == NAME_AND_SORT_LENGTH) { + SortOrder.fromString(nameSort[1]); + } else if (nameSort.length > NAME_AND_SORT_LENGTH) { + throw new IllegalArgumentException(TOO_MANY_ARGUMENTS + name); + } + if (TicketSort.fromSortKey(nameSort[0]) == TicketSort.INVALID) { + throw new IllegalArgumentException( + INVALID_VALUE_WITH_SORT.formatted(name, TicketSort.validSortKeys())); + } } @Override - protected List builderAggregations() { - var denySet = - Arrays.stream(TicketType.values()) - .filter(item -> !accessFilter.getAccessRightAsTicketTypes().contains(item)) - .map(Enum::toString) - .toArray(String[]::new); - return getTicketsAggregations(accessFilter.getCurrentUser(), denySet); + protected void setValue(String key, String value) { + var qpKey = TicketParameter.keyFromString(key); + var decodedValue = getDecodedValue(qpKey, value); + switch (qpKey) { + case INVALID -> invalidKeys.add(key); + case SEARCH_AFTER, FROM, SIZE, PAGE, AGGREGATION -> + query.parameters().set(qpKey, decodedValue); + case NODES_SEARCHED -> query.parameters().set(qpKey, ignoreInvalidFields(decodedValue)); + case SORT -> mergeToKey(SORT, trimSpace(decodedValue)); + case SORT_ORDER -> mergeToKey(SORT, decodedValue); + case TYPE -> mergeToKey(qpKey, toEnumStrings(TicketType::fromString, decodedValue)); + case STATUS -> mergeToKey(qpKey, toEnumStrings(TicketStatus::fromString, decodedValue)); + default -> mergeToKey(qpKey, decodedValue); + } } - @JacocoGenerated // default value shouldn't happen, (developer have forgotten to handle a key) @Override - protected Stream> builderCustomQueryStream( - TicketParameter key) { - return switch (key) { - case ASSIGNEE -> builderStreamByAssignee(); - case ORGANIZATION_ID, ORGANIZATION_ID_NOT -> builderStreamByOrganization(key); - case STATUS, STATUS_NOT -> builderStreamByStatus(key); - default -> throw new IllegalArgumentException(UNHANDLED_KEY + key.name()); - }; - } - - private Stream> builderStreamByStatus( - TicketParameter key) { - // we cannot query status New here, it is done together with assignee. - return hasAssigneeAndStatusNew() - ? Stream.empty() - : new KeywordQuery() - .buildQuery(key, parameters().get(key).toString()); - } - - public TicketAccessFilter withFilter() { - return accessFilter; - } - - private Stream> builderStreamByAssignee() { - var searchByUserName = // override assignee if is used - parameters().isPresent(BY_USER_PENDING) - ? accessFilter.getCurrentUser() - : parameters().get(ASSIGNEE).toString(); - - var returnedQuery = - new BoolQueryBuilder().minimumShouldMatch(1).queryName("builderStreamByAssignee"); - - if (hasStatusNew()) { // we'll query assignee and status New here.... - returnedQuery.should(new TermQueryBuilder(STATUS_KEYWORD, NEW.toString())); - } - var assigneeWithStatus = new BoolQueryBuilder().must(assigneeQuery(searchByUserName)); - if (hasMoreThanOneStatus()) { - assigneeWithStatus.must(new TermsQueryBuilder(STATUS_KEYWORD, statusWithoutNew())); - } - - returnedQuery.should(assigneeWithStatus); - - return Functions.queryToEntry(ASSIGNEE, returnedQuery); - } - - private boolean hasMoreThanOneStatus() { - return parameters().get(STATUS).asSplitStream(COMMA).count() > 1; - } - - private String[] statusWithoutNew() { - return parameters() - .get(STATUS) - .asSplitStream(COMMA) - .filter(s -> !NEW.toString().equals(s)) - .toArray(String[]::new); - } - - private MultiMatchQueryBuilder assigneeQuery(String searchByUserName) { - return multiMatchQuery( - searchByUserName, - ASSIGNEE.searchFields(KEYWORD_FALSE).toArray(String[]::new)) - .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) - .autoGenerateSynonymsPhraseQuery(false) - .fuzzyTranspositions(false) - .operator(Operator.AND); - } - - private Stream> builderStreamByOrganization( - TicketParameter key) { - if (parameters().get(EXCLUDE_SUBUNITS).asBoolean()) { - return new KeywordQuery() - .buildQuery(EXCLUDE_SUBUNITS, parameters().get(key).toString()) - .map(query -> Map.entry(key, query.getValue())); - } else { - return new AcrossFieldsQuery() - .buildQuery(key, parameters().get(key).toString()); - } - } - - private boolean hasStatusNew() { - return parameters().get(STATUS).contains(NEW); - } - - private boolean hasAssigneeAndStatusNew() { - return hasStatusNew() && parameters().isPresent(ASSIGNEE); - } - - /** - * Add a (default) filter to the query that will never match any document. - * - *

This whitelist the Query from any forgetful developer (me) - * - *

i.e.In order to return any results, withFilter* must be set - */ - private void applyImpossibleWhiteListFilters() { - var randomUri = URI.create("https://www.example.com/" + UUID.randomUUID()); - final var filterId = - new TermQueryBuilder(ORGANIZATION_ID_KEYWORD, randomUri) - .queryName(ORGANIZATION_ID.asCamelCase() + POST_FILTER); - filters().set(filterId); - } - - public static class TicketParameterValidator - extends ParameterValidator { - - TicketParameterValidator() { - super(new TicketSearchQuery()); - } - - @Override - protected void assignDefaultValues() { - requiredMissing() - .forEach( - key -> { - switch (key) { - case FROM -> setValue(key.name(), DEFAULT_OFFSET); - case SIZE -> setValue(key.name(), DEFAULT_VALUE_PER_PAGE); - case SORT -> setValue(key.name(), RELEVANCE_KEY_NAME); - case AGGREGATION -> setValue(key.name(), NONE); - default -> { - /* ignore and continue */ - } - } - }); - } - - @Override - protected void applyRulesAfterValidation() { - // convert page to offset if offset is not set - if (query.parameters().isPresent(PAGE)) { - if (query.parameters().isPresent(FROM)) { - var page = query.parameters().get(PAGE).as().longValue(); - var perPage = query.parameters().get(SIZE).as().longValue(); - query.parameters().set(FROM, String.valueOf(page * perPage)); - } - query.parameters().remove(PAGE); - } - if (query.parameters().isPresent(BY_USER_PENDING)) { - query.parameters().set(STATUS, PENDING.toString()); - - if (!query.parameters().get(BY_USER_PENDING).asBoolean()) { - query.parameters() - .set(TYPE, query.parameters().get(BY_USER_PENDING).toString()); - } - if (!query.parameters().isPresent(ASSIGNEE)) { - // business rule: need to set assignee to search by user pending - // (value is ignored) - query.parameters().set(ASSIGNEE, "👁"); - } - } - } - - @Override - protected Collection validKeys() { - return TICKET_PARAMETER_SET.stream().map(Enum::name).toList(); - } - - @Override - protected void validateSortKeyName(String name) { - var nameSort = name.split(COLON_OR_SPACE); - if (nameSort.length == NAME_AND_SORT_LENGTH) { - SortOrder.fromString(nameSort[1]); - } else if (nameSort.length > NAME_AND_SORT_LENGTH) { - throw new IllegalArgumentException(TOO_MANY_ARGUMENTS + name); - } - if (TicketSort.fromSortKey(nameSort[0]) == TicketSort.INVALID) { - throw new IllegalArgumentException( - INVALID_VALUE_WITH_SORT.formatted(name, TicketSort.validSortKeys())); - } - } - - @Override - protected void setValue(String key, String value) { - var qpKey = TicketParameter.keyFromString(key); - var decodedValue = getDecodedValue(qpKey, value); - switch (qpKey) { - case INVALID -> invalidKeys.add(key); - case SEARCH_AFTER, FROM, SIZE, PAGE, AGGREGATION -> - query.parameters().set(qpKey, decodedValue); - case NODES_SEARCHED -> - query.parameters().set(qpKey, ignoreInvalidFields(decodedValue)); - case SORT -> mergeToKey(SORT, trimSpace(decodedValue)); - case SORT_ORDER -> mergeToKey(SORT, decodedValue); - case TYPE -> mergeToKey(qpKey, toEnumStrings(TicketType::fromString, decodedValue)); - case STATUS -> - mergeToKey(qpKey, toEnumStrings(TicketStatus::fromString, decodedValue)); - default -> mergeToKey(qpKey, decodedValue); - } - } - - @Override - protected boolean isKeyValid(String keyName) { - return TicketParameter.keyFromString(keyName) != TicketParameter.INVALID; - } + protected boolean isKeyValid(String keyName) { + return TicketParameter.keyFromString(keyName) != TicketParameter.INVALID; } + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSort.java b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSort.java index 92a8ed827..1775a08ee 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSort.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketSort.java @@ -21,54 +21,52 @@ * @author Stig Norland */ public enum TicketSort implements SortKey { - INVALID(EMPTY_STRING), - RELEVANCE(Words.SCORE), - CREATED_DATE(Words.CREATED_DATE), - MODIFIED_DATE(Words.MODIFIED_DATE), - STATUS(STATUS_KEYWORD), - TYPE(TYPE_KEYWORD); + INVALID(EMPTY_STRING), + RELEVANCE(Words.SCORE), + CREATED_DATE(Words.CREATED_DATE), + MODIFIED_DATE(Words.MODIFIED_DATE), + STATUS(STATUS_KEYWORD), + TYPE(TYPE_KEYWORD); - private final String keyValidationRegEx; - private final String path; + private final String keyValidationRegEx; + private final String path; - TicketSort(String jsonPath) { - this.keyValidationRegEx = SortKey.getIgnoreCaseAndUnderscoreKeyExpression(this.name()); - this.path = jsonPath; - } + TicketSort(String jsonPath) { + this.keyValidationRegEx = SortKey.getIgnoreCaseAndUnderscoreKeyExpression(this.name()); + this.path = jsonPath; + } - public static TicketSort fromSortKey(String keyName) { - var result = - Arrays.stream(values()) - .filter(SortKey.equalTo(keyName)) - .collect(Collectors.toSet()); - return result.size() == 1 ? result.stream().findFirst().get() : INVALID; - } + public static TicketSort fromSortKey(String keyName) { + var result = + Arrays.stream(values()).filter(SortKey.equalTo(keyName)).collect(Collectors.toSet()); + return result.size() == 1 ? result.stream().findFirst().get() : INVALID; + } - public static Collection validSortKeys() { - return Arrays.stream(values()) - .sorted(SortKey::compareAscending) - .skip(1) // skip INVALID - .map(TicketSort::asLowerCase) - .toList(); - } + public static Collection validSortKeys() { + return Arrays.stream(values()) + .sorted(SortKey::compareAscending) + .skip(1) // skip INVALID + .map(TicketSort::asLowerCase) + .toList(); + } - @Override - public String asCamelCase() { - return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); - } + @Override + public String asCamelCase() { + return CaseUtils.toCamelCase(this.name(), false, CHAR_UNDERSCORE); + } - @Override - public String asLowerCase() { - return this.name().toLowerCase(Locale.getDefault()); - } + @Override + public String asLowerCase() { + return this.name().toLowerCase(Locale.getDefault()); + } - @Override - public String keyPattern() { - return keyValidationRegEx; - } + @Override + public String keyPattern() { + return keyValidationRegEx; + } - @Override - public Stream jsonPaths() { - return Arrays.stream(path.split(PATTERN_IS_PIPE)); - } + @Override + public Stream jsonPaths() { + return Arrays.stream(path.split(PATTERN_IS_PIPE)); + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketStatus.java b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketStatus.java index f8d9e6474..45be571ad 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketStatus.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketStatus.java @@ -13,32 +13,32 @@ * @author Stig Norland */ public enum TicketStatus { - NONE(EMPTY_STRING), - NEW("New"), - PENDING("Pending"), - COMPLETED("Completed"), - NOT_APPLICABLE("NotApplicable"), - CLOSED("Closed"); - - private final String code; - private final String keyValidationRegEx; - - TicketStatus(String name) { - this.code = name; - this.keyValidationRegEx = getIgnoreCaseAndUnderscoreKeyExpression(this.name()); - } - - public static TicketStatus fromString(String name) { - var result = Arrays.stream(values()).filter(equalTo(name)).collect(Collectors.toSet()); - return result.size() == 1 ? result.stream().findFirst().get() : NONE; - } - - private static Predicate equalTo(String name) { - return status -> name.matches(status.keyValidationRegEx); - } - - @Override - public String toString() { - return this.code; - } + NONE(EMPTY_STRING), + NEW("New"), + PENDING("Pending"), + COMPLETED("Completed"), + NOT_APPLICABLE("NotApplicable"), + CLOSED("Closed"); + + private final String code; + private final String keyValidationRegEx; + + TicketStatus(String name) { + this.code = name; + this.keyValidationRegEx = getIgnoreCaseAndUnderscoreKeyExpression(this.name()); + } + + public static TicketStatus fromString(String name) { + var result = Arrays.stream(values()).filter(equalTo(name)).collect(Collectors.toSet()); + return result.size() == 1 ? result.stream().findFirst().get() : NONE; + } + + private static Predicate equalTo(String name) { + return status -> name.matches(status.keyValidationRegEx); + } + + @Override + public String toString() { + return this.code; + } } diff --git a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketType.java b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketType.java index d2a9b7ced..f53d76e31 100644 --- a/search-commons/src/main/java/no/unit/nva/search/ticket/TicketType.java +++ b/search-commons/src/main/java/no/unit/nva/search/ticket/TicketType.java @@ -15,31 +15,31 @@ * @author Stig Norland */ public enum TicketType { - NONE("None"), - DOI_REQUEST("DoiRequest"), - GENERAL_SUPPORT_CASE("GeneralSupportCase"), - PUBLISHING_REQUEST("PublishingRequest"), - UNPUBLISH_REQUEST("UnpublishRequest"); - - private final String type; - private final String keyValidationRegEx; - - TicketType(String typeName) { - this.type = typeName; - this.keyValidationRegEx = getIgnoreCaseAndUnderscoreKeyExpression(this.name()); - } - - public static TicketType fromString(String name) { - var result = Arrays.stream(values()).filter(equalTo(name)).collect(Collectors.toSet()); - return result.size() == 1 ? result.stream().findFirst().get() : NONE; - } - - private static Predicate equalTo(String name) { - return type -> name.matches(type.keyValidationRegEx); - } - - @Override - public String toString() { - return this.type; - } + NONE("None"), + DOI_REQUEST("DoiRequest"), + GENERAL_SUPPORT_CASE("GeneralSupportCase"), + PUBLISHING_REQUEST("PublishingRequest"), + UNPUBLISH_REQUEST("UnpublishRequest"); + + private final String type; + private final String keyValidationRegEx; + + TicketType(String typeName) { + this.type = typeName; + this.keyValidationRegEx = getIgnoreCaseAndUnderscoreKeyExpression(this.name()); + } + + public static TicketType fromString(String name) { + var result = Arrays.stream(values()).filter(equalTo(name)).collect(Collectors.toSet()); + return result.size() == 1 ? result.stream().findFirst().get() : NONE; + } + + private static Predicate equalTo(String name) { + return type -> name.matches(type.keyValidationRegEx); + } + + @Override + public String toString() { + return this.type; + } } diff --git a/search-commons/src/test/java/no/unit/nva/common/Containers.java b/search-commons/src/test/java/no/unit/nva/common/Containers.java index c10f26c28..a8e465386 100644 --- a/search-commons/src/test/java/no/unit/nva/common/Containers.java +++ b/search-commons/src/test/java/no/unit/nva/common/Containers.java @@ -1,5 +1,6 @@ package no.unit.nva.common; +import static java.util.Objects.nonNull; import static no.unit.nva.common.TestConstants.DELAY_AFTER_INDEXING; import static no.unit.nva.common.TestConstants.OPEN_SEARCH_IMAGE; import static no.unit.nva.constants.Words.IDENTIFIER; @@ -7,22 +8,20 @@ import static no.unit.nva.constants.Words.RESOURCES; import static no.unit.nva.constants.Words.TICKETS; import static no.unit.nva.indexing.testutils.MockedJwtProvider.setupMockedCachedJwtProvider; - import static nva.commons.core.attempt.Try.attempt; import static nva.commons.core.ioutils.IoUtils.stringFromResources; -import static java.util.Objects.nonNull; - import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; - +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; import no.unit.nva.commons.json.JsonUtils; import no.unit.nva.identifiers.SortableIdentifier; import no.unit.nva.indexingclient.IndexingClient; import no.unit.nva.indexingclient.models.EventConsumptionAttributes; import no.unit.nva.indexingclient.models.IndexDocument; import no.unit.nva.indexingclient.models.RestHighLevelClientWrapper; - import org.apache.http.HttpHost; import org.opensearch.client.RestClient; import org.opensearch.testcontainers.OpensearchContainer; @@ -30,100 +29,95 @@ import org.slf4j.LoggerFactory; import org.testcontainers.junit.jupiter.Testcontainers; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Map; - @Testcontainers public class Containers { - public static final OpensearchContainer container = new OpensearchContainer(OPEN_SEARCH_IMAGE); - public static final String IMPORT_CANDIDATE_MAPPING_DEV_JSON = - "import_candidate_mappings_dev.json"; - private static final Logger logger = LoggerFactory.getLogger(Containers.class); - - private static final String RESOURCE_DATASOURCE_JSON = "resource_datasource.json"; - private static final String RESOURCE_MAPPING_DEV_JSON = "resource_mappings_dev.json"; - private static final String RESOURCE_SETTING_DEV_JSON = "resource_settings_dev.json"; - - private static final String TICKET_DATASOURCE_JSON = "ticket_datasource.json"; - private static final String TICKET_MAPPING_DEV_JSON = "ticket_mappings_dev.json"; - - private static final String IMPORT_CANDIDATE_DATASOURCE_JSON = - "import_candidate_datasource.json"; - public static IndexingClient indexingClient; - - public static void setup() throws IOException, InterruptedException { - container.withEnv("indices.query.bool.max_clause_count", "2048").start(); - - var restClientBuilder = RestClient.builder(HttpHost.create(container.getHttpHostAddress())); - var restHighLevelClientWrapper = new RestHighLevelClientWrapper(restClientBuilder); - var cachedJwtProvider = setupMockedCachedJwtProvider(); - indexingClient = new IndexingClient(restHighLevelClientWrapper, cachedJwtProvider); - - logger.info("creating indexes"); - - createIndex(TICKETS, TICKET_MAPPING_DEV_JSON, null); - createIndex(IMPORT_CANDIDATES_INDEX, IMPORT_CANDIDATE_MAPPING_DEV_JSON, null); - createIndex(RESOURCES, RESOURCE_MAPPING_DEV_JSON, RESOURCE_SETTING_DEV_JSON); - - logger.info("populating indexes"); - - populateIndex(TICKET_DATASOURCE_JSON, TICKETS); - populateIndex(IMPORT_CANDIDATE_DATASOURCE_JSON, IMPORT_CANDIDATES_INDEX); - populateIndex(RESOURCE_DATASOURCE_JSON, RESOURCES); - - logger.info("Waiting {} ms for indexing to complete", DELAY_AFTER_INDEXING); - Thread.sleep(DELAY_AFTER_INDEXING); - } - - public static void afterAll() throws Exception { - - logger.info("Stopping container"); - indexingClient.deleteIndex(RESOURCES); - indexingClient.deleteIndex(TICKETS); - indexingClient.deleteIndex(IMPORT_CANDIDATES_INDEX); - Thread.sleep(DELAY_AFTER_INDEXING); - container.stop(); + public static final OpensearchContainer container = new OpensearchContainer(OPEN_SEARCH_IMAGE); + public static final String IMPORT_CANDIDATE_MAPPING_DEV_JSON = + "import_candidate_mappings_dev.json"; + private static final Logger logger = LoggerFactory.getLogger(Containers.class); + + private static final String RESOURCE_DATASOURCE_JSON = "resource_datasource.json"; + private static final String RESOURCE_MAPPING_DEV_JSON = "resource_mappings_dev.json"; + private static final String RESOURCE_SETTING_DEV_JSON = "resource_settings_dev.json"; + + private static final String TICKET_DATASOURCE_JSON = "ticket_datasource.json"; + private static final String TICKET_MAPPING_DEV_JSON = "ticket_mappings_dev.json"; + + private static final String IMPORT_CANDIDATE_DATASOURCE_JSON = "import_candidate_datasource.json"; + public static IndexingClient indexingClient; + + public static void setup() throws IOException, InterruptedException { + container.withEnv("indices.query.bool.max_clause_count", "2048").start(); + + var restClientBuilder = RestClient.builder(HttpHost.create(container.getHttpHostAddress())); + var restHighLevelClientWrapper = new RestHighLevelClientWrapper(restClientBuilder); + var cachedJwtProvider = setupMockedCachedJwtProvider(); + indexingClient = new IndexingClient(restHighLevelClientWrapper, cachedJwtProvider); + + logger.info("creating indexes"); + + createIndex(TICKETS, TICKET_MAPPING_DEV_JSON, null); + createIndex(IMPORT_CANDIDATES_INDEX, IMPORT_CANDIDATE_MAPPING_DEV_JSON, null); + createIndex(RESOURCES, RESOURCE_MAPPING_DEV_JSON, RESOURCE_SETTING_DEV_JSON); + + logger.info("populating indexes"); + + populateIndex(TICKET_DATASOURCE_JSON, TICKETS); + populateIndex(IMPORT_CANDIDATE_DATASOURCE_JSON, IMPORT_CANDIDATES_INDEX); + populateIndex(RESOURCE_DATASOURCE_JSON, RESOURCES); + + logger.info("Waiting {} ms for indexing to complete", DELAY_AFTER_INDEXING); + Thread.sleep(DELAY_AFTER_INDEXING); + } + + public static void afterAll() throws Exception { + + logger.info("Stopping container"); + indexingClient.deleteIndex(RESOURCES); + indexingClient.deleteIndex(TICKETS); + indexingClient.deleteIndex(IMPORT_CANDIDATES_INDEX); + Thread.sleep(DELAY_AFTER_INDEXING); + container.stop(); + } + + public static Map loadMapFromResource(String resource) { + var mappingsJson = stringFromResources(Path.of(resource)); + var type = new TypeReference>() {}; + return attempt(() -> JsonUtils.dtoObjectMapper.readValue(mappingsJson, type)).orElseThrow(); + } + + private static void populateIndex(String resourcePath, String indexName) { + var jsonFile = stringFromResources(Path.of(resourcePath)); + var jsonNodes = attempt(() -> JsonUtils.dtoObjectMapper.readTree(jsonFile)).orElseThrow(); + + jsonNodes.forEach(jsonNode -> addDocumentToIndex(indexName, jsonNode)); + } + + private static void addDocumentToIndex(String indexName, JsonNode node) { + try { + var identifier = + node.has(IDENTIFIER) + ? new SortableIdentifier(node.get(IDENTIFIER).asText()) + : SortableIdentifier.next(); + var attributes = new EventConsumptionAttributes(indexName, identifier); + indexingClient.addDocumentToIndex(new IndexDocument(attributes, node)); + } catch (Exception e) { + logger.error(e.getMessage(), e.getCause()); } - - public static Map loadMapFromResource(String resource) { - var mappingsJson = stringFromResources(Path.of(resource)); - var type = new TypeReference>() {}; - return attempt(() -> JsonUtils.dtoObjectMapper.readValue(mappingsJson, type)).orElseThrow(); - } - - private static void populateIndex(String resourcePath, String indexName) { - var jsonFile = stringFromResources(Path.of(resourcePath)); - var jsonNodes = attempt(() -> JsonUtils.dtoObjectMapper.readTree(jsonFile)).orElseThrow(); - - jsonNodes.forEach(jsonNode -> addDocumentToIndex(indexName, jsonNode)); - } - - private static void addDocumentToIndex(String indexName, JsonNode node) { - try { - var identifier = - node.has(IDENTIFIER) - ? new SortableIdentifier(node.get(IDENTIFIER).asText()) - : SortableIdentifier.next(); - var attributes = new EventConsumptionAttributes(indexName, identifier); - indexingClient.addDocumentToIndex(new IndexDocument(attributes, node)); - } catch (Exception e) { - logger.error(e.getMessage(), e.getCause()); - } - } - - private static void createIndex(String indexName, String mappingsPath, String settingsPath) - throws IOException { - var mappings = nonNull(mappingsPath) ? loadMapFromResource(mappingsPath) : null; - var settings = nonNull(settingsPath) ? loadMapFromResource(settingsPath) : null; - - if (nonNull(mappings) && nonNull(settings)) { - indexingClient.createIndex(indexName, mappings, settings); - } else if (nonNull(mappings)) { - indexingClient.createIndex(indexName, mappings); - } else { - indexingClient.createIndex(indexName); - } + } + + private static void createIndex(String indexName, String mappingsPath, String settingsPath) + throws IOException { + var mappings = nonNull(mappingsPath) ? loadMapFromResource(mappingsPath) : null; + var settings = nonNull(settingsPath) ? loadMapFromResource(settingsPath) : null; + + if (nonNull(mappings) && nonNull(settings)) { + indexingClient.createIndex(indexName, mappings, settings); + } else if (nonNull(mappings)) { + indexingClient.createIndex(indexName, mappings); + } else { + indexingClient.createIndex(indexName); } + } } diff --git a/search-commons/src/test/java/no/unit/nva/common/EntrySetTools.java b/search-commons/src/test/java/no/unit/nva/common/EntrySetTools.java index 91beb9f37..4e9fa2cf0 100644 --- a/search-commons/src/test/java/no/unit/nva/common/EntrySetTools.java +++ b/search-commons/src/test/java/no/unit/nva/common/EntrySetTools.java @@ -1,56 +1,53 @@ package no.unit.nva.common; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.Words.AMPERSAND; import static no.unit.nva.constants.Words.EQUAL; - import static nva.commons.core.StringUtils.EMPTY_STRING; import static nva.commons.core.attempt.Try.attempt; -import static java.util.Objects.nonNull; - -import nva.commons.core.JacocoGenerated; - import java.net.URI; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map.Entry; +import nva.commons.core.JacocoGenerated; public final class EntrySetTools { - @JacocoGenerated - public EntrySetTools() {} - - public static Collection> queryToMapEntries(URI uri) { - return queryToMapEntries(uri.getQuery()); - } - - public static Collection> queryToMapEntries(String query) { - return nonNull(query) - ? Arrays.stream(query.split(AMPERSAND)) - .map(keyValue -> keyValue.split(EQUAL)) - .map(EntrySetTools::stringsToEntry) - .toList() - : Collections.emptyList(); - } - - public static Entry stringsToEntry(String... strings) { - return new Entry<>() { - @Override - public String getKey() { - return strings[0]; - } - - @Override - public String getValue() { - return attempt(() -> strings[1]).orElse((f) -> EMPTY_STRING); - } - - @Override - @JacocoGenerated - public String setValue(String value) { - return null; - } - }; - } + @JacocoGenerated + public EntrySetTools() {} + + public static Collection> queryToMapEntries(URI uri) { + return queryToMapEntries(uri.getQuery()); + } + + public static Collection> queryToMapEntries(String query) { + return nonNull(query) + ? Arrays.stream(query.split(AMPERSAND)) + .map(keyValue -> keyValue.split(EQUAL)) + .map(EntrySetTools::stringsToEntry) + .toList() + : Collections.emptyList(); + } + + public static Entry stringsToEntry(String... strings) { + return new Entry<>() { + @Override + public String getKey() { + return strings[0]; + } + + @Override + public String getValue() { + return attempt(() -> strings[1]).orElse((f) -> EMPTY_STRING); + } + + @Override + @JacocoGenerated + public String setValue(String value) { + return null; + } + }; + } } diff --git a/search-commons/src/test/java/no/unit/nva/common/InjectedTestSettings.java b/search-commons/src/test/java/no/unit/nva/common/InjectedTestSettings.java index 98aaec974..2849acea7 100644 --- a/search-commons/src/test/java/no/unit/nva/common/InjectedTestSettings.java +++ b/search-commons/src/test/java/no/unit/nva/common/InjectedTestSettings.java @@ -11,28 +11,28 @@ public class InjectedTestSettings implements TestExecutionListener { - private static final Logger logger = LoggerFactory.getLogger(InjectedTestSettings.class); + private static final Logger logger = LoggerFactory.getLogger(InjectedTestSettings.class); - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - TestExecutionListener.super.testPlanExecutionStarted(testPlan); - try { - Configurator.setAllLevels("", Level.WARN); - logger.info("Setting up Opensearch server"); - Containers.setup(); - } catch (InterruptedException | IOException e) { - logger.error(e.getMessage()); - } + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + TestExecutionListener.super.testPlanExecutionStarted(testPlan); + try { + Configurator.setAllLevels("", Level.WARN); + logger.info("Setting up Opensearch server"); + Containers.setup(); + } catch (InterruptedException | IOException e) { + logger.error(e.getMessage()); } + } - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - TestExecutionListener.super.testPlanExecutionFinished(testPlan); - try { - logger.info("Closing Opensearch server"); - Containers.afterAll(); - } catch (Exception e) { - logger.error(e.getMessage()); - } + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + TestExecutionListener.super.testPlanExecutionFinished(testPlan); + try { + logger.info("Closing Opensearch server"); + Containers.afterAll(); + } catch (Exception e) { + logger.error(e.getMessage()); } + } } diff --git a/search-commons/src/test/java/no/unit/nva/common/MockedHttpResponse.java b/search-commons/src/test/java/no/unit/nva/common/MockedHttpResponse.java index b71860791..6afb3863d 100644 --- a/search-commons/src/test/java/no/unit/nva/common/MockedHttpResponse.java +++ b/search-commons/src/test/java/no/unit/nva/common/MockedHttpResponse.java @@ -1,12 +1,10 @@ package no.unit.nva.common; +import static java.util.Objects.nonNull; import static no.unit.nva.constants.Words.CONTENT_TYPE; import static no.unit.nva.testutils.TestHeaders.APPLICATION_JSON; - import static nva.commons.core.ioutils.IoUtils.stringFromResources; -import static java.util.Objects.nonNull; - import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpHeaders; @@ -17,7 +15,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; - import javax.net.ssl.SSLSession; public class MockedHttpResponse { diff --git a/search-commons/src/test/java/no/unit/nva/common/TestConstants.java b/search-commons/src/test/java/no/unit/nva/common/TestConstants.java index fb0d7d71e..8231867f0 100644 --- a/search-commons/src/test/java/no/unit/nva/common/TestConstants.java +++ b/search-commons/src/test/java/no/unit/nva/common/TestConstants.java @@ -4,9 +4,9 @@ public class TestConstants { - public static final String OPEN_SEARCH_IMAGE = "opensearchproject/opensearch:2.11.0"; - public static final long DELAY_AFTER_INDEXING = 1000L; + public static final String OPEN_SEARCH_IMAGE = "opensearchproject/opensearch:2.11.0"; + public static final long DELAY_AFTER_INDEXING = 1000L; - @JacocoGenerated - public TestConstants() {} + @JacocoGenerated + public TestConstants() {} } diff --git a/search-commons/src/test/java/no/unit/nva/indexingclient/CachedJwtTest.java b/search-commons/src/test/java/no/unit/nva/indexingclient/CachedJwtTest.java index 49916412f..75c7f47ba 100644 --- a/search-commons/src/test/java/no/unit/nva/indexingclient/CachedJwtTest.java +++ b/search-commons/src/test/java/no/unit/nva/indexingclient/CachedJwtTest.java @@ -6,17 +6,14 @@ import static org.mockito.Mockito.when; import com.auth0.jwt.interfaces.DecodedJWT; - -import no.unit.nva.search.common.jwt.CachedJwtProvider; -import no.unit.nva.search.common.jwt.CognitoAuthenticator; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Date; +import no.unit.nva.search.common.jwt.CachedJwtProvider; +import no.unit.nva.search.common.jwt.CognitoAuthenticator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CachedJwtTest { diff --git a/search-commons/src/test/java/no/unit/nva/indexingclient/CognitoAuthenticatorTest.java b/search-commons/src/test/java/no/unit/nva/indexingclient/CognitoAuthenticatorTest.java index 794771a4f..5a2842392 100644 --- a/search-commons/src/test/java/no/unit/nva/indexingclient/CognitoAuthenticatorTest.java +++ b/search-commons/src/test/java/no/unit/nva/indexingclient/CognitoAuthenticatorTest.java @@ -37,86 +37,83 @@ @SuppressWarnings({"unchecked"}) class CognitoAuthenticatorTest { - final HttpClient httpClient = mock(HttpClient.class); - final HttpResponse okResponse = mock(HttpResponse.class); - final HttpResponse invalidResponse = mock(HttpResponse.class); - final HttpResponse errorResponse = mock(HttpResponse.class); - private CognitoAuthenticator cognitoAuthenticator; - private CognitoCredentials credentials; - - @BeforeEach - public void setup() { - var authServer = "http://localhost"; - var clientId = randomString(); - var clientSecret = randomString(); - credentials = - new CognitoCredentials(() -> clientId, () -> clientSecret, URI.create(authServer)); - cognitoAuthenticator = new CognitoAuthenticator(httpClient, credentials); - - when(okResponse.statusCode()).thenReturn(HTTP_OK); - when(okResponse.body()).thenReturn("{\"access_token\": \"" + TEST_TOKEN + "\"}"); - - when(invalidResponse.statusCode()).thenReturn(HTTP_OK); - when(invalidResponse.body()).thenReturn("{}"); - - when(errorResponse.statusCode()).thenReturn(HTTP_FORBIDDEN); - when(errorResponse.body()).thenReturn("{}"); - } - - @Test - void shouldReturnJwtTokenFromHttpRequestToCognito() throws IOException, InterruptedException { - when(httpClient.send(any(), any())).thenReturn(okResponse); - - var jwt = cognitoAuthenticator.fetchBearerToken(); - assertThat(jwt.getToken(), is(TEST_TOKEN)); - } - - @Test - void shouldReturnDecodedJwtWithClaims() throws IOException, InterruptedException { - when(httpClient.send(any(), any())).thenReturn(okResponse); - - var jwt = cognitoAuthenticator.fetchBearerToken(); - assertThat(jwt.getClaim("scope").asString(), is(TEST_SCOPE)); - } - - @Test - void shouldReturnDecodedJwtWhenSendingBasicAuthentication() - throws IOException, InterruptedException { - var uri = URI.create(credentials.getCognitoOAuthServerUri().toString() + "/oauth2/token"); - var usernamePassword = - credentials.getCognitoAppClientId() + ":" + credentials.getCognitoAppClientSecret(); - var encodedAuth = - Base64.getEncoder() - .encodeToString(usernamePassword.getBytes(StandardCharsets.UTF_8)); - - var expectedRequest = - HttpRequest.newBuilder() - .uri(uri) - .setHeader(AUTHORIZATION, "Basic " + encodedAuth) - .setHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .POST(BodyPublishers.noBody()) - .build(); - - when(httpClient.send( - argThat(new HttpRequestMetadataMatcher(expectedRequest)), any())) - .thenReturn(okResponse); - - var jwt = cognitoAuthenticator.fetchBearerToken(); - assertNotNull(jwt); - } - - @Test - void shouldThrowWhenResponseIsNotStructuedLikeAToken() - throws IOException, InterruptedException { - when(httpClient.send(any(), any())).thenReturn(invalidResponse); - assertThrows(NoSuchElementException.class, () -> cognitoAuthenticator.fetchBearerToken()); - } - - @Test - void shouldThrowWhenResponseIsNot200Ok() throws IOException, InterruptedException { - when(httpClient.send(any(), any())).thenReturn(errorResponse); - var exception = - assertThrows(RuntimeException.class, () -> cognitoAuthenticator.fetchBearerToken()); - assertEquals(AUTHORIZATION_ERROR_MESSAGE, exception.getMessage()); - } + final HttpClient httpClient = mock(HttpClient.class); + final HttpResponse okResponse = mock(HttpResponse.class); + final HttpResponse invalidResponse = mock(HttpResponse.class); + final HttpResponse errorResponse = mock(HttpResponse.class); + private CognitoAuthenticator cognitoAuthenticator; + private CognitoCredentials credentials; + + @BeforeEach + public void setup() { + var authServer = "http://localhost"; + var clientId = randomString(); + var clientSecret = randomString(); + credentials = + new CognitoCredentials(() -> clientId, () -> clientSecret, URI.create(authServer)); + cognitoAuthenticator = new CognitoAuthenticator(httpClient, credentials); + + when(okResponse.statusCode()).thenReturn(HTTP_OK); + when(okResponse.body()).thenReturn("{\"access_token\": \"" + TEST_TOKEN + "\"}"); + + when(invalidResponse.statusCode()).thenReturn(HTTP_OK); + when(invalidResponse.body()).thenReturn("{}"); + + when(errorResponse.statusCode()).thenReturn(HTTP_FORBIDDEN); + when(errorResponse.body()).thenReturn("{}"); + } + + @Test + void shouldReturnJwtTokenFromHttpRequestToCognito() throws IOException, InterruptedException { + when(httpClient.send(any(), any())).thenReturn(okResponse); + + var jwt = cognitoAuthenticator.fetchBearerToken(); + assertThat(jwt.getToken(), is(TEST_TOKEN)); + } + + @Test + void shouldReturnDecodedJwtWithClaims() throws IOException, InterruptedException { + when(httpClient.send(any(), any())).thenReturn(okResponse); + + var jwt = cognitoAuthenticator.fetchBearerToken(); + assertThat(jwt.getClaim("scope").asString(), is(TEST_SCOPE)); + } + + @Test + void shouldReturnDecodedJwtWhenSendingBasicAuthentication() + throws IOException, InterruptedException { + var uri = URI.create(credentials.getCognitoOAuthServerUri().toString() + "/oauth2/token"); + var usernamePassword = + credentials.getCognitoAppClientId() + ":" + credentials.getCognitoAppClientSecret(); + var encodedAuth = + Base64.getEncoder().encodeToString(usernamePassword.getBytes(StandardCharsets.UTF_8)); + + var expectedRequest = + HttpRequest.newBuilder() + .uri(uri) + .setHeader(AUTHORIZATION, "Basic " + encodedAuth) + .setHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .POST(BodyPublishers.noBody()) + .build(); + + when(httpClient.send(argThat(new HttpRequestMetadataMatcher(expectedRequest)), any())) + .thenReturn(okResponse); + + var jwt = cognitoAuthenticator.fetchBearerToken(); + assertNotNull(jwt); + } + + @Test + void shouldThrowWhenResponseIsNotStructuedLikeAToken() throws IOException, InterruptedException { + when(httpClient.send(any(), any())).thenReturn(invalidResponse); + assertThrows(NoSuchElementException.class, () -> cognitoAuthenticator.fetchBearerToken()); + } + + @Test + void shouldThrowWhenResponseIsNot200Ok() throws IOException, InterruptedException { + when(httpClient.send(any(), any())).thenReturn(errorResponse); + var exception = + assertThrows(RuntimeException.class, () -> cognitoAuthenticator.fetchBearerToken()); + assertEquals(AUTHORIZATION_ERROR_MESSAGE, exception.getMessage()); + } } diff --git a/search-commons/src/test/java/no/unit/nva/indexingclient/IndexingClientTest.java b/search-commons/src/test/java/no/unit/nva/indexingclient/IndexingClientTest.java index c3357f4f2..b2bbc3554 100644 --- a/search-commons/src/test/java/no/unit/nva/indexingclient/IndexingClientTest.java +++ b/search-commons/src/test/java/no/unit/nva/indexingclient/IndexingClientTest.java @@ -6,9 +6,7 @@ import static no.unit.nva.indexingclient.IndexingClient.BULK_SIZE; import static no.unit.nva.testutils.RandomDataGenerator.randomJson; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; @@ -28,7 +26,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; - +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import no.unit.nva.identifiers.SortableIdentifier; import no.unit.nva.indexingclient.models.EventConsumptionAttributes; import no.unit.nva.indexingclient.models.IndexDocument; @@ -36,9 +38,7 @@ import no.unit.nva.indexingclient.models.RestHighLevelClientWrapper; import no.unit.nva.search.common.jwt.CachedJwtProvider; import no.unit.nva.search.common.records.UsernamePasswordWrapper; - import nva.commons.secrets.SecretsReader; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -53,12 +53,6 @@ import org.opensearch.client.RequestOptions; import org.opensearch.client.indices.CreateIndexRequest; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - class IndexingClientTest { public static final int diff --git a/search-commons/src/test/java/no/unit/nva/indexingclient/models/IndexDocumentTest.java b/search-commons/src/test/java/no/unit/nva/indexingclient/models/IndexDocumentTest.java index 97899f760..c148aa8ad 100644 --- a/search-commons/src/test/java/no/unit/nva/indexingclient/models/IndexDocumentTest.java +++ b/search-commons/src/test/java/no/unit/nva/indexingclient/models/IndexDocumentTest.java @@ -11,9 +11,7 @@ import static no.unit.nva.indexingclient.models.IndexDocument.TICKET; import static no.unit.nva.testutils.RandomDataGenerator.randomJson; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.attempt.Try.attempt; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; @@ -23,16 +21,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.util.stream.Stream; import no.unit.nva.identifiers.SortableIdentifier; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.stream.Stream; - class IndexDocumentTest { public static Stream invalidConsumptionAttributes() { diff --git a/search-commons/src/test/java/no/unit/nva/indexingclient/utils/HttpRequestMetadataMatcher.java b/search-commons/src/test/java/no/unit/nva/indexingclient/utils/HttpRequestMetadataMatcher.java index b4e3d5e2a..6386d2430 100644 --- a/search-commons/src/test/java/no/unit/nva/indexingclient/utils/HttpRequestMetadataMatcher.java +++ b/search-commons/src/test/java/no/unit/nva/indexingclient/utils/HttpRequestMetadataMatcher.java @@ -1,8 +1,7 @@ package no.unit.nva.indexingclient.utils; -import org.mockito.ArgumentMatcher; - import java.net.http.HttpRequest; +import org.mockito.ArgumentMatcher; /** * A ArgumentMatcher that will match whenever 2 HttpRequests have the same URI, Headers and Method. diff --git a/search-commons/src/test/java/no/unit/nva/indexingclient/utils/RequestOptionsHeaderMatcher.java b/search-commons/src/test/java/no/unit/nva/indexingclient/utils/RequestOptionsHeaderMatcher.java index bea923aef..7302281fb 100644 --- a/search-commons/src/test/java/no/unit/nva/indexingclient/utils/RequestOptionsHeaderMatcher.java +++ b/search-commons/src/test/java/no/unit/nva/indexingclient/utils/RequestOptionsHeaderMatcher.java @@ -5,17 +5,17 @@ public class RequestOptionsHeaderMatcher implements ArgumentMatcher { - private final RequestOptions sourceRequestOptions; + private final RequestOptions sourceRequestOptions; - public RequestOptionsHeaderMatcher(RequestOptions sourceRequestOptions) { - this.sourceRequestOptions = sourceRequestOptions; - } + public RequestOptionsHeaderMatcher(RequestOptions sourceRequestOptions) { + this.sourceRequestOptions = sourceRequestOptions; + } - @Override - public boolean matches(RequestOptions requestOptions) { - var sourceHeaders = sourceRequestOptions.getHeaders(); - var matchingHeaders = requestOptions.getHeaders(); + @Override + public boolean matches(RequestOptions requestOptions) { + var sourceHeaders = sourceRequestOptions.getHeaders(); + var matchingHeaders = requestOptions.getHeaders(); - return sourceHeaders.equals(matchingHeaders); - } + return sourceHeaders.equals(matchingHeaders); + } } diff --git a/search-commons/src/test/java/no/unit/nva/search/HardToHitFunctionsTest.java b/search-commons/src/test/java/no/unit/nva/search/HardToHitFunctionsTest.java index e4525850c..a47515def 100644 --- a/search-commons/src/test/java/no/unit/nva/search/HardToHitFunctionsTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/HardToHitFunctionsTest.java @@ -29,86 +29,86 @@ import java.util.stream.Stream; class HardToHitFunctionsTest { - private static final Logger logger = LoggerFactory.getLogger(HardToHitFunctionsTest.class); - private static final String TEST = "test"; - - @Test - void invalidRangeQueryMust() { - var queryRange = new RangeQuery(); - assertThrows( - IllegalArgumentException.class, - () -> queryRange.buildQuery(ResourceParameter.CONTEXT_TYPE, TEST)); - } - - @Test - void invalidResourceParameterFailsWhenInvokingBuildQuery() { - var queryRange = new PartOfQuery(); - var fakeResourceParameter = mock(ResourceParameter.class); - when(fakeResourceParameter.subQuery()).thenReturn(ResourceParameter.CREATED_BEFORE); - when(fakeResourceParameter.searchOperator()).thenReturn(BETWEEN); - assertThrows( - IllegalStateException.class, - () -> queryRange.buildQuery(fakeResourceParameter, "2021-01-01")); - } - - @Test - void invalidRangeQueryMustNot() { - var queryRange = new RangeQuery(); - assertThrows( - IllegalArgumentException.class, - () -> queryRange.buildQuery(ResourceParameter.CONTEXT_TYPE_NOT, TEST)); - } - - @Test - void checkTicketSort() { - assertNotNull(TicketSort.STATUS.asCamelCase()); - } - - @Test - void invalidSortQueryMust() { - assertNotNull(ImportCandidateSort.RELEVANCE.asLowerCase()); - } - - @Test - void printResourceParameter() { - printEnum(Arrays.stream(ResourceParameter.values())); - } - - @Test - void printTicketParameter() { - printEnum(Arrays.stream(TicketParameter.values())); - } - - @Test - void printImportCandidateParameter() { - printEnum(Arrays.stream(ImportCandidateParameter.values())); - } - - @Test - void printSortResourceParameter() { - printEnumSort(Arrays.stream(ResourceSort.values())); - } - - private void printEnum(Stream> parameterKeyStream) { - parameterKeyStream.forEach( - key -> - logger.info( - "|{}|{}|{}|{}|{}|", - key.asLowerCase(), - key.asCamelCase(), - key.fieldType().asCamelCase(), - key.searchOperator().asLowerCase(), - key.searchFields().collect(Collectors.joining(COMMA + SPACE)))); - } - - private void printEnumSort(Stream parameterSortKeyStream) { - parameterSortKeyStream.forEach( - key -> - logger.info( - "|{}|{}|{}|{}|", - key.asLowerCase(), - key.asCamelCase(), - key.keyPattern(), - key.jsonPaths().collect(Collectors.joining(COMMA + SPACE)))); - } + private static final Logger logger = LoggerFactory.getLogger(HardToHitFunctionsTest.class); + private static final String TEST = "test"; + + @Test + void invalidRangeQueryMust() { + var queryRange = new RangeQuery(); + assertThrows( + IllegalArgumentException.class, + () -> queryRange.buildQuery(ResourceParameter.CONTEXT_TYPE, TEST)); + } + + @Test + void invalidResourceParameterFailsWhenInvokingBuildQuery() { + var queryRange = new PartOfQuery(); + var fakeResourceParameter = mock(ResourceParameter.class); + when(fakeResourceParameter.subQuery()).thenReturn(ResourceParameter.CREATED_BEFORE); + when(fakeResourceParameter.searchOperator()).thenReturn(BETWEEN); + assertThrows( + IllegalStateException.class, + () -> queryRange.buildQuery(fakeResourceParameter, "2021-01-01")); + } + + @Test + void invalidRangeQueryMustNot() { + var queryRange = new RangeQuery(); + assertThrows( + IllegalArgumentException.class, + () -> queryRange.buildQuery(ResourceParameter.CONTEXT_TYPE_NOT, TEST)); + } + + @Test + void checkTicketSort() { + assertNotNull(TicketSort.STATUS.asCamelCase()); + } + + @Test + void invalidSortQueryMust() { + assertNotNull(ImportCandidateSort.RELEVANCE.asLowerCase()); + } + + @Test + void printResourceParameter() { + printEnum(Arrays.stream(ResourceParameter.values())); + } + + @Test + void printTicketParameter() { + printEnum(Arrays.stream(TicketParameter.values())); + } + + @Test + void printImportCandidateParameter() { + printEnum(Arrays.stream(ImportCandidateParameter.values())); + } + + @Test + void printSortResourceParameter() { + printEnumSort(Arrays.stream(ResourceSort.values())); + } + + private void printEnum(Stream> parameterKeyStream) { + parameterKeyStream.forEach( + key -> + logger.info( + "|{}|{}|{}|{}|{}|", + key.asLowerCase(), + key.asCamelCase(), + key.fieldType().asCamelCase(), + key.searchOperator().asLowerCase(), + key.searchFields().collect(Collectors.joining(COMMA + SPACE)))); + } + + private void printEnumSort(Stream parameterSortKeyStream) { + parameterSortKeyStream.forEach( + key -> + logger.info( + "|{}|{}|{}|{}|", + key.asLowerCase(), + key.asCamelCase(), + key.keyPattern(), + key.jsonPaths().collect(Collectors.joining(COMMA + SPACE)))); + } } diff --git a/search-commons/src/test/java/no/unit/nva/search/common/ContentTypeUtilsTest.java b/search-commons/src/test/java/no/unit/nva/search/common/ContentTypeUtilsTest.java index 4bef7d5c4..d3f2664de 100644 --- a/search-commons/src/test/java/no/unit/nva/search/common/ContentTypeUtilsTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/common/ContentTypeUtilsTest.java @@ -4,12 +4,10 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import java.util.Map; import nva.commons.apigateway.RequestInfo; - import org.junit.jupiter.api.Test; -import java.util.Map; - class ContentTypeUtilsTest { public static final String ACCEPT_HEADER_VALUE = "application/json; version=2023-05-10"; public static final String ACCEPT_HEADER_VALUE_WITH_QUOTES = diff --git a/search-commons/src/test/java/no/unit/nva/search/common/builder/PartOfQueryTest.java b/search-commons/src/test/java/no/unit/nva/search/common/builder/PartOfQueryTest.java index 912c3c52c..342abfeea 100644 --- a/search-commons/src/test/java/no/unit/nva/search/common/builder/PartOfQueryTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/common/builder/PartOfQueryTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertNotNull; import no.unit.nva.search.resource.ResourceParameter; - import org.junit.jupiter.api.Test; class PartOfQueryTest { diff --git a/search-commons/src/test/java/no/unit/nva/search/importcandidate/ImportCandidateClientTest.java b/search-commons/src/test/java/no/unit/nva/search/importcandidate/ImportCandidateClientTest.java index 0f3756d2d..0f1664655 100644 --- a/search-commons/src/test/java/no/unit/nva/search/importcandidate/ImportCandidateClientTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/importcandidate/ImportCandidateClientTest.java @@ -48,234 +48,227 @@ class ImportCandidateClientTest { - public static final String REQUEST_BASE_URL = "https://example.com/?"; - private static ImportCandidateClient importCandidateClient; + public static final String REQUEST_BASE_URL = "https://example.com/?"; + private static ImportCandidateClient importCandidateClient; - @BeforeAll - public static void setUp() { + @BeforeAll + public static void setUp() { - var cachedJwtProvider = setupMockedCachedJwtProvider(); - importCandidateClient = - new ImportCandidateClient(HttpClient.newHttpClient(), cachedJwtProvider); - } + var cachedJwtProvider = setupMockedCachedJwtProvider(); + importCandidateClient = + new ImportCandidateClient(HttpClient.newHttpClient(), cachedJwtProvider); + } - static Stream uriSortingProvider() { - return Stream.of( - URI.create( - REQUEST_BASE_URL + "sort=title&sortOrder=asc&sort=created_date&order=desc"), - URI.create( - REQUEST_BASE_URL - + "category=AcademicArticle&sort=title&sortOrder=asc&sort=created_date"), - URI.create( - REQUEST_BASE_URL - + "category=AcademicArticle&sort=title&sortOrder=asc&sort=created_date"), - URI.create( - REQUEST_BASE_URL - + "category=AcademicArticle&size=10&from=0&sort=created_date"), - URI.create(REQUEST_BASE_URL + "orderBy=INSTANCE_TYPE:asc,PUBLICATION_YEAR:desc"), - URI.create( - REQUEST_BASE_URL - + "orderBy=title:asc,CREATED_DATE:desc&searchAfter=1241234,23412"), - URI.create(REQUEST_BASE_URL + "orderBy=relevance,title:asc,CREATED_DATE:desc"), - URI.create( - REQUEST_BASE_URL - + "category=AcademicArticle&sort=relevance,TYPE+asc&sort=INSTANCE_TYPE+desc")); - } + static Stream uriSortingProvider() { + return Stream.of( + URI.create(REQUEST_BASE_URL + "sort=title&sortOrder=asc&sort=created_date&order=desc"), + URI.create( + REQUEST_BASE_URL + + "category=AcademicArticle&sort=title&sortOrder=asc&sort=created_date"), + URI.create( + REQUEST_BASE_URL + + "category=AcademicArticle&sort=title&sortOrder=asc&sort=created_date"), + URI.create(REQUEST_BASE_URL + "category=AcademicArticle&size=10&from=0&sort=created_date"), + URI.create(REQUEST_BASE_URL + "orderBy=INSTANCE_TYPE:asc,PUBLICATION_YEAR:desc"), + URI.create( + REQUEST_BASE_URL + "orderBy=title:asc,CREATED_DATE:desc&searchAfter=1241234,23412"), + URI.create(REQUEST_BASE_URL + "orderBy=relevance,title:asc,CREATED_DATE:desc"), + URI.create( + REQUEST_BASE_URL + + "category=AcademicArticle&sort=relevance,TYPE+asc&sort=INSTANCE_TYPE+desc")); + } - static Stream uriProvider() { - return Stream.of( - URI.create(REQUEST_BASE_URL + "size=8"), - URI.create(REQUEST_BASE_URL + "aggregation=ALL&size=8"), - URI.create(REQUEST_BASE_URL + "aggregation=importStatus&size=8"), - URI.create(REQUEST_BASE_URL + "category=AcademicArticle&size=5"), - URI.create(REQUEST_BASE_URL + "CONTRIBUTOR=Andrew+Morrison&size=1"), - URI.create(REQUEST_BASE_URL + "CONTRIBUTOR=Andrew+Morrison,Nina+Bjørnstad&size=1"), - URI.create(REQUEST_BASE_URL + "CONTRIBUTOR_NOT=George+Rigos&size=7"), - URI.create(REQUEST_BASE_URL + "files=hasPublicFiles&size=7"), - URI.create(REQUEST_BASE_URL + "IMPORT_STATUS=IMPORTED&size=4"), - URI.create(REQUEST_BASE_URL + "IMPORT_STATUS=1136326@20754.0.0.0&size=2"), - URI.create(REQUEST_BASE_URL + "IMPORT_STATUS=20754.0.0.0&size=4"), - URI.create( - REQUEST_BASE_URL - + "id=018bed744c78-f53e06f7-74da-4c91-969f-ec307a7e7816&size=1"), - URI.create(REQUEST_BASE_URL + "license=CC-BY&size=7"), - URI.create(REQUEST_BASE_URL + "MODIFIED_DATE=2023&size=8"), - URI.create(REQUEST_BASE_URL + "MODIFIED_DATE=2023-10&size=2"), - URI.create(REQUEST_BASE_URL + "MODIFIED_DATE=2023-05,&size=7"), - URI.create(REQUEST_BASE_URL + "PUBLICATION_YEAR_BEFORE=2023&size=5"), - URI.create( - REQUEST_BASE_URL - + "PublicationYearBefore=2024&publication_year_since=2023&size=3"), - URI.create(REQUEST_BASE_URL + "publicationYear=2022,2022&size=1"), - URI.create(REQUEST_BASE_URL + "publicationYear=2022&size=1"), - URI.create(REQUEST_BASE_URL + "publicationYear=,2022&size=5"), - URI.create(REQUEST_BASE_URL + "publicationYear=2022,&size=4"), - URI.create(REQUEST_BASE_URL + "publicationYear=2023&size=3"), - URI.create(REQUEST_BASE_URL + "publicationYear=2022,2023&size=4"), - URI.create(REQUEST_BASE_URL + "title=In+reply:+Why+big+data&size=1"), - URI.create(REQUEST_BASE_URL + "title=chronic+diseases&size=1"), - URI.create(REQUEST_BASE_URL + "title=antibacterial,Fishing&size=2"), - URI.create(REQUEST_BASE_URL + "query=antibacterial&fields=category,title&size=1"), - URI.create( - REQUEST_BASE_URL - + "query=antibacterial&fields=category,title,werstfg&ID_NOT=123&size=1"), - URI.create(REQUEST_BASE_URL + "query=European&fields=all&size=3"), - URI.create(REQUEST_BASE_URL + "CRISTIN_IDENTIFIER=3212342&size=1"), - URI.create(REQUEST_BASE_URL + "SCOPUS_IDENTIFIER=3212342&size=1")); - } + static Stream uriProvider() { + return Stream.of( + URI.create(REQUEST_BASE_URL + "size=8"), + URI.create(REQUEST_BASE_URL + "aggregation=ALL&size=8"), + URI.create(REQUEST_BASE_URL + "aggregation=importStatus&size=8"), + URI.create(REQUEST_BASE_URL + "category=AcademicArticle&size=5"), + URI.create(REQUEST_BASE_URL + "CONTRIBUTOR=Andrew+Morrison&size=1"), + URI.create(REQUEST_BASE_URL + "CONTRIBUTOR=Andrew+Morrison,Nina+Bjørnstad&size=1"), + URI.create(REQUEST_BASE_URL + "CONTRIBUTOR_NOT=George+Rigos&size=7"), + URI.create(REQUEST_BASE_URL + "files=hasPublicFiles&size=7"), + URI.create(REQUEST_BASE_URL + "IMPORT_STATUS=IMPORTED&size=4"), + URI.create(REQUEST_BASE_URL + "IMPORT_STATUS=1136326@20754.0.0.0&size=2"), + URI.create(REQUEST_BASE_URL + "IMPORT_STATUS=20754.0.0.0&size=4"), + URI.create( + REQUEST_BASE_URL + "id=018bed744c78-f53e06f7-74da-4c91-969f-ec307a7e7816&size=1"), + URI.create(REQUEST_BASE_URL + "license=CC-BY&size=7"), + URI.create(REQUEST_BASE_URL + "MODIFIED_DATE=2023&size=8"), + URI.create(REQUEST_BASE_URL + "MODIFIED_DATE=2023-10&size=2"), + URI.create(REQUEST_BASE_URL + "MODIFIED_DATE=2023-05,&size=7"), + URI.create(REQUEST_BASE_URL + "PUBLICATION_YEAR_BEFORE=2023&size=5"), + URI.create( + REQUEST_BASE_URL + "PublicationYearBefore=2024&publication_year_since=2023&size=3"), + URI.create(REQUEST_BASE_URL + "publicationYear=2022,2022&size=1"), + URI.create(REQUEST_BASE_URL + "publicationYear=2022&size=1"), + URI.create(REQUEST_BASE_URL + "publicationYear=,2022&size=5"), + URI.create(REQUEST_BASE_URL + "publicationYear=2022,&size=4"), + URI.create(REQUEST_BASE_URL + "publicationYear=2023&size=3"), + URI.create(REQUEST_BASE_URL + "publicationYear=2022,2023&size=4"), + URI.create(REQUEST_BASE_URL + "title=In+reply:+Why+big+data&size=1"), + URI.create(REQUEST_BASE_URL + "title=chronic+diseases&size=1"), + URI.create(REQUEST_BASE_URL + "title=antibacterial,Fishing&size=2"), + URI.create(REQUEST_BASE_URL + "query=antibacterial&fields=category,title&size=1"), + URI.create( + REQUEST_BASE_URL + + "query=antibacterial&fields=category,title,werstfg&ID_NOT=123&size=1"), + URI.create(REQUEST_BASE_URL + "query=European&fields=all&size=3"), + URI.create(REQUEST_BASE_URL + "CRISTIN_IDENTIFIER=3212342&size=1"), + URI.create(REQUEST_BASE_URL + "SCOPUS_IDENTIFIER=3212342&size=1")); + } - static Stream uriInvalidProvider() { - return Stream.of( - URI.create(REQUEST_BASE_URL + "size=7&sort="), - URI.create(REQUEST_BASE_URL + "query=European&fields"), - URI.create(REQUEST_BASE_URL + "feilName=epler"), - URI.create(REQUEST_BASE_URL + "query=epler&fields=feilName"), - URI.create(REQUEST_BASE_URL + "CREATED_DATE=epler"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORY:DEdd"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORdfgY:desc"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORY"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORY:asc:DEdd"), - URI.create(REQUEST_BASE_URL + "size=8&sort=epler"), - URI.create(REQUEST_BASE_URL + "size=8&sort=type:DEdd"), - URI.create(REQUEST_BASE_URL + "categories=hello+world"), - URI.create(REQUEST_BASE_URL + "tittles=hello+world&modified_before=2019-01"), - URI.create( - REQUEST_BASE_URL - + "conttributors=hello+world&PUBLICATION_YEAR_BEFORE=2020-01-01"), - URI.create(REQUEST_BASE_URL + "category=PhdThesis&sort=beunited+asc"), - URI.create(REQUEST_BASE_URL + "funding=NFR,296896"), - URI.create(REQUEST_BASE_URL + "useers=hello+world")); - } + static Stream uriInvalidProvider() { + return Stream.of( + URI.create(REQUEST_BASE_URL + "size=7&sort="), + URI.create(REQUEST_BASE_URL + "query=European&fields"), + URI.create(REQUEST_BASE_URL + "feilName=epler"), + URI.create(REQUEST_BASE_URL + "query=epler&fields=feilName"), + URI.create(REQUEST_BASE_URL + "CREATED_DATE=epler"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORY:DEdd"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORdfgY:desc"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORY"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORY:asc:DEdd"), + URI.create(REQUEST_BASE_URL + "size=8&sort=epler"), + URI.create(REQUEST_BASE_URL + "size=8&sort=type:DEdd"), + URI.create(REQUEST_BASE_URL + "categories=hello+world"), + URI.create(REQUEST_BASE_URL + "tittles=hello+world&modified_before=2019-01"), + URI.create( + REQUEST_BASE_URL + "conttributors=hello+world&PUBLICATION_YEAR_BEFORE=2020-01-01"), + URI.create(REQUEST_BASE_URL + "category=PhdThesis&sort=beunited+asc"), + URI.create(REQUEST_BASE_URL + "funding=NFR,296896"), + URI.create(REQUEST_BASE_URL + "useers=hello+world")); + } - @Test - void shouldCheckFacets() throws BadRequestException { - var hostAddress = URI.create(container.getHttpHostAddress()); - var uri1 = URI.create(REQUEST_BASE_URL + AGGREGATION.asCamelCase() + EQUAL + ALL); + @Test + void shouldCheckFacets() throws BadRequestException { + var hostAddress = URI.create(container.getHttpHostAddress()); + var uri1 = URI.create(REQUEST_BASE_URL + AGGREGATION.asCamelCase() + EQUAL + ALL); - var response1 = - ImportCandidateSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri1)) - .withDockerHostUri(hostAddress) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .build() - .doSearch(importCandidateClient); + var response1 = + ImportCandidateSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri1)) + .withDockerHostUri(hostAddress) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .build() + .doSearch(importCandidateClient); - assertNotNull(response1); + assertNotNull(response1); - var aggregations = response1.toPagedResponse().aggregations(); + var aggregations = response1.toPagedResponse().aggregations(); - assertFalse(aggregations.isEmpty()); - assertThat(aggregations.get(IMPORT_STATUS.asCamelCase()).size(), is(2)); - assertThat(aggregations.get(CONTRIBUTOR.asCamelCase()).size(), is(5)); - assertThat(aggregations.get(COLLABORATION_TYPE.asCamelCase()).size(), is(2)); - assertThat(aggregations.get(FILES.asCamelCase()).getFirst().count(), is(7)); - assertThat(aggregations.get(PUBLICATION_YEAR.asCamelCase()).size(), is(4)); - assertThat(aggregations.get(TYPE.asCamelCase()).size(), is(4)); - assertThat(aggregations.get(TOP_LEVEL_ORGANIZATION.asCamelCase()).size(), is(9)); - assertThat( - aggregations.get(TOP_LEVEL_ORGANIZATION.asCamelCase()).get(1).labels().get("nb"), - is(equalTo("Universitetet i Bergen"))); - } + assertFalse(aggregations.isEmpty()); + assertThat(aggregations.get(IMPORT_STATUS.asCamelCase()).size(), is(2)); + assertThat(aggregations.get(CONTRIBUTOR.asCamelCase()).size(), is(5)); + assertThat(aggregations.get(COLLABORATION_TYPE.asCamelCase()).size(), is(2)); + assertThat(aggregations.get(FILES.asCamelCase()).getFirst().count(), is(7)); + assertThat(aggregations.get(PUBLICATION_YEAR.asCamelCase()).size(), is(4)); + assertThat(aggregations.get(TYPE.asCamelCase()).size(), is(4)); + assertThat(aggregations.get(TOP_LEVEL_ORGANIZATION.asCamelCase()).size(), is(9)); + assertThat( + aggregations.get(TOP_LEVEL_ORGANIZATION.asCamelCase()).get(1).labels().get("nb"), + is(equalTo("Universitetet i Bergen"))); + } - @Test - void openSearchFailedResponse() throws IOException, InterruptedException { - HttpClient httpClient = mock(HttpClient.class); - var response = mock(HttpResponse.class); - when(httpClient.send(any(), any())).thenReturn(response); - when(response.statusCode()).thenReturn(500); - when(response.body()).thenReturn("EXPECTED ERROR"); - var toMapEntries = queryToMapEntries(URI.create(REQUEST_BASE_URL + "size=2")); - var importCandidateClient = - new ImportCandidateClient(httpClient, setupMockedCachedJwtProvider()); + @Test + void openSearchFailedResponse() throws IOException, InterruptedException { + HttpClient httpClient = mock(HttpClient.class); + var response = mock(HttpResponse.class); + when(httpClient.send(any(), any())).thenReturn(response); + when(response.statusCode()).thenReturn(500); + when(response.body()).thenReturn("EXPECTED ERROR"); + var toMapEntries = queryToMapEntries(URI.create(REQUEST_BASE_URL + "size=2")); + var importCandidateClient = + new ImportCandidateClient(httpClient, setupMockedCachedJwtProvider()); - assertThrows( - RuntimeException.class, - () -> - ImportCandidateSearchQuery.builder() - .withRequiredParameters(SIZE, FROM) - .fromTestQueryParameters(toMapEntries) - .build() - .doSearch(importCandidateClient)); - } + assertThrows( + RuntimeException.class, + () -> + ImportCandidateSearchQuery.builder() + .withRequiredParameters(SIZE, FROM) + .fromTestQueryParameters(toMapEntries) + .build() + .doSearch(importCandidateClient)); + } - @ParameterizedTest - @MethodSource("uriProvider") - void searchWithUriReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { - var response = - ImportCandidateSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE, SORT) - .build() - .doSearch(importCandidateClient); + @ParameterizedTest + @MethodSource("uriProvider") + void searchWithUriReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { + var response = + ImportCandidateSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE, SORT) + .build() + .doSearch(importCandidateClient); - var pagedResponse = response.toPagedResponse(); + var pagedResponse = response.toPagedResponse(); - assertNotNull(response.toPagedResponse()); - assertThat(pagedResponse.hits().size(), is(equalTo(response.parameters().get(SIZE).as()))); - assertThat(pagedResponse.totalHits(), is(equalTo(response.parameters().get(SIZE).as()))); - } + assertNotNull(response.toPagedResponse()); + assertThat(pagedResponse.hits().size(), is(equalTo(response.parameters().get(SIZE).as()))); + assertThat(pagedResponse.totalHits(), is(equalTo(response.parameters().get(SIZE).as()))); + } - @ParameterizedTest - @MethodSource("uriProvider") - @Disabled( - "Does not work. When test was written it returned an empty string even if there were" - + " supposed to be hits. Now we throw an exception instead as the method is not" - + " implemented.") - void searchWithUriReturnsCsvResponse(URI uri) throws ApiGatewayException { - var csvResult = - ImportCandidateSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE, SORT) - .withMediaType(Words.TEXT_CSV) - .build() - .doSearch(importCandidateClient); - assertNotNull(csvResult); - } + @ParameterizedTest + @MethodSource("uriProvider") + @Disabled( + "Does not work. When test was written it returned an empty string even if there were" + + " supposed to be hits. Now we throw an exception instead as the method is not" + + " implemented.") + void searchWithUriReturnsCsvResponse(URI uri) throws ApiGatewayException { + var csvResult = + ImportCandidateSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE, SORT) + .withMediaType(Words.TEXT_CSV) + .build() + .doSearch(importCandidateClient); + assertNotNull(csvResult); + } - @ParameterizedTest - @MethodSource("uriSortingProvider") - void searchUriWithSortingReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { - var response = - ImportCandidateSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .doSearch(importCandidateClient); + @ParameterizedTest + @MethodSource("uriSortingProvider") + void searchUriWithSortingReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { + var response = + ImportCandidateSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .doSearch(importCandidateClient); - var pagedResponse = response.toPagedResponse(); - assertNotNull(pagedResponse.id()); - assertNotNull(pagedResponse.context()); - assertTrue(pagedResponse.id().getScheme().contains("https")); - } + var pagedResponse = response.toPagedResponse(); + assertNotNull(pagedResponse.id()); + assertNotNull(pagedResponse.context()); + assertTrue(pagedResponse.id().getScheme().contains("https")); + } - @ParameterizedTest - @MethodSource("uriInvalidProvider") - void failToSearchUri(URI uri) { - assertThrows( - BadRequestException.class, - () -> - ImportCandidateSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .build() - .doSearch(importCandidateClient)); - } + @ParameterizedTest + @MethodSource("uriInvalidProvider") + void failToSearchUri(URI uri) { + assertThrows( + BadRequestException.class, + () -> + ImportCandidateSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .build() + .doSearch(importCandidateClient)); + } - @ParameterizedTest - @MethodSource("uriInvalidProvider") - void failToSetRequired(URI uri) { - assertThrows( - BadRequestException.class, - () -> - ImportCandidateSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE, CREATED_DATE) - .build() - .doSearch(importCandidateClient)); - } + @ParameterizedTest + @MethodSource("uriInvalidProvider") + void failToSetRequired(URI uri) { + assertThrows( + BadRequestException.class, + () -> + ImportCandidateSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE, CREATED_DATE) + .build() + .doSearch(importCandidateClient)); + } } diff --git a/search-commons/src/test/java/no/unit/nva/search/resource/ResourceClientTest.java b/search-commons/src/test/java/no/unit/nva/search/resource/ResourceClientTest.java index 08195306f..8be95d458 100644 --- a/search-commons/src/test/java/no/unit/nva/search/resource/ResourceClientTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/resource/ResourceClientTest.java @@ -132,1027 +132,967 @@ @Testcontainers class ResourceClientTest { - public static final int EXPECTED_NUMBER_OF_AGGREGATIONS = 10; - public static final String RESOURCE_VALID_DEV_URLS_JSON = "resource_urls.json"; - public static final String USER_SETTINGS_EMPTY_JSON = "user_settings_empty.json"; - public static final String USER_SETTINGS_JSON = "user_settings.json"; - public static final String BASE_URL = "https://x.org/?size=22&"; - public static final String PROPERTIES = "properties"; - public static final String NESTED = "nested"; - public static final String NOT_FOUND = "Not found"; - public static final String NUMBER_FIVE = "5"; - public static final String ONE_MINUTE = "1m"; - - static final String Y2019 = "2019"; - static final String Y2022 = "2022"; - static final String Y2020 = "2020"; - private static final Logger logger = LoggerFactory.getLogger(ResourceClientTest.class); - private static ScrollClient scrollClient; - private static ResourceClient searchClient; - private static IndexingClient indexingClient; - - @BeforeAll - static void setUp() { - var cachedJwtProvider = setupMockedCachedJwtProvider(); - var mochedHttpClient = mock(HttpClient.class); - var userSettingsClient = new UserSettingsClient(mochedHttpClient, cachedJwtProvider); - var response = mockedFutureHttpResponse(Path.of(USER_SETTINGS_JSON)); - when(mochedHttpClient.sendAsync(any(), any())) - .thenReturn(response) - .thenReturn(mockedFutureHttpResponse("")) - .thenReturn(mockedFutureFailed()); - searchClient = - new ResourceClient( - HttpClient.newHttpClient(), cachedJwtProvider, userSettingsClient); - scrollClient = new ScrollClient(HttpClient.newHttpClient(), cachedJwtProvider); - indexingClient = initiateIndexingClient(cachedJwtProvider); - } - - private static IndexingClient initiateIndexingClient(CachedJwtProvider cachedJwtProvider) { - var restClientBuilder = RestClient.builder(HttpHost.create(container.getHttpHostAddress())); - var restHighLevelClientWrapper = new RestHighLevelClientWrapper(restClientBuilder); - return new IndexingClient(restHighLevelClientWrapper, cachedJwtProvider); - } - - static Stream uriPagingProvider() { - return Stream.of( - createArgument("page=0&aggregation=all", 22), - createArgument("page=1&size=10&aggregation=all&sort=modifiedDate:asc", 10), - createArgument("page=3&aggregation=all&sort=modifiedDate:asc", 0), - createArgument("page=1&aggregation=all&size=1", 1), - createArgument("page=2&aggregation=all&size=1", 1), - createArgument("page=3&aggregation=all&size=1", 1), - createArgument("page=0&aggregation=all&size=0", 0), - createArgument("offset=15&aggregation=all&size=2", 2), - createArgument("offset=15&aggregation=all&limit=2", 2), - createArgument("offset=15&aggregation=all&results=2", 2), - createArgument("offset=15&aggregation=all&per_page=2", 2), - createArgument("OFFSET=15&aggregation=all&PER_PAGE=2", 2), - createArgument("offset=15&aggregation=all&perPage=2", 2)); - } - - private static Arguments createArgument(String searchUri, int expectedCount) { - return Arguments.of(URI.create(BASE_URL + searchUri), expectedCount); - } - - static Stream uriInvalidProvider() { - return Stream.of( - URI.create(BASE_URL + "sort=epler"), - URI.create(BASE_URL + "sort=CATEGORY:DEdd"), - URI.create(BASE_URL + "sort=CATEGORY:desc:asc"), - URI.create(BASE_URL + "categories=hello+world&lang=en"), - URI.create(BASE_URL + "tittles=hello+world&modified_before=2019-01-01"), - URI.create(BASE_URL + "conttributors=hello+world&published_before=2020-01-01"), - URI.create(BASE_URL + "category=PhdThesis&sort=beunited+asc"), - URI.create(BASE_URL + "funding=NFR,296896"), - URI.create(BASE_URL + "useers=hello+world&lang=en")); - } - - /** - * Provides a stream of valid page ranges for parameterized tests. Each argument consists of a - * minimum and maximum page count, and the expected number of results. - * - * @return a stream of arguments where each argument is a tuple of (min, max, - * expectedResultCount) - */ - static Stream provideValidPageRanges() { - return Stream.of( - Arguments.of(1, 100, 8), - Arguments.of(37, 39, 1), - Arguments.of(38, 38, 1), - Arguments.of(17, 20, 3)); - } - - static Stream uriProvider() { - return loadMapFromResource(RESOURCE_VALID_DEV_URLS_JSON).entrySet().stream() - .map(entry -> createArgument(entry.getKey(), (Integer) entry.getValue())); - } - - static Stream roleProvider() { - return Stream.of( - Arguments.of(5, List.of(MANAGE_RESOURCES_ALL)), - Arguments.of(22, List.of(MANAGE_CUSTOMERS)), - Arguments.of(22, List.of(MANAGE_CUSTOMERS, MANAGE_RESOURCES_ALL))); - } - - static Stream uriSortingProvider() { - - return Stream.of( - URI.create(BASE_URL + "status=PUBLISHED&sort=relevance,createdDate"), - URI.create(BASE_URL + "query=year+project&sort=RELEVANCE,modifiedDate"), - URI.create(BASE_URL + "status=PUBLISHED&sort=unitId"), - URI.create(BASE_URL + "query=PublishedFile&sort=unitId"), - URI.create(BASE_URL + "query=research&orderBy=UNIT_ID:asc,title:desc"), - URI.create( - BASE_URL - + "query=year+project,PublishedFile&sort=created_date&sortOrder=asc&sort=category&order=desc"), - URI.create( - BASE_URL - + "query=project,PublishedFile&sort=modified_date&sortOrder=asc&sort=category"), - URI.create( - BASE_URL - + "query=PublishedFile&sort=published_date&sortOrder=asc&sort=category"), - URI.create(BASE_URL + "query=PublishedFile&sort=published_date:desc"), - URI.create(BASE_URL + "query=PublishedFile&size=10&from=0&sort=modified_date"), - URI.create(BASE_URL + "query=infrastructure&sort=instanceType"), - URI.create(BASE_URL + "status=PUBLISHED&sort=createdDate"), - URI.create(BASE_URL + "status=PUBLISHED&sort=modifiedDate"), - URI.create(BASE_URL + "status=PUBLISHED&sort=publishedDate"), - URI.create(BASE_URL + "status=PUBLISHED&sort=publicationDate"), - URI.create(BASE_URL + "query=PublishedFile&sort=title"), - URI.create(BASE_URL + "query=PublishedFile&sort=user"), - URI.create( - BASE_URL + "query=year+project&orderBy=created_date:asc,modifiedDate:desc"), - URI.create( - BASE_URL - + "query=year+project&orderBy=RELEVANCE,created_date:asc,modifiedDate:desc" - + "&searchAfter=3.4478912,1241234,23412,123"), - URI.create( - BASE_URL - + "query=year+project&sort=published_date+asc&sort=category+desc")); - } - - private static int pageNodeToInt(JsonNode hit) { - return hit.at( - String.join( - SLASH, - SLASH + ENTITY_DESCRIPTION, - REFERENCE, - PUBLICATION_INSTANCE, - PAGES, - PAGES)) - .asInt(); - } - - private static HttpResponseFormatter fetchDocumentWithId( - IndexDocument indexDocument) throws BadRequestException { - return ResourceSearchQuery.builder() - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .fromTestParameterMap(Map.of(ID, indexDocument.getDocumentIdentifier())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - } - - private static IndexDocument indexDocumentWithIdentifier() throws JsonProcessingException { - var identifier = SortableIdentifier.next(); - var document = - """ + public static final int EXPECTED_NUMBER_OF_AGGREGATIONS = 10; + public static final String RESOURCE_VALID_DEV_URLS_JSON = "resource_urls.json"; + public static final String USER_SETTINGS_EMPTY_JSON = "user_settings_empty.json"; + public static final String USER_SETTINGS_JSON = "user_settings.json"; + public static final String BASE_URL = "https://x.org/?size=22&"; + public static final String PROPERTIES = "properties"; + public static final String NESTED = "nested"; + public static final String NOT_FOUND = "Not found"; + public static final String NUMBER_FIVE = "5"; + public static final String ONE_MINUTE = "1m"; + + static final String Y2019 = "2019"; + static final String Y2022 = "2022"; + static final String Y2020 = "2020"; + private static final Logger logger = LoggerFactory.getLogger(ResourceClientTest.class); + private static ScrollClient scrollClient; + private static ResourceClient searchClient; + private static IndexingClient indexingClient; + + @BeforeAll + static void setUp() { + var cachedJwtProvider = setupMockedCachedJwtProvider(); + var mochedHttpClient = mock(HttpClient.class); + var userSettingsClient = new UserSettingsClient(mochedHttpClient, cachedJwtProvider); + var response = mockedFutureHttpResponse(Path.of(USER_SETTINGS_JSON)); + when(mochedHttpClient.sendAsync(any(), any())) + .thenReturn(response) + .thenReturn(mockedFutureHttpResponse("")) + .thenReturn(mockedFutureFailed()); + searchClient = + new ResourceClient(HttpClient.newHttpClient(), cachedJwtProvider, userSettingsClient); + scrollClient = new ScrollClient(HttpClient.newHttpClient(), cachedJwtProvider); + indexingClient = initiateIndexingClient(cachedJwtProvider); + } + + private static IndexingClient initiateIndexingClient(CachedJwtProvider cachedJwtProvider) { + var restClientBuilder = RestClient.builder(HttpHost.create(container.getHttpHostAddress())); + var restHighLevelClientWrapper = new RestHighLevelClientWrapper(restClientBuilder); + return new IndexingClient(restHighLevelClientWrapper, cachedJwtProvider); + } + + static Stream uriPagingProvider() { + return Stream.of( + createArgument("page=0&aggregation=all", 22), + createArgument("page=1&size=10&aggregation=all&sort=modifiedDate:asc", 10), + createArgument("page=3&aggregation=all&sort=modifiedDate:asc", 0), + createArgument("page=1&aggregation=all&size=1", 1), + createArgument("page=2&aggregation=all&size=1", 1), + createArgument("page=3&aggregation=all&size=1", 1), + createArgument("page=0&aggregation=all&size=0", 0), + createArgument("offset=15&aggregation=all&size=2", 2), + createArgument("offset=15&aggregation=all&limit=2", 2), + createArgument("offset=15&aggregation=all&results=2", 2), + createArgument("offset=15&aggregation=all&per_page=2", 2), + createArgument("OFFSET=15&aggregation=all&PER_PAGE=2", 2), + createArgument("offset=15&aggregation=all&perPage=2", 2)); + } + + private static Arguments createArgument(String searchUri, int expectedCount) { + return Arguments.of(URI.create(BASE_URL + searchUri), expectedCount); + } + + static Stream uriInvalidProvider() { + return Stream.of( + URI.create(BASE_URL + "sort=epler"), + URI.create(BASE_URL + "sort=CATEGORY:DEdd"), + URI.create(BASE_URL + "sort=CATEGORY:desc:asc"), + URI.create(BASE_URL + "categories=hello+world&lang=en"), + URI.create(BASE_URL + "tittles=hello+world&modified_before=2019-01-01"), + URI.create(BASE_URL + "conttributors=hello+world&published_before=2020-01-01"), + URI.create(BASE_URL + "category=PhdThesis&sort=beunited+asc"), + URI.create(BASE_URL + "funding=NFR,296896"), + URI.create(BASE_URL + "useers=hello+world&lang=en")); + } + + /** + * Provides a stream of valid page ranges for parameterized tests. Each argument consists of a + * minimum and maximum page count, and the expected number of results. + * + * @return a stream of arguments where each argument is a tuple of (min, max, expectedResultCount) + */ + static Stream provideValidPageRanges() { + return Stream.of( + Arguments.of(1, 100, 8), + Arguments.of(37, 39, 1), + Arguments.of(38, 38, 1), + Arguments.of(17, 20, 3)); + } + + static Stream uriProvider() { + return loadMapFromResource(RESOURCE_VALID_DEV_URLS_JSON).entrySet().stream() + .map(entry -> createArgument(entry.getKey(), (Integer) entry.getValue())); + } + + static Stream roleProvider() { + return Stream.of( + Arguments.of(5, List.of(MANAGE_RESOURCES_ALL)), + Arguments.of(22, List.of(MANAGE_CUSTOMERS)), + Arguments.of(22, List.of(MANAGE_CUSTOMERS, MANAGE_RESOURCES_ALL))); + } + + static Stream uriSortingProvider() { + + return Stream.of( + URI.create(BASE_URL + "status=PUBLISHED&sort=relevance,createdDate"), + URI.create(BASE_URL + "query=year+project&sort=RELEVANCE,modifiedDate"), + URI.create(BASE_URL + "status=PUBLISHED&sort=unitId"), + URI.create(BASE_URL + "query=PublishedFile&sort=unitId"), + URI.create(BASE_URL + "query=research&orderBy=UNIT_ID:asc,title:desc"), + URI.create( + BASE_URL + + "query=year+project,PublishedFile&sort=created_date&sortOrder=asc&sort=category&order=desc"), + URI.create( + BASE_URL + + "query=project,PublishedFile&sort=modified_date&sortOrder=asc&sort=category"), + URI.create( + BASE_URL + "query=PublishedFile&sort=published_date&sortOrder=asc&sort=category"), + URI.create(BASE_URL + "query=PublishedFile&sort=published_date:desc"), + URI.create(BASE_URL + "query=PublishedFile&size=10&from=0&sort=modified_date"), + URI.create(BASE_URL + "query=infrastructure&sort=instanceType"), + URI.create(BASE_URL + "status=PUBLISHED&sort=createdDate"), + URI.create(BASE_URL + "status=PUBLISHED&sort=modifiedDate"), + URI.create(BASE_URL + "status=PUBLISHED&sort=publishedDate"), + URI.create(BASE_URL + "status=PUBLISHED&sort=publicationDate"), + URI.create(BASE_URL + "query=PublishedFile&sort=title"), + URI.create(BASE_URL + "query=PublishedFile&sort=user"), + URI.create(BASE_URL + "query=year+project&orderBy=created_date:asc,modifiedDate:desc"), + URI.create( + BASE_URL + + "query=year+project&orderBy=RELEVANCE,created_date:asc,modifiedDate:desc" + + "&searchAfter=3.4478912,1241234,23412,123"), + URI.create(BASE_URL + "query=year+project&sort=published_date+asc&sort=category+desc")); + } + + private static int pageNodeToInt(JsonNode hit) { + return hit.at( + String.join( + SLASH, SLASH + ENTITY_DESCRIPTION, REFERENCE, PUBLICATION_INSTANCE, PAGES, PAGES)) + .asInt(); + } + + private static HttpResponseFormatter fetchDocumentWithId( + IndexDocument indexDocument) throws BadRequestException { + return ResourceSearchQuery.builder() + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .fromTestParameterMap(Map.of(ID, indexDocument.getDocumentIdentifier())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + } + + private static IndexDocument indexDocumentWithIdentifier() throws JsonProcessingException { + var identifier = SortableIdentifier.next(); + var document = + """ { "type": "Publication", "status": "PUBLISHED", "identifier": "__ID__" } """ - .replace("__ID__", identifier.toString()); - var jsonNode = JsonUtils.dtoObjectMapper.readTree(document); - return new IndexDocument(new EventConsumptionAttributes(RESOURCES, identifier), jsonNode); - } - - @Test - void shouldCheckMapping() { - - var mapping = indexingClient.getMapping(RESOURCES); - assertThat(mapping, is(notNullValue())); - var topLevelOrgType = - mapping.path(PROPERTIES).path(TOP_LEVEL_ORGANIZATIONS).path(TYPE).textValue(); - assertThat(topLevelOrgType, is(equalTo(NESTED))); - logger.info(mapping.toString()); - } - - @Test - void testingFromRequestInfoSuccessful() throws UnauthorizedException, BadRequestException { - AtomicReference uri = new AtomicReference<>(); - uriSortingProvider().findFirst().ifPresent(uri::set); - - var accessRights = List.of(MANAGE_CUSTOMERS); - var mockedRequestInfoLocal = mock(RequestInfo.class); - when(mockedRequestInfoLocal.getPersonAffiliation()) - .thenReturn( - URI.create( - "https://api.dev.nva.aws.unit.no/cristin/organization/184.0.0.0")); - when(mockedRequestInfoLocal.getAccessRights()).thenReturn(accessRights); - - var result = - ResourceSearchQuery.builder() - .fromRequestInfo(mockedRequestInfoLocal) - .fromTestQueryParameters(queryToMapEntries(uri.get())) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfoLocal) - .doSearch(searchClient); - assertThat(result.toPagedResponse().hits().size(), is(2)); - } - - @Test - void shouldCheckFacets() throws BadRequestException { - var hostAddress = URI.create(container.getHttpHostAddress()); - var uri1 = - URI.create( - BASE_URL - + AGGREGATION.asCamelCase() - + EQUAL - + ALL - + "&query=EntityDescription"); - - var response1 = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri1)) - .withDockerHostUri(hostAddress) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - assertNotNull(response1); - - var aggregations = response1.toPagedResponse().aggregations(); - - assertFalse(aggregations.isEmpty()); - assertThat(aggregations.get(TYPE).size(), is(6)); - assertThat(aggregations.get(FILES).getFirst().count(), is(17)); - assertThat(aggregations.get(LICENSE).getFirst().count(), is(10)); - assertThat(aggregations.get(FUNDING_SOURCE).size(), is(1)); - assertThat(aggregations.get(PUBLISHER).getFirst().count(), is(2)); - assertThat(aggregations.get(CONTRIBUTOR).size(), is(17)); - assertThat(aggregations.get(TOP_LEVEL_ORGANIZATION).size(), is(11)); - assertThat( - aggregations.get(TOP_LEVEL_ORGANIZATION).get(1).labels().get("nb"), - is(equalTo("Sikt – Kunnskapssektorens tjenesteleverandør"))); - } - - @Test - void userSettingsNotFoundReturn200() - throws IOException, InterruptedException, BadRequestException { - var mochedHttpClient = mock(HttpClient.class); - var userSettingsClient = - new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); - var mockedResponse = mockedHttpResponse(USER_SETTINGS_EMPTY_JSON, 200); - when(mochedHttpClient.send(any(), any())).thenReturn(mockedResponse); - var searchClient = - new ResourceClient( - HttpClient.newHttpClient(), - setupMockedCachedJwtProvider(), - userSettingsClient); - - var uri = - URI.create( - "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - assertNotNull(response); - } - - @Test - void userSettingsNotFoundReturn404() - throws IOException, InterruptedException, BadRequestException { - var mochedHttpClient = mock(HttpClient.class); - var userSettingsClient = - new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); - var mockedResponse = mockedHttpResponse(USER_SETTINGS_EMPTY_JSON, 404); - when(mochedHttpClient.send(any(), any())).thenReturn(mockedResponse); - var searchClient = - new ResourceClient( - HttpClient.newHttpClient(), - setupMockedCachedJwtProvider(), - userSettingsClient); - - var uri = - URI.create( - "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE, SORT) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - assertNotNull(response); - } - - @Test - void userSettingsFailsIOException() - throws IOException, InterruptedException, BadRequestException { - var mochedHttpClient = mock(HttpClient.class); - var userSettingsClient = - new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); - when(mochedHttpClient.send(any(), any())).thenThrow(new IOException(NOT_FOUND)); - var searchClient = - new ResourceClient( - HttpClient.newHttpClient(), - setupMockedCachedJwtProvider(), - userSettingsClient); - - var uri = - URI.create( - "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - assertNotNull(response); - } - - @Test - void userSettingsFailswithWrongFormat() - throws IOException, InterruptedException, BadRequestException { - var mochedHttpClient = mock(HttpClient.class); - var userSettingsClient = - new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); - when(mochedHttpClient.send(any(), any())) - .thenReturn(mockedHttpResponse(USER_SETTINGS_EMPTY_JSON, 200)); - var searchClient = - new ResourceClient( - HttpClient.newHttpClient(), - setupMockedCachedJwtProvider(), - userSettingsClient); - - var uri = - URI.create( - "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - assertNotNull(response); - } - - @Test - void emptyResultShouldIncludeHits() throws BadRequestException { - var uri = URI.create("https://x.org/?id=018b857b77b7&from=10"); - - var pagedResult = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .requiredStatus( - NEW, - DRAFT, - PUBLISHED_METADATA, - PUBLISHED, - DELETED, - UNPUBLISHED, - DRAFT_FOR_DELETION) - .apply() - .doSearch(searchClient) - .toString(); - assertNotNull(pagedResult); - assertTrue(pagedResult.contains("\"hits\":[")); - } - - @Test - void searchAfterAndSortByRelevanceException() { - var uri = - URI.create("https://x.org/?id=018b857b77b7&from=10&searchAfter=12&sort=relevance"); - assertThrows( - BadRequestException.class, - () -> - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .doSearch(searchClient)); - } - - @ParameterizedTest - @CsvSource({"FYS5960,1", "fys5960,1", "fys596,1", "fys5961,0"}) - void shouldReturnCaseInsensitiveCourses(String searchValue, int expectedHits) - throws BadRequestException { - var uri = URI.create("https://x.org/?course=" + searchValue); - - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .requiredStatus( - NEW, - DRAFT, - PUBLISHED_METADATA, - PUBLISHED, - DELETED, - UNPUBLISHED, - DRAFT_FOR_DELETION) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - assertEquals(expectedHits, pagedSearchResourceDto.totalHits()); - } - - @ParameterizedTest - @MethodSource("roleProvider") - void isSearchingForAllPublicationsAsRoleWork(int expectedHits, List accessRights) - throws UnauthorizedException, BadRequestException { - var requestInfo = mock(RequestInfo.class); - when(requestInfo.getAccessRights()).thenReturn(accessRights); - when(requestInfo.getPersonAffiliation()) - .thenReturn( - URI.create( - "https://api.dev.nva.aws.unit.no/cristin/organization/20754.0.0.0")); - - var response = - ResourceSearchQuery.builder() - .fromRequestInfo(requestInfo) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withParameter(NODES_EXCLUDED, META_INFO) - .withParameter(STATISTICS, Boolean.TRUE.toString()) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .fromRequestInfo(requestInfo) - .doSearch(searchClient); - - assertNotNull(response); - - var pagedSearchResourceDto = response.toPagedResponse(); - assertEquals(expectedHits, pagedSearchResourceDto.totalHits()); - } - - @Test - void isSearchingWithWrongAccessFails() throws UnauthorizedException { - var requestInfo = mock(RequestInfo.class); - when(requestInfo.getAccessRights()).thenReturn(List.of(MANAGE_DEGREE)); - when(requestInfo.getPersonAffiliation()) - .thenReturn(URI.create("https://api.dev.nva.aws.unit.no/cristin/organization/")); - - assertThrows( - UnauthorizedException.class, - () -> - ResourceSearchQuery.builder() - .fromRequestInfo(requestInfo) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withParameter(NODES_EXCLUDED, META_INFO) - .withParameter(STATISTICS, Boolean.TRUE.toString()) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .fromRequestInfo(requestInfo) - .doSearch(searchClient)); - } - - @Test - void withOrganizationDoWork() throws BadRequestException, UnauthorizedException { - var requestInfo = mock(RequestInfo.class); - when(requestInfo.getAccessRights()).thenReturn(List.of(MANAGE_CUSTOMERS)); - when(requestInfo.getTopLevelOrgCristinId()) - .thenReturn( - Optional.of( - URI.create( - "https://api.dev.nva.aws.unit.no/cristin/organization/184.0.0.0"))); - var response = - ResourceSearchQuery.builder() - .fromRequestInfo(requestInfo) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withParameter(NODES_EXCLUDED, META_INFO) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .fromRequestInfo(requestInfo) - .doSearch(searchClient); - - assertNotNull(response); - - var pagedSearchResourceDto = response.toPagedResponse(); - assertEquals(3, pagedSearchResourceDto.totalHits()); - } - - @Test - void scrollClientExceuteOK() throws BadRequestException { - var includedNodes = String.join(COMMA, ResourceCsvTransformer.getJsonFields()); - var firstResponse = - ResourceSearchQuery.builder() - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withParameter(FROM, ZERO) - .withParameter(SIZE, NUMBER_FIVE) - .withParameter(AGGREGATION, NONE) - .withParameter(NODES_INCLUDED, includedNodes) - .build() - .withFilter() - .requiredStatus(PUBLISHED_METADATA, PUBLISHED) - .apply() - .withScrollTime(ONE_MINUTE) - .doSearch(searchClient) - .swsResponse(); - - var response = - ScrollQuery.builder() - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withInitialResponse(firstResponse) - .withScrollTime(ONE_MINUTE) - .build() - .doSearch(scrollClient) - .toCsvText(); - assertNotNull(response); - } - - @ParameterizedTest - @MethodSource("uriPagingProvider") - void searchWithUriPageableReturnsOpenSearchResponse(URI uri, int expectedCount) - throws ApiGatewayException { - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .validate() - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - assertNotNull(pagedSearchResourceDto); - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); - assertThat( - pagedSearchResourceDto.aggregations().size(), - is(equalTo(EXPECTED_NUMBER_OF_AGGREGATIONS))); - logger.debug(pagedSearchResourceDto.id().toString()); - } - - // TODO: Remove duplicate test? - @ParameterizedTest - @MethodSource("uriProvider") - void searchWithUriReturnsOpenSearchAwsResponse(URI uri, int expectedCount) - throws ApiGatewayException { - - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - assertNotNull(pagedSearchResourceDto); - if (expectedCount == 0) { - logger.debug(pagedSearchResourceDto.toJsonString()); - } else { - logger.debug(pagedSearchResourceDto.toString()); - } - - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); - assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedCount))); - } - - @Test - void shouldFindAnthologyWithChapters() throws ApiGatewayException { - // Given that a parent document exists of type BookAnthology, - // and the parent document has two children of type AcademicChapter, - // when a query filters by hasParts of type AcademicChapter, - // then one document should be returned, - // and the returned document should be of type BookAnthology, - // and the returned document should have the ID of the parent document. - - var uri = - UriWrapper.fromUri( - "https://x.org/?instanceTypeofChild=AcademicChapter&from=0&size=20") - .getUri(); - var expectedHits = 1; - var expectedParentIdSuffix = "01905518408c-dba987f7-0d84-4519-a625-89605672afc8"; - - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - var document = pagedSearchResourceDto.hits().getFirst(); - var actualId = document.get(IDENTIFIER).asText(); - - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); - assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); - assertThat(actualId, is(equalTo(expectedParentIdSuffix))); - } - - @Test - void shouldFindDocumentsWithParent() throws ApiGatewayException { - // Given that a parent document exists, - // and the parent document has two children, - // when a query filters by HAS_PARENT=true, - // then two child documents should be returned, - // and the parent ID should be found in each child document. - - var uri = UriWrapper.fromUri("https://x.org/?HAS_PARENT=true&from=0&size=20").getUri(); - var expectedHits = 2; - var expectedParentIdSuffix = "0190554a46d9-41780102-675d-43ba-81df-17168d78fa22"; - - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - var document = pagedSearchResourceDto.hits().getFirst(); - var actualId = document.get(IDENTIFIER).asText(); - - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); - assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); - assertThat(actualId, is(equalTo(expectedParentIdSuffix))); - } - - @Test - void shouldFindDocumentsWithChildren() throws ApiGatewayException { - // Given that a parent document with two children exists, - // and the parent document has two children, - // when a query filters by HAS_CHILDREN=true, - // then one document should be returned, - // and the returned document should have the ID of the parent document. - - var uri = UriWrapper.fromUri("https://x.org/?HAS_PARENT=true&from=0&size=20").getUri(); - var expectedHits = 2; - var expectedParentIdSuffix = "01905518408c-dba987f7-0d84-4519-a625-89605672afc8"; - - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - var documents = pagedSearchResourceDto.hits(); - - for (var document : documents) { - var actualParentId = - document.get(ENTITY_DESCRIPTION) - .get(REFERENCE) - .get(PUBLICATION_CONTEXT) - .get(ID) - .asText(); - - assertThat(actualParentId, containsString(expectedParentIdSuffix)); - } - - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); - assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); - } - - @Test - void shouldFindChaptersOfBookAnthology() throws ApiGatewayException { - // Given that a parent document exists of type BookAnthology, - // and the parent document has two children of type AcademicChapter, - // when a query filters by partOf type BookAnthology, - // then two child documents should be returned, - // and the parent ID should be found in each child document. - - var uri = - UriWrapper.fromUri( - "https://x.org/?instanceTypeOfParent=BookAnthology&from=0&size=20") - .getUri(); - var expectedHits = 2; - var expectedParentIdSuffix = "01905518408c-dba987f7-0d84-4519-a625-89605672afc8"; - - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - var documents = pagedSearchResourceDto.hits(); - - for (var document : documents) { - var actualParentId = - document.get(ENTITY_DESCRIPTION) - .get(REFERENCE) - .get(PUBLICATION_CONTEXT) - .get(ID) - .asText(); - - var actualInstanceType = - document.get(ENTITY_DESCRIPTION) - .get(REFERENCE) - .get(PUBLICATION_INSTANCE) - .get(TYPE) - .asText(); - - assertThat(actualParentId, containsString(expectedParentIdSuffix)); - assertThat(actualInstanceType, is(equalTo("AcademicChapter"))); - } - - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); - assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); - } - - @ParameterizedTest - @MethodSource("uriProvider") - void searchWithUriReturnsCsvResponse(URI uri) throws ApiGatewayException { - var csvResult = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withAlwaysExcludedFields(GLOBAL_EXCLUDED_FIELDS) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withMediaType(Words.TEXT_CSV) - .build() - .withFilter() - .requiredStatus(PUBLISHED_METADATA) - .apply() - .doSearch(searchClient) - .toString(); - assertNotNull(csvResult); - } - - @ParameterizedTest - @MethodSource("uriSortingProvider") - void searchUriWithSortingReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { - var response = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE, SORT) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - assertNotNull(pagedSearchResourceDto.id()); - var searchName = response.parameters().get(SORT).split(COMMA)[0].split(COLON)[0]; - var searchFieldName = - ResourceSort.fromSortKey(searchName) - .jsonPaths() - .findFirst() - .map(path -> path.contains(KEYWORD) ? trimKeyword(path) : path) - .map(path -> SLASH + path.replace(DOT, SLASH)) - .orElseThrow(); - - var logInfo = - response.swsResponse().hits().hits().stream() - .map(item -> item._score() + " + " + searchFieldName) - .collect(Collectors.joining(SPACE + PIPE + SPACE)); - logger.debug(logInfo); - assertNotNull(pagedSearchResourceDto.context()); - assertTrue(pagedSearchResourceDto.totalHits() >= 0); - } - - private String trimKeyword(String path) { - return path.substring(0, path.indexOf(KEYWORD) - 1); - } - - @ParameterizedTest - @MethodSource("uriInvalidProvider") - void failToSearchUri(URI uri) { - assertThrows( - BadRequestException.class, - () -> - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .doSearch(searchClient)); - } - - @Test - void shouldReturnResourcesForScientificPeriods() throws BadRequestException { - var response = - ResourceSearchQuery.builder() - .fromTestParameterMap( - Map.of( - SCIENTIFIC_REPORT_PERIOD_SINCE.asCamelCase(), - Y2019, - SCIENTIFIC_REPORT_PERIOD_BEFORE.asCamelCase(), - Y2022)) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - assertThat(pagedSearchResourceDto.hits(), hasSize(2)); - } - - @Test - void shouldReturnResourcesForSinglePeriods() throws BadRequestException { - var response = - ResourceSearchQuery.builder() - .fromTestParameterMap( - Map.of( - SCIENTIFIC_REPORT_PERIOD_SINCE.asCamelCase(), - Y2019, - SCIENTIFIC_REPORT_PERIOD_BEFORE.asCamelCase(), - Y2020)) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - var pagedSearchResourceDto = response.toPagedResponse(); - assertThat(pagedSearchResourceDto.hits(), hasSize(1)); - } - - @Test - void - shouldNotReturnResourcesContainingAffiliationThatShouldBeExcludedWhenIsSubunitOfRequestedViewingScopeI() - throws BadRequestException { - var viewingScope = - URLEncoder.encode( - "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.0.0", - StandardCharsets.UTF_8); - var response = - ResourceSearchQuery.builder() - .fromTestParameterMap( - Map.of( - UNIT.asCamelCase(), - viewingScope, - EXCLUDE_SUBUNITS.asCamelCase(), - Boolean.TRUE.toString())) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - var excludedSubunit = "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.1.0"; - - assertThat(pagedSearchResourceDto.toJsonString(), not(containsString(excludedSubunit))); - assertThat(pagedSearchResourceDto.hits(), hasSize(1)); - } - - @Test - void shouldReturnResourcesWithSubunitsWhenExcludedSubunitsNotProvided() - throws BadRequestException { - var unit = - URLEncoder.encode( - "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.0.0", - StandardCharsets.UTF_8); - var topLevelOrg = - URLEncoder.encode( - "https://api.dev.nva.aws.unit.no/cristin/organization/20754.0.0.0", - StandardCharsets.UTF_8); - var response = - ResourceSearchQuery.builder() - .fromTestParameterMap( - Map.of( - UNIT.asCamelCase(), - unit, - TOP_LEVEL_ORGANIZATION, - topLevelOrg)) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA, DELETED) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - var includedSubunitI = "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.1.0"; - var includedSubunitII = "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.1.1"; - - assertThat(pagedSearchResourceDto.toJsonString(), containsString(includedSubunitI)); - assertThat(pagedSearchResourceDto.toJsonString(), containsString(includedSubunitII)); - assertThat(pagedSearchResourceDto.hits(), hasSize(2)); + .replace("__ID__", identifier.toString()); + var jsonNode = JsonUtils.dtoObjectMapper.readTree(document); + return new IndexDocument(new EventConsumptionAttributes(RESOURCES, identifier), jsonNode); + } + + @Test + void shouldCheckMapping() { + + var mapping = indexingClient.getMapping(RESOURCES); + assertThat(mapping, is(notNullValue())); + var topLevelOrgType = + mapping.path(PROPERTIES).path(TOP_LEVEL_ORGANIZATIONS).path(TYPE).textValue(); + assertThat(topLevelOrgType, is(equalTo(NESTED))); + logger.info(mapping.toString()); + } + + @Test + void testingFromRequestInfoSuccessful() throws UnauthorizedException, BadRequestException { + AtomicReference uri = new AtomicReference<>(); + uriSortingProvider().findFirst().ifPresent(uri::set); + + var accessRights = List.of(MANAGE_CUSTOMERS); + var mockedRequestInfoLocal = mock(RequestInfo.class); + when(mockedRequestInfoLocal.getPersonAffiliation()) + .thenReturn(URI.create("https://api.dev.nva.aws.unit.no/cristin/organization/184.0.0.0")); + when(mockedRequestInfoLocal.getAccessRights()).thenReturn(accessRights); + + var result = + ResourceSearchQuery.builder() + .fromRequestInfo(mockedRequestInfoLocal) + .fromTestQueryParameters(queryToMapEntries(uri.get())) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfoLocal) + .doSearch(searchClient); + assertThat(result.toPagedResponse().hits().size(), is(2)); + } + + @Test + void shouldCheckFacets() throws BadRequestException { + var hostAddress = URI.create(container.getHttpHostAddress()); + var uri1 = + URI.create(BASE_URL + AGGREGATION.asCamelCase() + EQUAL + ALL + "&query=EntityDescription"); + + var response1 = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri1)) + .withDockerHostUri(hostAddress) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + assertNotNull(response1); + + var aggregations = response1.toPagedResponse().aggregations(); + + assertFalse(aggregations.isEmpty()); + assertThat(aggregations.get(TYPE).size(), is(6)); + assertThat(aggregations.get(FILES).getFirst().count(), is(17)); + assertThat(aggregations.get(LICENSE).getFirst().count(), is(10)); + assertThat(aggregations.get(FUNDING_SOURCE).size(), is(1)); + assertThat(aggregations.get(PUBLISHER).getFirst().count(), is(2)); + assertThat(aggregations.get(CONTRIBUTOR).size(), is(17)); + assertThat(aggregations.get(TOP_LEVEL_ORGANIZATION).size(), is(11)); + assertThat( + aggregations.get(TOP_LEVEL_ORGANIZATION).get(1).labels().get("nb"), + is(equalTo("Sikt – Kunnskapssektorens tjenesteleverandør"))); + } + + @Test + void userSettingsNotFoundReturn200() + throws IOException, InterruptedException, BadRequestException { + var mochedHttpClient = mock(HttpClient.class); + var userSettingsClient = + new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); + var mockedResponse = mockedHttpResponse(USER_SETTINGS_EMPTY_JSON, 200); + when(mochedHttpClient.send(any(), any())).thenReturn(mockedResponse); + var searchClient = + new ResourceClient( + HttpClient.newHttpClient(), setupMockedCachedJwtProvider(), userSettingsClient); + + var uri = + URI.create( + "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + assertNotNull(response); + } + + @Test + void userSettingsNotFoundReturn404() + throws IOException, InterruptedException, BadRequestException { + var mochedHttpClient = mock(HttpClient.class); + var userSettingsClient = + new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); + var mockedResponse = mockedHttpResponse(USER_SETTINGS_EMPTY_JSON, 404); + when(mochedHttpClient.send(any(), any())).thenReturn(mockedResponse); + var searchClient = + new ResourceClient( + HttpClient.newHttpClient(), setupMockedCachedJwtProvider(), userSettingsClient); + + var uri = + URI.create( + "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE, SORT) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + assertNotNull(response); + } + + @Test + void userSettingsFailsIOException() + throws IOException, InterruptedException, BadRequestException { + var mochedHttpClient = mock(HttpClient.class); + var userSettingsClient = + new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); + when(mochedHttpClient.send(any(), any())).thenThrow(new IOException(NOT_FOUND)); + var searchClient = + new ResourceClient( + HttpClient.newHttpClient(), setupMockedCachedJwtProvider(), userSettingsClient); + + var uri = + URI.create( + "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + assertNotNull(response); + } + + @Test + void userSettingsFailswithWrongFormat() + throws IOException, InterruptedException, BadRequestException { + var mochedHttpClient = mock(HttpClient.class); + var userSettingsClient = + new UserSettingsClient(mochedHttpClient, setupMockedCachedJwtProvider()); + when(mochedHttpClient.send(any(), any())) + .thenReturn(mockedHttpResponse(USER_SETTINGS_EMPTY_JSON, 200)); + var searchClient = + new ResourceClient( + HttpClient.newHttpClient(), setupMockedCachedJwtProvider(), userSettingsClient); + + var uri = + URI.create( + "https://x.org/?CONTRIBUTOR=https://api.dev.nva.aws.unit.no/cristin/person/1136254"); + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + assertNotNull(response); + } + + @Test + void emptyResultShouldIncludeHits() throws BadRequestException { + var uri = URI.create("https://x.org/?id=018b857b77b7&from=10"); + + var pagedResult = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .requiredStatus( + NEW, DRAFT, PUBLISHED_METADATA, PUBLISHED, DELETED, UNPUBLISHED, DRAFT_FOR_DELETION) + .apply() + .doSearch(searchClient) + .toString(); + assertNotNull(pagedResult); + assertTrue(pagedResult.contains("\"hits\":[")); + } + + @Test + void searchAfterAndSortByRelevanceException() { + var uri = URI.create("https://x.org/?id=018b857b77b7&from=10&searchAfter=12&sort=relevance"); + assertThrows( + BadRequestException.class, + () -> + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .doSearch(searchClient)); + } + + @ParameterizedTest + @CsvSource({"FYS5960,1", "fys5960,1", "fys596,1", "fys5961,0"}) + void shouldReturnCaseInsensitiveCourses(String searchValue, int expectedHits) + throws BadRequestException { + var uri = URI.create("https://x.org/?course=" + searchValue); + + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .requiredStatus( + NEW, DRAFT, PUBLISHED_METADATA, PUBLISHED, DELETED, UNPUBLISHED, DRAFT_FOR_DELETION) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + assertEquals(expectedHits, pagedSearchResourceDto.totalHits()); + } + + @ParameterizedTest + @MethodSource("roleProvider") + void isSearchingForAllPublicationsAsRoleWork(int expectedHits, List accessRights) + throws UnauthorizedException, BadRequestException { + var requestInfo = mock(RequestInfo.class); + when(requestInfo.getAccessRights()).thenReturn(accessRights); + when(requestInfo.getPersonAffiliation()) + .thenReturn(URI.create("https://api.dev.nva.aws.unit.no/cristin/organization/20754.0.0.0")); + + var response = + ResourceSearchQuery.builder() + .fromRequestInfo(requestInfo) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withParameter(NODES_EXCLUDED, META_INFO) + .withParameter(STATISTICS, Boolean.TRUE.toString()) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .fromRequestInfo(requestInfo) + .doSearch(searchClient); + + assertNotNull(response); + + var pagedSearchResourceDto = response.toPagedResponse(); + assertEquals(expectedHits, pagedSearchResourceDto.totalHits()); + } + + @Test + void isSearchingWithWrongAccessFails() throws UnauthorizedException { + var requestInfo = mock(RequestInfo.class); + when(requestInfo.getAccessRights()).thenReturn(List.of(MANAGE_DEGREE)); + when(requestInfo.getPersonAffiliation()) + .thenReturn(URI.create("https://api.dev.nva.aws.unit.no/cristin/organization/")); + + assertThrows( + UnauthorizedException.class, + () -> + ResourceSearchQuery.builder() + .fromRequestInfo(requestInfo) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withParameter(NODES_EXCLUDED, META_INFO) + .withParameter(STATISTICS, Boolean.TRUE.toString()) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .fromRequestInfo(requestInfo) + .doSearch(searchClient)); + } + + @Test + void withOrganizationDoWork() throws BadRequestException, UnauthorizedException { + var requestInfo = mock(RequestInfo.class); + when(requestInfo.getAccessRights()).thenReturn(List.of(MANAGE_CUSTOMERS)); + when(requestInfo.getTopLevelOrgCristinId()) + .thenReturn( + Optional.of( + URI.create("https://api.dev.nva.aws.unit.no/cristin/organization/184.0.0.0"))); + var response = + ResourceSearchQuery.builder() + .fromRequestInfo(requestInfo) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withParameter(NODES_EXCLUDED, META_INFO) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .fromRequestInfo(requestInfo) + .doSearch(searchClient); + + assertNotNull(response); + + var pagedSearchResourceDto = response.toPagedResponse(); + assertEquals(3, pagedSearchResourceDto.totalHits()); + } + + @Test + void scrollClientExceuteOK() throws BadRequestException { + var includedNodes = String.join(COMMA, ResourceCsvTransformer.getJsonFields()); + var firstResponse = + ResourceSearchQuery.builder() + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withParameter(FROM, ZERO) + .withParameter(SIZE, NUMBER_FIVE) + .withParameter(AGGREGATION, NONE) + .withParameter(NODES_INCLUDED, includedNodes) + .build() + .withFilter() + .requiredStatus(PUBLISHED_METADATA, PUBLISHED) + .apply() + .withScrollTime(ONE_MINUTE) + .doSearch(searchClient) + .swsResponse(); + + var response = + ScrollQuery.builder() + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withInitialResponse(firstResponse) + .withScrollTime(ONE_MINUTE) + .build() + .doSearch(scrollClient) + .toCsvText(); + assertNotNull(response); + } + + @ParameterizedTest + @MethodSource("uriPagingProvider") + void searchWithUriPageableReturnsOpenSearchResponse(URI uri, int expectedCount) + throws ApiGatewayException { + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .validate() + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + assertNotNull(pagedSearchResourceDto); + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); + assertThat( + pagedSearchResourceDto.aggregations().size(), is(equalTo(EXPECTED_NUMBER_OF_AGGREGATIONS))); + logger.debug(pagedSearchResourceDto.id().toString()); + } + + // TODO: Remove duplicate test? + @ParameterizedTest + @MethodSource("uriProvider") + void searchWithUriReturnsOpenSearchAwsResponse(URI uri, int expectedCount) + throws ApiGatewayException { + + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + assertNotNull(pagedSearchResourceDto); + if (expectedCount == 0) { + logger.debug(pagedSearchResourceDto.toJsonString()); + } else { + logger.debug(pagedSearchResourceDto.toString()); } - @Test - void shouldReturnResourcesWithFieldContributorsPreviewAndNotPreview() - throws BadRequestException { - - var response = - ResourceSearchQuery.builder() - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withAlwaysExcludedFields("entityDescription.contributors") - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA, DELETED) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - assertThat( - pagedSearchResourceDto.toJsonString(), containsString("\"contributorsPreview\":")); - assertThat(pagedSearchResourceDto.toJsonString(), not(containsString("\"contributors\":"))); + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); + assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedCount))); + } + + @Test + void shouldFindAnthologyWithChapters() throws ApiGatewayException { + // Given that a parent document exists of type BookAnthology, + // and the parent document has two children of type AcademicChapter, + // when a query filters by hasParts of type AcademicChapter, + // then one document should be returned, + // and the returned document should be of type BookAnthology, + // and the returned document should have the ID of the parent document. + + var uri = + UriWrapper.fromUri("https://x.org/?instanceTypeofChild=AcademicChapter&from=0&size=20") + .getUri(); + var expectedHits = 1; + var expectedParentIdSuffix = "01905518408c-dba987f7-0d84-4519-a625-89605672afc8"; + + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + var document = pagedSearchResourceDto.hits().getFirst(); + var actualId = document.get(IDENTIFIER).asText(); + + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); + assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); + assertThat(actualId, is(equalTo(expectedParentIdSuffix))); + } + + @Test + void shouldFindDocumentsWithParent() throws ApiGatewayException { + // Given that a parent document exists, + // and the parent document has two children, + // when a query filters by HAS_PARENT=true, + // then two child documents should be returned, + // and the parent ID should be found in each child document. + + var uri = UriWrapper.fromUri("https://x.org/?HAS_PARENT=true&from=0&size=20").getUri(); + var expectedHits = 2; + var expectedParentIdSuffix = "0190554a46d9-41780102-675d-43ba-81df-17168d78fa22"; + + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + var document = pagedSearchResourceDto.hits().getFirst(); + var actualId = document.get(IDENTIFIER).asText(); + + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); + assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); + assertThat(actualId, is(equalTo(expectedParentIdSuffix))); + } + + @Test + void shouldFindDocumentsWithChildren() throws ApiGatewayException { + // Given that a parent document with two children exists, + // and the parent document has two children, + // when a query filters by HAS_CHILDREN=true, + // then one document should be returned, + // and the returned document should have the ID of the parent document. + + var uri = UriWrapper.fromUri("https://x.org/?HAS_PARENT=true&from=0&size=20").getUri(); + var expectedHits = 2; + var expectedParentIdSuffix = "01905518408c-dba987f7-0d84-4519-a625-89605672afc8"; + + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + var documents = pagedSearchResourceDto.hits(); + + for (var document : documents) { + var actualParentId = + document.get(ENTITY_DESCRIPTION).get(REFERENCE).get(PUBLICATION_CONTEXT).get(ID).asText(); + + assertThat(actualParentId, containsString(expectedParentIdSuffix)); } - @ParameterizedTest - @MethodSource("provideValidPageRanges") - void shouldFilterByPageCount(int min, int max, int expectedResultCount) - throws BadRequestException { - var pageRange = String.format("%d,%d", min, max); - var response = - ResourceSearchQuery.builder() - .fromTestParameterMap(Map.of(PUBLICATION_PAGES.asCamelCase(), pageRange)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - var pageCounts = - pagedSearchResourceDto.hits().stream() - .map(ResourceClientTest::pageNodeToInt) - .toList(); - - assertThat("Number of hits", pagedSearchResourceDto.hits(), hasSize(expectedResultCount)); - assertThat( - "All page counts are within the specified range", - pageCounts, - everyItem(allOf(greaterThanOrEqualTo(min), lessThanOrEqualTo(max)))); + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); + assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); + } + + @Test + void shouldFindChaptersOfBookAnthology() throws ApiGatewayException { + // Given that a parent document exists of type BookAnthology, + // and the parent document has two children of type AcademicChapter, + // when a query filters by partOf type BookAnthology, + // then two child documents should be returned, + // and the parent ID should be found in each child document. + + var uri = + UriWrapper.fromUri("https://x.org/?instanceTypeOfParent=BookAnthology&from=0&size=20") + .getUri(); + var expectedHits = 2; + var expectedParentIdSuffix = "01905518408c-dba987f7-0d84-4519-a625-89605672afc8"; + + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + var documents = pagedSearchResourceDto.hits(); + + for (var document : documents) { + var actualParentId = + document.get(ENTITY_DESCRIPTION).get(REFERENCE).get(PUBLICATION_CONTEXT).get(ID).asText(); + + var actualInstanceType = + document + .get(ENTITY_DESCRIPTION) + .get(REFERENCE) + .get(PUBLICATION_INSTANCE) + .get(TYPE) + .asText(); + + assertThat(actualParentId, containsString(expectedParentIdSuffix)); + assertThat(actualInstanceType, is(equalTo("AcademicChapter"))); } - @Test - void shouldRemoveDocumentFromIndexWithShards() - throws BadRequestException, IOException, InterruptedException { - var indexDocument = indexDocumentWithIdentifier(); - indexingClient.addDocumentToIndex(indexDocument); - Thread.sleep(1000); - var response = fetchDocumentWithId(indexDocument); - - var pagedSearchResourceDto = response.toPagedResponse(); - - assertThat(pagedSearchResourceDto.hits(), hasSize(1)); - - indexingClient.removeDocumentFromResourcesIndex(indexDocument.getDocumentIdentifier()); - Thread.sleep(1000); - var responseAfterDeletion = fetchDocumentWithId(indexDocument); - - assertThat(responseAfterDeletion.toPagedResponse().hits(), is(emptyIterable())); - } + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedHits))); + assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedHits))); + } + + @ParameterizedTest + @MethodSource("uriProvider") + void searchWithUriReturnsCsvResponse(URI uri) throws ApiGatewayException { + var csvResult = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withAlwaysExcludedFields(GLOBAL_EXCLUDED_FIELDS) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withMediaType(Words.TEXT_CSV) + .build() + .withFilter() + .requiredStatus(PUBLISHED_METADATA) + .apply() + .doSearch(searchClient) + .toString(); + assertNotNull(csvResult); + } + + @ParameterizedTest + @MethodSource("uriSortingProvider") + void searchUriWithSortingReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { + var response = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE, SORT) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + assertNotNull(pagedSearchResourceDto.id()); + var searchName = response.parameters().get(SORT).split(COMMA)[0].split(COLON)[0]; + var searchFieldName = + ResourceSort.fromSortKey(searchName) + .jsonPaths() + .findFirst() + .map(path -> path.contains(KEYWORD) ? trimKeyword(path) : path) + .map(path -> SLASH + path.replace(DOT, SLASH)) + .orElseThrow(); + + var logInfo = + response.swsResponse().hits().hits().stream() + .map(item -> item._score() + " + " + searchFieldName) + .collect(Collectors.joining(SPACE + PIPE + SPACE)); + logger.debug(logInfo); + assertNotNull(pagedSearchResourceDto.context()); + assertTrue(pagedSearchResourceDto.totalHits() >= 0); + } + + private String trimKeyword(String path) { + return path.substring(0, path.indexOf(KEYWORD) - 1); + } + + @ParameterizedTest + @MethodSource("uriInvalidProvider") + void failToSearchUri(URI uri) { + assertThrows( + BadRequestException.class, + () -> + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .doSearch(searchClient)); + } + + @Test + void shouldReturnResourcesForScientificPeriods() throws BadRequestException { + var response = + ResourceSearchQuery.builder() + .fromTestParameterMap( + Map.of( + SCIENTIFIC_REPORT_PERIOD_SINCE.asCamelCase(), + Y2019, + SCIENTIFIC_REPORT_PERIOD_BEFORE.asCamelCase(), + Y2022)) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + assertThat(pagedSearchResourceDto.hits(), hasSize(2)); + } + + @Test + void shouldReturnResourcesForSinglePeriods() throws BadRequestException { + var response = + ResourceSearchQuery.builder() + .fromTestParameterMap( + Map.of( + SCIENTIFIC_REPORT_PERIOD_SINCE.asCamelCase(), + Y2019, + SCIENTIFIC_REPORT_PERIOD_BEFORE.asCamelCase(), + Y2020)) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + var pagedSearchResourceDto = response.toPagedResponse(); + assertThat(pagedSearchResourceDto.hits(), hasSize(1)); + } + + @Test + void + shouldNotReturnResourcesContainingAffiliationThatShouldBeExcludedWhenIsSubunitOfRequestedViewingScopeI() + throws BadRequestException { + var viewingScope = + URLEncoder.encode( + "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.0.0", + StandardCharsets.UTF_8); + var response = + ResourceSearchQuery.builder() + .fromTestParameterMap( + Map.of( + UNIT.asCamelCase(), + viewingScope, + EXCLUDE_SUBUNITS.asCamelCase(), + Boolean.TRUE.toString())) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + var excludedSubunit = "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.1.0"; + + assertThat(pagedSearchResourceDto.toJsonString(), not(containsString(excludedSubunit))); + assertThat(pagedSearchResourceDto.hits(), hasSize(1)); + } + + @Test + void shouldReturnResourcesWithSubunitsWhenExcludedSubunitsNotProvided() + throws BadRequestException { + var unit = + URLEncoder.encode( + "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.0.0", + StandardCharsets.UTF_8); + var topLevelOrg = + URLEncoder.encode( + "https://api.dev.nva.aws.unit.no/cristin/organization/20754.0.0.0", + StandardCharsets.UTF_8); + var response = + ResourceSearchQuery.builder() + .fromTestParameterMap( + Map.of(UNIT.asCamelCase(), unit, TOP_LEVEL_ORGANIZATION, topLevelOrg)) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA, DELETED) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + var includedSubunitI = "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.1.0"; + var includedSubunitII = "https://api.dev.nva.aws.unit.no/cristin/organization/20754.6.1.1"; + + assertThat(pagedSearchResourceDto.toJsonString(), containsString(includedSubunitI)); + assertThat(pagedSearchResourceDto.toJsonString(), containsString(includedSubunitII)); + assertThat(pagedSearchResourceDto.hits(), hasSize(2)); + } + + @Test + void shouldReturnResourcesWithFieldContributorsPreviewAndNotPreview() throws BadRequestException { + + var response = + ResourceSearchQuery.builder() + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withAlwaysExcludedFields("entityDescription.contributors") + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA, DELETED) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + assertThat(pagedSearchResourceDto.toJsonString(), containsString("\"contributorsPreview\":")); + assertThat(pagedSearchResourceDto.toJsonString(), not(containsString("\"contributors\":"))); + } + + @ParameterizedTest + @MethodSource("provideValidPageRanges") + void shouldFilterByPageCount(int min, int max, int expectedResultCount) + throws BadRequestException { + var pageRange = String.format("%d,%d", min, max); + var response = + ResourceSearchQuery.builder() + .fromTestParameterMap(Map.of(PUBLICATION_PAGES.asCamelCase(), pageRange)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + var pageCounts = + pagedSearchResourceDto.hits().stream().map(ResourceClientTest::pageNodeToInt).toList(); + + assertThat("Number of hits", pagedSearchResourceDto.hits(), hasSize(expectedResultCount)); + assertThat( + "All page counts are within the specified range", + pageCounts, + everyItem(allOf(greaterThanOrEqualTo(min), lessThanOrEqualTo(max)))); + } + + @Test + void shouldRemoveDocumentFromIndexWithShards() + throws BadRequestException, IOException, InterruptedException { + var indexDocument = indexDocumentWithIdentifier(); + indexingClient.addDocumentToIndex(indexDocument); + Thread.sleep(1000); + var response = fetchDocumentWithId(indexDocument); + + var pagedSearchResourceDto = response.toPagedResponse(); + + assertThat(pagedSearchResourceDto.hits(), hasSize(1)); + + indexingClient.removeDocumentFromResourcesIndex(indexDocument.getDocumentIdentifier()); + Thread.sleep(1000); + var responseAfterDeletion = fetchDocumentWithId(indexDocument); + + assertThat(responseAfterDeletion.toPagedResponse().hits(), is(emptyIterable())); + } } diff --git a/search-commons/src/test/java/no/unit/nva/search/resource/ResourceSearchQueryTest.java b/search-commons/src/test/java/no/unit/nva/search/resource/ResourceSearchQueryTest.java index 0faa529e0..7534c7f05 100644 --- a/search-commons/src/test/java/no/unit/nva/search/resource/ResourceSearchQueryTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/resource/ResourceSearchQueryTest.java @@ -1,5 +1,6 @@ package no.unit.nva.search.resource; +import static java.util.Objects.nonNull; import static no.unit.nva.common.Containers.container; import static no.unit.nva.common.EntrySetTools.queryToMapEntries; import static no.unit.nva.common.MockedHttpResponse.mockedFutureHttpResponse; @@ -16,7 +17,6 @@ import static no.unit.nva.search.resource.ResourceParameter.SCIENTIFIC_REPORT_PERIOD_SINCE; import static no.unit.nva.search.resource.ResourceParameter.SIZE; import static no.unit.nva.search.resource.ResourceParameter.SORT; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -27,13 +27,16 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static java.util.Objects.nonNull; - +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.search.common.records.PagedSearch; - import nva.commons.apigateway.exceptions.BadRequestException; import nva.commons.core.paths.UriWrapper; - import org.joda.time.DateTime; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -41,14 +44,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.http.HttpClient; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - class ResourceSearchQueryTest { private static final Logger logger = LoggerFactory.getLogger(ResourceSearchQueryTest.class); diff --git a/search-commons/src/test/java/no/unit/nva/search/resource/SimplifiedMutatorTest.java b/search-commons/src/test/java/no/unit/nva/search/resource/SimplifiedMutatorTest.java index 981d8c166..3a31576f1 100644 --- a/search-commons/src/test/java/no/unit/nva/search/resource/SimplifiedMutatorTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/resource/SimplifiedMutatorTest.java @@ -2,9 +2,7 @@ import static no.unit.nva.testutils.RandomDataGenerator.objectMapper; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.core.ioutils.IoUtils.stringFromResources; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; @@ -18,106 +16,103 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; - +import java.nio.file.Path; import no.unit.nva.search.resource.response.ResourceSearchResponse; - import org.junit.jupiter.api.Test; -import java.nio.file.Path; - class SimplifiedMutatorTest { - @Test - void shouldNotThrowOnEmptyBody() { - assertDoesNotThrow( - () -> new SimplifiedMutator().transform(new ObjectMapper().createObjectNode())); - } - - @Test - void shouldOutputIdAndNoEmptyStringsIdIfOnlyIdIsProvidedAsInput() { - var id = randomString(); - var input = new ObjectMapper().createObjectNode(); - input.set("id", new TextNode(id)); - var result = new SimplifiedMutator().transform(input); - assertTrue(result.isObject()); - ObjectNode resultAsObject = (ObjectNode) result; - resultAsObject - .iterator() - .forEachRemaining( - jsonNode -> { - if (jsonNode.isTextual()) { - assertFalse(jsonNode.textValue().isEmpty()); - } - }); - assertFalse(input.path("id").isMissingNode()); - } - - @Test - void shouldKeepAllContributorsOMutating() throws JsonProcessingException { - var json = - new ObjectMapper() - .readTree(stringFromResources(Path.of("resource_datasource.json"))) - .get(0); - - var contributorsInSampleJson = 1; - - var mutated = new SimplifiedMutator().transform(json); - var asDto = objectMapper.treeToValue(mutated, ResourceSearchResponse.class); - assertThat(asDto.contributorsPreview().size(), is(equalTo(contributorsInSampleJson))); - assertThat(asDto.contributorsCount(), is(equalTo(contributorsInSampleJson))); - } - - @Test - void shouldMapAlternativeTitles() throws JsonProcessingException { - var language = randomString(); - var expectedAlternativeTitle = randomString(); - var input = new ObjectMapper().createObjectNode(); - var entityDescription = new ObjectMapper().createObjectNode(); - var alternativeTitles = new ObjectMapper().createObjectNode(); - alternativeTitles.put(language, expectedAlternativeTitle); - entityDescription.set("alternativeTitles", alternativeTitles); - input.set("entityDescription", entityDescription); - - var mutated = new SimplifiedMutator().transform(input); - var asDto = objectMapper.treeToValue(mutated, ResourceSearchResponse.class); - assertThat(asDto.alternativeTitles().size(), is(equalTo(1))); - assertThat(asDto.alternativeTitles().get(language), is(equalTo(expectedAlternativeTitle))); - } - - @Test - void shouldMapIsbnsForArtistics() throws JsonProcessingException { - var isbn1 = randomString(); - var isbn2 = randomString(); - var input = new ObjectMapper().createObjectNode(); - var entityDescription = new ObjectMapper().createObjectNode(); - var reference = new ObjectMapper().createObjectNode(); - var publicationInstance = new ObjectMapper().createObjectNode(); - var manifistations = new ObjectMapper().createArrayNode(); - var manifistation = new ObjectMapper().createObjectNode(); - var isbnList = new ObjectMapper().createArrayNode(); - isbnList.add(new TextNode(isbn1)); - isbnList.add(new TextNode(isbn2)); - manifistation.set("isbnList", isbnList); - manifistations.add(manifistation); - publicationInstance.set("manifestations", manifistations); - reference.set("publicationInstance", publicationInstance); - entityDescription.set("reference", reference); - input.set("entityDescription", entityDescription); - - var mutated = new SimplifiedMutator().transform(input); - var asDto = objectMapper.treeToValue(mutated, ResourceSearchResponse.class); - - assertThat(asDto.otherIdentifiers().isbn(), hasItem(isbn1)); - assertThat(asDto.otherIdentifiers().isbn(), hasItem(isbn2)); - } - - @Test - void shouldParseSampleFileWithNoExceptions() throws JsonProcessingException { - var json = - new ObjectMapper() - .readTree(stringFromResources(Path.of("resource_datasource.json"))) - .get(0); - var result = new SimplifiedMutator().transform(json); - assertNotNull(result); - } + @Test + void shouldNotThrowOnEmptyBody() { + assertDoesNotThrow( + () -> new SimplifiedMutator().transform(new ObjectMapper().createObjectNode())); + } + + @Test + void shouldOutputIdAndNoEmptyStringsIdIfOnlyIdIsProvidedAsInput() { + var id = randomString(); + var input = new ObjectMapper().createObjectNode(); + input.set("id", new TextNode(id)); + var result = new SimplifiedMutator().transform(input); + assertTrue(result.isObject()); + ObjectNode resultAsObject = (ObjectNode) result; + resultAsObject + .iterator() + .forEachRemaining( + jsonNode -> { + if (jsonNode.isTextual()) { + assertFalse(jsonNode.textValue().isEmpty()); + } + }); + assertFalse(input.path("id").isMissingNode()); + } + + @Test + void shouldKeepAllContributorsOMutating() throws JsonProcessingException { + var json = + new ObjectMapper() + .readTree(stringFromResources(Path.of("resource_datasource.json"))) + .get(0); + + var contributorsInSampleJson = 1; + + var mutated = new SimplifiedMutator().transform(json); + var asDto = objectMapper.treeToValue(mutated, ResourceSearchResponse.class); + assertThat(asDto.contributorsPreview().size(), is(equalTo(contributorsInSampleJson))); + assertThat(asDto.contributorsCount(), is(equalTo(contributorsInSampleJson))); + } + + @Test + void shouldMapAlternativeTitles() throws JsonProcessingException { + var language = randomString(); + var expectedAlternativeTitle = randomString(); + var input = new ObjectMapper().createObjectNode(); + var entityDescription = new ObjectMapper().createObjectNode(); + var alternativeTitles = new ObjectMapper().createObjectNode(); + alternativeTitles.put(language, expectedAlternativeTitle); + entityDescription.set("alternativeTitles", alternativeTitles); + input.set("entityDescription", entityDescription); + + var mutated = new SimplifiedMutator().transform(input); + var asDto = objectMapper.treeToValue(mutated, ResourceSearchResponse.class); + assertThat(asDto.alternativeTitles().size(), is(equalTo(1))); + assertThat(asDto.alternativeTitles().get(language), is(equalTo(expectedAlternativeTitle))); + } + + @Test + void shouldMapIsbnsForArtistics() throws JsonProcessingException { + var isbn1 = randomString(); + var isbn2 = randomString(); + var input = new ObjectMapper().createObjectNode(); + var entityDescription = new ObjectMapper().createObjectNode(); + var reference = new ObjectMapper().createObjectNode(); + var publicationInstance = new ObjectMapper().createObjectNode(); + var manifistations = new ObjectMapper().createArrayNode(); + var manifistation = new ObjectMapper().createObjectNode(); + var isbnList = new ObjectMapper().createArrayNode(); + isbnList.add(new TextNode(isbn1)); + isbnList.add(new TextNode(isbn2)); + manifistation.set("isbnList", isbnList); + manifistations.add(manifistation); + publicationInstance.set("manifestations", manifistations); + reference.set("publicationInstance", publicationInstance); + entityDescription.set("reference", reference); + input.set("entityDescription", entityDescription); + + var mutated = new SimplifiedMutator().transform(input); + var asDto = objectMapper.treeToValue(mutated, ResourceSearchResponse.class); + + assertThat(asDto.otherIdentifiers().isbn(), hasItem(isbn1)); + assertThat(asDto.otherIdentifiers().isbn(), hasItem(isbn2)); + } + + @Test + void shouldParseSampleFileWithNoExceptions() throws JsonProcessingException { + var json = + new ObjectMapper() + .readTree(stringFromResources(Path.of("resource_datasource.json"))) + .get(0); + var result = new SimplifiedMutator().transform(json); + assertNotNull(result); + } } diff --git a/search-commons/src/test/java/no/unit/nva/search/resource/UserSettingsClientTest.java b/search-commons/src/test/java/no/unit/nva/search/resource/UserSettingsClientTest.java index b11bb7d87..27dd9d241 100644 --- a/search-commons/src/test/java/no/unit/nva/search/resource/UserSettingsClientTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/resource/UserSettingsClientTest.java @@ -35,59 +35,59 @@ class UserSettingsClientTest { - public static final String SAMPLE_USER_SETTINGS_RESPONSE = "user_settings.json"; - private static final Logger logger = LoggerFactory.getLogger(UserSettingsClientTest.class); - private static UserSettingsClient userSettingsClient; + public static final String SAMPLE_USER_SETTINGS_RESPONSE = "user_settings.json"; + private static final Logger logger = LoggerFactory.getLogger(UserSettingsClientTest.class); + private static UserSettingsClient userSettingsClient; - @BeforeAll - public static void setUp() { - var mochedHttpClient = mock(HttpClient.class); - var cachedJwtProvider = setupMockedCachedJwtProvider(); - userSettingsClient = new UserSettingsClient(mochedHttpClient, cachedJwtProvider); - final var path = Path.of(SAMPLE_USER_SETTINGS_RESPONSE); + @BeforeAll + public static void setUp() { + var mochedHttpClient = mock(HttpClient.class); + var cachedJwtProvider = setupMockedCachedJwtProvider(); + userSettingsClient = new UserSettingsClient(mochedHttpClient, cachedJwtProvider); + final var path = Path.of(SAMPLE_USER_SETTINGS_RESPONSE); - when(mochedHttpClient.sendAsync(any(), any())) - .thenReturn(mockedFutureHttpResponse(EMPTY_STRING)) - .thenReturn(mockedFutureFailed()) - .thenReturn(mockedFutureHttpResponse(path)) - .thenReturn(mockedFutureHttpResponse(path)); - } + when(mochedHttpClient.sendAsync(any(), any())) + .thenReturn(mockedFutureHttpResponse(EMPTY_STRING)) + .thenReturn(mockedFutureFailed()) + .thenReturn(mockedFutureHttpResponse(path)) + .thenReturn(mockedFutureHttpResponse(path)); + } - static Stream uriProvider() { - return Stream.of( - URI.create( - "https://example.com/?contributor=http://hello.worl.test.orgd&modified_before=2019-01-01"), - URI.create( - "https://example.com/?contributor=https://api.dev.nva.aws.unit.no/cristin/person/1269057"), - URI.create( - "https://example.com/?contributor=https%3A%2F%2Fapi.dev.nva.aws.unit" - + ".no%2Fcristin%2Fperson%2F1269057&orderBy=UNIT_ID:asc,title:desc"), - URI.create( - "https://example.com/?contributor=https://api.dev.nva.aws.unit.no/cristin/person/1269051")); - } + static Stream uriProvider() { + return Stream.of( + URI.create( + "https://example.com/?contributor=http://hello.worl.test.orgd&modified_before=2019-01-01"), + URI.create( + "https://example.com/?contributor=https://api.dev.nva.aws.unit.no/cristin/person/1269057"), + URI.create( + "https://example.com/?contributor=https%3A%2F%2Fapi.dev.nva.aws.unit" + + ".no%2Fcristin%2Fperson%2F1269057&orderBy=UNIT_ID:asc,title:desc"), + URI.create( + "https://example.com/?contributor=https://api.dev.nva.aws.unit.no/cristin/person/1269051")); + } - @ParameterizedTest - @MethodSource("uriProvider") - void searchWithUriReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { - var resourceAwsQuery = - ResourceSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .build(); - var promotedPublications = - attempt(() -> userSettingsClient.doSearch(resourceAwsQuery)) - .map(UserSettings::promotedPublications) - .orElse(logExceptionAndContinue()); - assertNotNull(promotedPublications); - } + @ParameterizedTest + @MethodSource("uriProvider") + void searchWithUriReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayException { + var resourceAwsQuery = + ResourceSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .build(); + var promotedPublications = + attempt(() -> userSettingsClient.doSearch(resourceAwsQuery)) + .map(UserSettings::promotedPublications) + .orElse(logExceptionAndContinue()); + assertNotNull(promotedPublications); + } - private FunctionWithException>, List, RuntimeException> - logExceptionAndContinue() { - return (e) -> { - if (e.isFailure()) { - logger.error(e.getException().getMessage()); - } - return List.of(); - }; - } + private FunctionWithException>, List, RuntimeException> + logExceptionAndContinue() { + return (e) -> { + if (e.isFailure()) { + logger.error(e.getException().getMessage()); + } + return List.of(); + }; + } } diff --git a/search-commons/src/test/java/no/unit/nva/search/scroll/ScrollClientTest.java b/search-commons/src/test/java/no/unit/nva/search/scroll/ScrollClientTest.java index 3f7a5b8b9..564274911 100644 --- a/search-commons/src/test/java/no/unit/nva/search/scroll/ScrollClientTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/scroll/ScrollClientTest.java @@ -3,20 +3,18 @@ import static no.unit.nva.common.MockedHttpResponse.mockedFutureHttpResponse; import static no.unit.nva.indexing.testutils.MockedJwtProvider.setupMockedCachedJwtProvider; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.net.http.HttpClient; +import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.http.HttpClient; -import java.nio.file.Path; - class ScrollClientTest { public static final String SAMPLE_PUBLICATION_SEARCH = diff --git a/search-commons/src/test/java/no/unit/nva/search/ticket/TicketClientTest.java b/search-commons/src/test/java/no/unit/nva/search/ticket/TicketClientTest.java index 4062b9329..74953296c 100644 --- a/search-commons/src/test/java/no/unit/nva/search/ticket/TicketClientTest.java +++ b/search-commons/src/test/java/no/unit/nva/search/ticket/TicketClientTest.java @@ -1,5 +1,6 @@ package no.unit.nva.search.ticket; +import static java.util.Objects.nonNull; import static no.unit.nva.auth.uriretriever.UriRetriever.ACCEPT; import static no.unit.nva.common.Containers.container; import static no.unit.nva.common.Containers.indexingClient; @@ -20,7 +21,6 @@ import static no.unit.nva.search.ticket.TicketParameter.SORT; import static no.unit.nva.search.ticket.TicketParameter.STATISTICS; import static no.unit.nva.testutils.RandomDataGenerator.randomString; - import static nva.commons.apigateway.AccessRight.MANAGE_CUSTOMERS; import static nva.commons.apigateway.AccessRight.MANAGE_DOI; import static nva.commons.apigateway.AccessRight.MANAGE_PUBLISHING_REQUESTS; @@ -28,7 +28,6 @@ import static nva.commons.core.attempt.Try.attempt; import static nva.commons.core.ioutils.IoUtils.stringFromResources; import static nva.commons.core.paths.UriWrapper.fromUri; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -42,20 +41,25 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static java.util.Objects.nonNull; - import com.fasterxml.jackson.core.type.TypeReference; - +import java.net.URI; +import java.net.http.HttpClient; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; import no.unit.nva.commons.json.JsonUtils; import no.unit.nva.constants.Words; import no.unit.nva.search.common.records.Facet; - import nva.commons.apigateway.AccessRight; import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.ApiGatewayException; import nva.commons.apigateway.exceptions.BadRequestException; import nva.commons.apigateway.exceptions.UnauthorizedException; - import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -66,644 +70,616 @@ import org.slf4j.LoggerFactory; import org.testcontainers.junit.jupiter.Testcontainers; -import java.net.URI; -import java.net.http.HttpClient; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; -import java.util.stream.Stream; - @Testcontainers class TicketClientTest { - public static final String REQUEST_BASE_URL = "https://x.org/?size=22&aggregation=all&"; - public static final int EXPECTED_NUMBER_OF_AGGREGATIONS = 4; - public static final URI testOrganizationId = - URI.create("https://api.dev.nva.aws.unit.no/cristin/organization/20754.0.0.0"); - public static final String AnetteOlli = "1412322@20754.0.0.0"; - public static final String Kir = "1492596@20754.0.0.0"; - public static final String USER_03 = "1485369@5923.0.0.0"; - public static final String TRUE = "true"; - public static final String CURRENT_USERNAME = AnetteOlli; - public static final String USER_04 = "34322@20754.0.0.0"; - public static final String USER_05 = "1485369@20754.0.0.0"; - private static final Logger logger = LoggerFactory.getLogger(TicketClientTest.class); - private static final String TICKETS_VALID_TEST_URL_JSON = "ticket_datasource_urls.json"; - private static final RequestInfo mockedRequestInfo = mock(RequestInfo.class); - private static TicketClient searchClient; - - @BeforeAll - public static void setUp() throws UnauthorizedException { - var cachedJwtProvider = setupMockedCachedJwtProvider(); - searchClient = new TicketClient(HttpClient.newHttpClient(), cachedJwtProvider); - - when(mockedRequestInfo.getTopLevelOrgCristinId()) - .thenReturn(Optional.of(testOrganizationId)); - when(mockedRequestInfo.getUserName()).thenReturn(CURRENT_USERNAME); - when(mockedRequestInfo.getHeaders()).thenReturn(Map.of(ACCEPT, Words.TEXT_CSV)); - } - - static Stream uriPagingProvider() { - return Stream.of( - createArgument("page=0&aggregation=all,the,best,", 20), - createArgument("page=1&aggregation=all&size=1", 1), - createArgument("page=2&aggregation=all&size=1", 1), - createArgument("page=3&aggregation=all&size=1", 1), - createArgument("page=0&aggregation=all&size=0", 0), - createArgument("offset=15&aggregation=all&size=2", 2), - createArgument("offset=15&aggregation=all&limit=2", 2), - createArgument("offset=15&aggregation=all&results=2", 2), - createArgument("offset=15&aggregation=all&per_page=2", 2), - createArgument("OFFSET=15&aggregation=all&PER_PAGE=2", 2), - createArgument("offset=15&perPage=2", 2)); - } - - private static Arguments createArgument(String searchUri, int expectedCount) { - return Arguments.of(URI.create(REQUEST_BASE_URL + searchUri), expectedCount); - } - - static Stream uriAccessRights() { - final AccessRight[] accessRights = {MANAGE_DOI, SUPPORT, MANAGE_PUBLISHING_REQUESTS}; - final var statistics = STATISTICS.asCamelCase(); - var uri = fromUri(REQUEST_BASE_URL).getUri(); - return Stream.of( - accessRightArg(uriWithParam(uri, Words.OWNER, AnetteOlli), 0, 7, AnetteOlli), - accessRightArg(uriWithParam(uri, Words.OWNER, Kir), 0, 9, Kir), - accessRightArg(uriWithParam(uri, statistics, TRUE), 0, 22, Kir, MANAGE_CUSTOMERS), - accessRightArg(uri, 0, 10, Kir, MANAGE_DOI), - accessRightArg(uri, 0, 14, Kir, SUPPORT), - accessRightArg(uri, 0, 14, Kir, MANAGE_PUBLISHING_REQUESTS), - accessRightArg(uri, 0, 1, USER_03, MANAGE_DOI), - accessRightArg(uri, 0, 6, USER_03, SUPPORT), - accessRightArg(uri, 0, 13, USER_03, MANAGE_PUBLISHING_REQUESTS), - accessRightArg(uri, 0, 7, USER_03, MANAGE_DOI, SUPPORT), - accessRightArg(uri, 0, 20, AnetteOlli, accessRights), - accessRightArg(uri, 0, 20, USER_03, accessRights)); - } - - private static URI uriWithParam(URI uri, String key, String value) { - return fromUri(uri).addQueryParameter(key, value).getUri(); - } - - static Arguments accessRightArg( - URI searchUri, - int pending, - int expectedCount, - String userName, - AccessRight... accessRights) { - return Arguments.of(searchUri, pending, expectedCount, userName, accessRights); - } - - static Stream uriProviderWithAggregationsAndAccessRights() { - final var url = URI.create("https://x.org/?size=0&aggregation=all"); - final var url2 = URI.create("https://x.org/?size=0&aggregation=all&STATISTICS=true"); - final var pending0 = 0; - final var pending1 = 1; - final AccessRight[] accessRights = {MANAGE_DOI, SUPPORT, MANAGE_PUBLISHING_REQUESTS}; - return Stream.of( - accessRightArg(url, pending0, 7, AnetteOlli), - accessRightArg(url, pending0, 7, AnetteOlli, MANAGE_DOI), - accessRightArg(url, pending0, 8, AnetteOlli, SUPPORT), - accessRightArg(url, pending1, 19, AnetteOlli, MANAGE_PUBLISHING_REQUESTS), - accessRightArg(url, pending0, 9, Kir), - accessRightArg(url, pending1, 10, Kir, MANAGE_DOI), - accessRightArg(url, pending0, 14, Kir, SUPPORT), - accessRightArg(url, pending0, 14, Kir, MANAGE_PUBLISHING_REQUESTS), - accessRightArg(url2, pending0, 22, USER_03, MANAGE_CUSTOMERS), - accessRightArg(url, pending0, 0, USER_03), - accessRightArg(url, pending0, 20, USER_03, accessRights), - accessRightArg(url, pending0, 4, USER_04), - accessRightArg(url, pending0, 20, USER_04, accessRights), - accessRightArg(url, pending0, 0, USER_05), - accessRightArg(url, pending0, 20, USER_05, accessRights)); - } - - static Stream uriInvalidProvider() { - return Stream.of( - URI.create(REQUEST_BASE_URL + "feilName=epler"), - URI.create(REQUEST_BASE_URL + "query=epler&fields=feilName"), - URI.create(REQUEST_BASE_URL + "CREATED_DATE=epler"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORY:DEdd"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORdfgY:desc"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORY"), - URI.create(REQUEST_BASE_URL + "sort=CATEGORY:asc:DEdd"), - URI.create(REQUEST_BASE_URL + "categories=hello+world&lang=en"), - URI.create(REQUEST_BASE_URL + "tittles=hello+world&modified_before=2019-01-01"), - URI.create(REQUEST_BASE_URL + "useers=hello+world&lang=en")); - } - - static Stream uriProviderAsAdmin() { - return loadMapFromResource(TICKETS_VALID_TEST_URL_JSON).entrySet().stream() - .map(entry -> createArgument(entry.getKey(), entry.getValue())); - } - - private static Map loadMapFromResource(String resource) { - var mappingsJson = stringFromResources(Path.of(resource)); - var type = new TypeReference>() {}; - return attempt(() -> JsonUtils.dtoObjectMapper.readValue(mappingsJson, type)).orElseThrow(); - } - - static Stream uriSortingProvider() { - - return Stream.of( - URI.create( - REQUEST_BASE_URL - + "sort=status&sortOrder=asc&sort=created_date&order=desc"), - URI.create(REQUEST_BASE_URL + "orderBy=status:asc,created_date:desc"), - URI.create(REQUEST_BASE_URL + "sort=status+asc&sort=created_date+desc"), - URI.create( - REQUEST_BASE_URL - + "sort=created_date&sortOrder=asc&sort=status&order=desc"), - URI.create(REQUEST_BASE_URL + "sort=modified_date+asc&sort=type+desc"), - URI.create(REQUEST_BASE_URL + "sort=relevance,modified_date+asc")); - } - - @Test - void shouldCheckMapping() { - - var mapping = indexingClient.getMapping(TICKETS); - assertThat(mapping, is(notNullValue())); - logger.info(mapping.toString()); - } - - @Test - void openSearchFailedResponse() { - HttpClient httpClient = mock(HttpClient.class); - when(httpClient.sendAsync(any(), any())) - .thenReturn(mockedFutureHttpResponse((String) null)); - var toMapEntries = queryToMapEntries(URI.create("https://example.com/?size=2")); - var resourceClient2 = new TicketClient(httpClient, setupMockedCachedJwtProvider()); - assertThrows( - RuntimeException.class, - () -> - TicketSearchQuery.builder() - .withRequiredParameters(SIZE, FROM) - .fromTestQueryParameters(toMapEntries) - .build() - .doSearch(resourceClient2)); - } - - @Test - void shouldCheckFacets() throws BadRequestException, UnauthorizedException { - var hostAddress = URI.create(container.getHttpHostAddress()); - var uri1 = URI.create(REQUEST_BASE_URL + AGGREGATION.name() + EQUAL + ALL); - var response1 = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri1)) - .withDockerHostUri(hostAddress) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .organization(testOrganizationId) - .user(CURRENT_USERNAME) - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .apply() - .doSearch(searchClient); - - assertNotNull(response1); - - var aggregations = response1.toPagedResponse().aggregations(); - - assertFalse(aggregations.isEmpty()); - assertThat(aggregations.size(), is(equalTo(EXPECTED_NUMBER_OF_AGGREGATIONS))); - - assertThat(aggregations.get(TYPE).size(), is(3)); - assertThat(aggregations.get(STATUS).getFirst().count(), is(11)); - assertThat(aggregations.get(BY_USER_PENDING.asCamelCase()).size(), is(1)); - assertThat(aggregations.get(PUBLICATION_STATUS).size(), is(3)); - - assertNotNull(FROM.asLowerCase()); - assertEquals(TicketStatus.NONE, TicketStatus.fromString("ewrdfg")); - assertEquals(TicketType.NONE, TicketType.fromString("wre")); - } - - @Test - void emptyResultShouldIncludeHits() throws BadRequestException, UnauthorizedException { - var uri = URI.create("https://x.org/?id=018b857b77b7"); - - var pagedResult = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE, SORT) - .build() - .withFilter() - .user(CURRENT_USERNAME) - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient); - assertNotNull(pagedResult.swsResponse()); - assertTrue(pagedResult.toString().contains("\"hits\":[")); - } - - @Test - void shouldReturnNewTicketsWhenSearchingForNewTicketsOnlyAndTypes() - throws BadRequestException, UnauthorizedException { - var uri = - URI.create( - "https://x.org/?status=New&size=500&type=doiRequest,generalSupportCase,publishingRequest&from=0"); - - var pagedResult = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .user(CURRENT_USERNAME) - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient) - .toPagedResponse(); - - assertEquals(7, pagedResult.hits().size()); - } - - @Test - void shouldReturnNewTicketsWhenSearchingForNewTicketsOnly() - throws BadRequestException, UnauthorizedException { - var uri = URI.create("https://x.org/?status=New&size=500&from=0"); - - var pagedResult = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .user(CURRENT_USERNAME) - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient) - .toPagedResponse(); - - assertEquals(7, pagedResult.hits().size()); - } - - @Test - void shouldReturnWhenSearchingForNewTicketsOnly() - throws BadRequestException, UnauthorizedException { - var uri = URI.create("https://x.org/?size=500&from=0&OWNER=1492596@20754.0.0.0"); - - var pagedResult = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .user("1492596@20754.0.0.0") - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient) - .toPagedResponse(); - - assertEquals(9, pagedResult.hits().size()); - } - - @Test - void shouldReturnWhenSearchingForAssignee() throws BadRequestException, UnauthorizedException { - var uri = URI.create("https://x.org/?size=500&from=0&ASSIGNEE=1492596@20754.0.0.0"); - - var pagedResult = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .user("1492596@20754.0.0.0") - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient) - .toPagedResponse(); - - assertEquals(1, pagedResult.hits().size()); - } - - @Test - void - shouldReturnNewAndPendingTicketsWithAssigneeWhenSearchingForTicketsWithStatusNewAndPendingAndAssignee() - throws BadRequestException, UnauthorizedException { - var uri = - URI.create( - "https://x.org/?status=New,Pending&assignee=1412322@20754.0.0.0&size=20&from=0"); - - var pagedResult = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .user(CURRENT_USERNAME) - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient) - .toPagedResponse(); - - assertEquals(8, pagedResult.hits().size()); - } - - @ParameterizedTest - @MethodSource("uriPagingProvider") - void uriRequestPageableReturnsSuccessfulResponse(URI uri, int expectedCount) - throws ApiGatewayException { - - var response = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .user(CURRENT_USERNAME) - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - assertNotNull(pagedSearchResourceDto); - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); - logger.debug(pagedSearchResourceDto.id().toString()); - } - - @ParameterizedTest - @MethodSource("uriProviderAsAdmin") - void uriRequestReturnsSuccessfulResponseAsAdmin(URI uri, int expectedCount) - throws ApiGatewayException { - - var response = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .user(CURRENT_USERNAME) - .accessRights( - MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT, MANAGE_CUSTOMERS) - .organization(testOrganizationId) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - assertNotNull(pagedSearchResourceDto); - if (expectedCount == 0) { - logger.debug(pagedSearchResourceDto.toJsonString()); - } else { - logger.debug(pagedSearchResourceDto.toString()); - } - - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); - assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedCount))); - } - - @ParameterizedTest - @MethodSource("uriAccessRights") - void uriRequestReturnsSuccessfulResponseAsUser( - URI uri, - Integer ignoredValue, - Integer expectedCount, - String userName, - AccessRight... accessRights) - throws ApiGatewayException { - - var accessRightList = - new java.util.ArrayList<>( - nonNull(accessRights) ? Arrays.asList(accessRights) : List.of()); - - var mockedRequestInfoLocal = mock(RequestInfo.class); - when(mockedRequestInfoLocal.getUserName()).thenReturn(userName); - when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) - .thenReturn(Optional.of(testOrganizationId)); - when(mockedRequestInfoLocal.getAccessRights()).thenReturn(accessRightList); - - var response = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfoLocal) - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - - assertNotNull(pagedSearchResourceDto); - assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); - assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedCount))); - } - - @ParameterizedTest() - @MethodSource("uriProviderWithAggregationsAndAccessRights") - void uriRequestReturnsSuccessfulResponseWithAggregations( - URI uri, - Integer pending, - Integer expectedCount, - String userName, - AccessRight... accessRights) - throws ApiGatewayException { - var response = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .organization(testOrganizationId) - .user(userName) - .accessRights(accessRights) - .apply() - .doSearch(searchClient); - - var aggregations = response.toPagedResponse().aggregations(); - assertNotNull(aggregations); - - var actualPending = - aggregations.get(Constants.BY_USER_PENDING).stream().mapToInt(Facet::count).sum(); - var actualCount = aggregations.get(TYPE).stream().mapToInt(Facet::count).sum(); - assertThat(actualPending, is(equalTo(pending))); - assertThat(actualCount, is(equalTo(expectedCount))); - } - - @Test - void shouldThrowUnauthorizedWhenRequestingTicketsForOwnerWhichIsNotCurrentCustomer() - throws UnauthorizedException { - var currentUserUsername = randomString(); - var ownerToSearch = randomString(); - - var mockedRequestInfoLocal = mock(RequestInfo.class); - when(mockedRequestInfoLocal.getUserName()).thenReturn(currentUserUsername); - when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) - .thenReturn(Optional.of(testOrganizationId)); - - assertThrows( - UnauthorizedException.class, - () -> - TicketSearchQuery.builder() - .withParameter(OWNER, ownerToSearch) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfoLocal) - .doSearch(searchClient)); - } - - @ParameterizedTest - @MethodSource("uriProviderAsAdmin") - @Disabled( - "Does not work. When test was written it returned an empty string even if there were" - + " supposed to be hits. Now we throw an exception instead as the method is not" - + " implemented.") - void uriRequestReturnsCsvResponse(URI uri) throws ApiGatewayException { - var query = - queryToMapEntries(uri).stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - when(mockedRequestInfo.getQueryParameters()).thenReturn(query); - - var csvResult = - TicketSearchQuery.builder() - .fromRequestInfo(mockedRequestInfo) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(FROM, SIZE) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfo) - .doSearch(searchClient); - assertNotNull(csvResult); - } - - @ParameterizedTest - @MethodSource("uriSortingProvider") - void uriRequestWithSortingReturnsSuccessfulResponse(URI uri) throws ApiGatewayException { - var response = - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE, SORT, AGGREGATION) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .organization(testOrganizationId) - .user(CURRENT_USERNAME) - .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) - .apply() - .doSearch(searchClient); - - var pagedSearchResourceDto = response.toPagedResponse(); - assertNotNull(pagedSearchResourceDto.id()); - assertNotNull(pagedSearchResourceDto.context()); - assertTrue(pagedSearchResourceDto.totalHits() >= 0); - } - - @ParameterizedTest - @MethodSource("uriInvalidProvider") - void uriRequestReturnsBadRequest(URI uri) { - assertThrows( - BadRequestException.class, - () -> - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri)) - .withRequiredParameters(FROM, SIZE) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .doSearch(searchClient)); - } - - @Test - void ownerEqualAssigneeReturnsUnauthorized() throws UnauthorizedException { - var uriBad = URI.create(REQUEST_BASE_URL + "owner=Kir+Truhacev&assignee=Kir+Truhacev"); - // var uriBad = URI.create(REQUEST_BASE_URL + - // "owner=Kir+Truhacev&assignee=Annette+Olli"); - - var mockedRequestInfoLocal = mock(RequestInfo.class); - when(mockedRequestInfoLocal.getUserName()).thenReturn("Kir Truhacev"); - when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) - .thenReturn(Optional.of(testOrganizationId)); - when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of(MANAGE_DOI)); - assertThrows( - UnauthorizedException.class, - () -> - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uriBad)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(SIZE, FROM) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfoLocal) - .doSearch(searchClient)); - } - - @Test - void notSiktAdminReturnsUnauthorized() throws UnauthorizedException { - var uriBad = URI.create(REQUEST_BASE_URL + "STATISTICS=true"); - - var mockedRequestInfoLocal = mock(RequestInfo.class); - when(mockedRequestInfoLocal.getUserName()).thenReturn(randomString()); - when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) - .thenReturn(Optional.of(testOrganizationId)); - when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of(MANAGE_DOI)); - assertThrows( - UnauthorizedException.class, - () -> - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uriBad)) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .withRequiredParameters(SIZE, FROM) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfoLocal) - .doSearch(searchClient)); - } - - @Test - void uriRequestReturnsUnauthorized() throws UnauthorizedException { - AtomicReference uri = new AtomicReference<>(); - uriSortingProvider().findFirst().ifPresent(uri::set); - var mockedRequestInfoLocal = mock(RequestInfo.class); - when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of()); - when(mockedRequestInfoLocal.getCurrentCustomer()).thenReturn(null); - assertThrows( - UnauthorizedException.class, - () -> - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri.get())) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfoLocal) - .doSearch(searchClient)); - } - - @Test - void missingUserNameReturnsUnauthorized() throws UnauthorizedException { - AtomicReference uri = new AtomicReference<>(); - uriSortingProvider().findFirst().ifPresent(uri::set); - var mockedRequestInfoLocal = mock(RequestInfo.class); - when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of()); - when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) - .thenReturn(Optional.of(testOrganizationId)); - when(mockedRequestInfoLocal.getCurrentCustomer()).thenReturn(null); - assertThrows( - UnauthorizedException.class, - () -> - TicketSearchQuery.builder() - .fromTestQueryParameters(queryToMapEntries(uri.get())) - .withDockerHostUri(URI.create(container.getHttpHostAddress())) - .build() - .withFilter() - .fromRequestInfo(mockedRequestInfoLocal) - .doSearch(searchClient)); - } + public static final String REQUEST_BASE_URL = "https://x.org/?size=22&aggregation=all&"; + public static final int EXPECTED_NUMBER_OF_AGGREGATIONS = 4; + public static final URI testOrganizationId = + URI.create("https://api.dev.nva.aws.unit.no/cristin/organization/20754.0.0.0"); + public static final String AnetteOlli = "1412322@20754.0.0.0"; + public static final String Kir = "1492596@20754.0.0.0"; + public static final String USER_03 = "1485369@5923.0.0.0"; + public static final String TRUE = "true"; + public static final String CURRENT_USERNAME = AnetteOlli; + public static final String USER_04 = "34322@20754.0.0.0"; + public static final String USER_05 = "1485369@20754.0.0.0"; + private static final Logger logger = LoggerFactory.getLogger(TicketClientTest.class); + private static final String TICKETS_VALID_TEST_URL_JSON = "ticket_datasource_urls.json"; + private static final RequestInfo mockedRequestInfo = mock(RequestInfo.class); + private static TicketClient searchClient; + + @BeforeAll + public static void setUp() throws UnauthorizedException { + var cachedJwtProvider = setupMockedCachedJwtProvider(); + searchClient = new TicketClient(HttpClient.newHttpClient(), cachedJwtProvider); + + when(mockedRequestInfo.getTopLevelOrgCristinId()).thenReturn(Optional.of(testOrganizationId)); + when(mockedRequestInfo.getUserName()).thenReturn(CURRENT_USERNAME); + when(mockedRequestInfo.getHeaders()).thenReturn(Map.of(ACCEPT, Words.TEXT_CSV)); + } + + static Stream uriPagingProvider() { + return Stream.of( + createArgument("page=0&aggregation=all,the,best,", 20), + createArgument("page=1&aggregation=all&size=1", 1), + createArgument("page=2&aggregation=all&size=1", 1), + createArgument("page=3&aggregation=all&size=1", 1), + createArgument("page=0&aggregation=all&size=0", 0), + createArgument("offset=15&aggregation=all&size=2", 2), + createArgument("offset=15&aggregation=all&limit=2", 2), + createArgument("offset=15&aggregation=all&results=2", 2), + createArgument("offset=15&aggregation=all&per_page=2", 2), + createArgument("OFFSET=15&aggregation=all&PER_PAGE=2", 2), + createArgument("offset=15&perPage=2", 2)); + } + + private static Arguments createArgument(String searchUri, int expectedCount) { + return Arguments.of(URI.create(REQUEST_BASE_URL + searchUri), expectedCount); + } + + static Stream uriAccessRights() { + final AccessRight[] accessRights = {MANAGE_DOI, SUPPORT, MANAGE_PUBLISHING_REQUESTS}; + final var statistics = STATISTICS.asCamelCase(); + var uri = fromUri(REQUEST_BASE_URL).getUri(); + return Stream.of( + accessRightArg(uriWithParam(uri, Words.OWNER, AnetteOlli), 0, 7, AnetteOlli), + accessRightArg(uriWithParam(uri, Words.OWNER, Kir), 0, 9, Kir), + accessRightArg(uriWithParam(uri, statistics, TRUE), 0, 22, Kir, MANAGE_CUSTOMERS), + accessRightArg(uri, 0, 10, Kir, MANAGE_DOI), + accessRightArg(uri, 0, 14, Kir, SUPPORT), + accessRightArg(uri, 0, 14, Kir, MANAGE_PUBLISHING_REQUESTS), + accessRightArg(uri, 0, 1, USER_03, MANAGE_DOI), + accessRightArg(uri, 0, 6, USER_03, SUPPORT), + accessRightArg(uri, 0, 13, USER_03, MANAGE_PUBLISHING_REQUESTS), + accessRightArg(uri, 0, 7, USER_03, MANAGE_DOI, SUPPORT), + accessRightArg(uri, 0, 20, AnetteOlli, accessRights), + accessRightArg(uri, 0, 20, USER_03, accessRights)); + } + + private static URI uriWithParam(URI uri, String key, String value) { + return fromUri(uri).addQueryParameter(key, value).getUri(); + } + + static Arguments accessRightArg( + URI searchUri, int pending, int expectedCount, String userName, AccessRight... accessRights) { + return Arguments.of(searchUri, pending, expectedCount, userName, accessRights); + } + + static Stream uriProviderWithAggregationsAndAccessRights() { + final var url = URI.create("https://x.org/?size=0&aggregation=all"); + final var url2 = URI.create("https://x.org/?size=0&aggregation=all&STATISTICS=true"); + final var pending0 = 0; + final var pending1 = 1; + final AccessRight[] accessRights = {MANAGE_DOI, SUPPORT, MANAGE_PUBLISHING_REQUESTS}; + return Stream.of( + accessRightArg(url, pending0, 7, AnetteOlli), + accessRightArg(url, pending0, 7, AnetteOlli, MANAGE_DOI), + accessRightArg(url, pending0, 8, AnetteOlli, SUPPORT), + accessRightArg(url, pending1, 19, AnetteOlli, MANAGE_PUBLISHING_REQUESTS), + accessRightArg(url, pending0, 9, Kir), + accessRightArg(url, pending1, 10, Kir, MANAGE_DOI), + accessRightArg(url, pending0, 14, Kir, SUPPORT), + accessRightArg(url, pending0, 14, Kir, MANAGE_PUBLISHING_REQUESTS), + accessRightArg(url2, pending0, 22, USER_03, MANAGE_CUSTOMERS), + accessRightArg(url, pending0, 0, USER_03), + accessRightArg(url, pending0, 20, USER_03, accessRights), + accessRightArg(url, pending0, 4, USER_04), + accessRightArg(url, pending0, 20, USER_04, accessRights), + accessRightArg(url, pending0, 0, USER_05), + accessRightArg(url, pending0, 20, USER_05, accessRights)); + } + + static Stream uriInvalidProvider() { + return Stream.of( + URI.create(REQUEST_BASE_URL + "feilName=epler"), + URI.create(REQUEST_BASE_URL + "query=epler&fields=feilName"), + URI.create(REQUEST_BASE_URL + "CREATED_DATE=epler"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORY:DEdd"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORdfgY:desc"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORY"), + URI.create(REQUEST_BASE_URL + "sort=CATEGORY:asc:DEdd"), + URI.create(REQUEST_BASE_URL + "categories=hello+world&lang=en"), + URI.create(REQUEST_BASE_URL + "tittles=hello+world&modified_before=2019-01-01"), + URI.create(REQUEST_BASE_URL + "useers=hello+world&lang=en")); + } + + static Stream uriProviderAsAdmin() { + return loadMapFromResource(TICKETS_VALID_TEST_URL_JSON).entrySet().stream() + .map(entry -> createArgument(entry.getKey(), entry.getValue())); + } + + private static Map loadMapFromResource(String resource) { + var mappingsJson = stringFromResources(Path.of(resource)); + var type = new TypeReference>() {}; + return attempt(() -> JsonUtils.dtoObjectMapper.readValue(mappingsJson, type)).orElseThrow(); + } + + static Stream uriSortingProvider() { + + return Stream.of( + URI.create(REQUEST_BASE_URL + "sort=status&sortOrder=asc&sort=created_date&order=desc"), + URI.create(REQUEST_BASE_URL + "orderBy=status:asc,created_date:desc"), + URI.create(REQUEST_BASE_URL + "sort=status+asc&sort=created_date+desc"), + URI.create(REQUEST_BASE_URL + "sort=created_date&sortOrder=asc&sort=status&order=desc"), + URI.create(REQUEST_BASE_URL + "sort=modified_date+asc&sort=type+desc"), + URI.create(REQUEST_BASE_URL + "sort=relevance,modified_date+asc")); + } + + @Test + void shouldCheckMapping() { + + var mapping = indexingClient.getMapping(TICKETS); + assertThat(mapping, is(notNullValue())); + logger.info(mapping.toString()); + } + + @Test + void openSearchFailedResponse() { + HttpClient httpClient = mock(HttpClient.class); + when(httpClient.sendAsync(any(), any())).thenReturn(mockedFutureHttpResponse((String) null)); + var toMapEntries = queryToMapEntries(URI.create("https://example.com/?size=2")); + var resourceClient2 = new TicketClient(httpClient, setupMockedCachedJwtProvider()); + assertThrows( + RuntimeException.class, + () -> + TicketSearchQuery.builder() + .withRequiredParameters(SIZE, FROM) + .fromTestQueryParameters(toMapEntries) + .build() + .doSearch(resourceClient2)); + } + + @Test + void shouldCheckFacets() throws BadRequestException, UnauthorizedException { + var hostAddress = URI.create(container.getHttpHostAddress()); + var uri1 = URI.create(REQUEST_BASE_URL + AGGREGATION.name() + EQUAL + ALL); + var response1 = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri1)) + .withDockerHostUri(hostAddress) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .organization(testOrganizationId) + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .apply() + .doSearch(searchClient); + + assertNotNull(response1); + + var aggregations = response1.toPagedResponse().aggregations(); + + assertFalse(aggregations.isEmpty()); + assertThat(aggregations.size(), is(equalTo(EXPECTED_NUMBER_OF_AGGREGATIONS))); + + assertThat(aggregations.get(TYPE).size(), is(3)); + assertThat(aggregations.get(STATUS).getFirst().count(), is(11)); + assertThat(aggregations.get(BY_USER_PENDING.asCamelCase()).size(), is(1)); + assertThat(aggregations.get(PUBLICATION_STATUS).size(), is(3)); + + assertNotNull(FROM.asLowerCase()); + assertEquals(TicketStatus.NONE, TicketStatus.fromString("ewrdfg")); + assertEquals(TicketType.NONE, TicketType.fromString("wre")); + } + + @Test + void emptyResultShouldIncludeHits() throws BadRequestException, UnauthorizedException { + var uri = URI.create("https://x.org/?id=018b857b77b7"); + + var pagedResult = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE, SORT) + .build() + .withFilter() + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient); + assertNotNull(pagedResult.swsResponse()); + assertTrue(pagedResult.toString().contains("\"hits\":[")); + } + + @Test + void shouldReturnNewTicketsWhenSearchingForNewTicketsOnlyAndTypes() + throws BadRequestException, UnauthorizedException { + var uri = + URI.create( + "https://x.org/?status=New&size=500&type=doiRequest,generalSupportCase,publishingRequest&from=0"); + + var pagedResult = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient) + .toPagedResponse(); + + assertEquals(7, pagedResult.hits().size()); + } + + @Test + void shouldReturnNewTicketsWhenSearchingForNewTicketsOnly() + throws BadRequestException, UnauthorizedException { + var uri = URI.create("https://x.org/?status=New&size=500&from=0"); + + var pagedResult = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient) + .toPagedResponse(); + + assertEquals(7, pagedResult.hits().size()); + } + + @Test + void shouldReturnWhenSearchingForNewTicketsOnly() + throws BadRequestException, UnauthorizedException { + var uri = URI.create("https://x.org/?size=500&from=0&OWNER=1492596@20754.0.0.0"); + + var pagedResult = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .user("1492596@20754.0.0.0") + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient) + .toPagedResponse(); + + assertEquals(9, pagedResult.hits().size()); + } + + @Test + void shouldReturnWhenSearchingForAssignee() throws BadRequestException, UnauthorizedException { + var uri = URI.create("https://x.org/?size=500&from=0&ASSIGNEE=1492596@20754.0.0.0"); + + var pagedResult = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .user("1492596@20754.0.0.0") + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient) + .toPagedResponse(); + + assertEquals(1, pagedResult.hits().size()); + } + + @Test + void + shouldReturnNewAndPendingTicketsWithAssigneeWhenSearchingForTicketsWithStatusNewAndPendingAndAssignee() + throws BadRequestException, UnauthorizedException { + var uri = + URI.create("https://x.org/?status=New,Pending&assignee=1412322@20754.0.0.0&size=20&from=0"); + + var pagedResult = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient) + .toPagedResponse(); + + assertEquals(8, pagedResult.hits().size()); + } + + @ParameterizedTest + @MethodSource("uriPagingProvider") + void uriRequestPageableReturnsSuccessfulResponse(URI uri, int expectedCount) + throws ApiGatewayException { + + var response = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + assertNotNull(pagedSearchResourceDto); + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); + logger.debug(pagedSearchResourceDto.id().toString()); + } + + @ParameterizedTest + @MethodSource("uriProviderAsAdmin") + void uriRequestReturnsSuccessfulResponseAsAdmin(URI uri, int expectedCount) + throws ApiGatewayException { + + var response = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT, MANAGE_CUSTOMERS) + .organization(testOrganizationId) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + assertNotNull(pagedSearchResourceDto); + if (expectedCount == 0) { + logger.debug(pagedSearchResourceDto.toJsonString()); + } else { + logger.debug(pagedSearchResourceDto.toString()); + } + + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); + assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedCount))); + } + + @ParameterizedTest + @MethodSource("uriAccessRights") + void uriRequestReturnsSuccessfulResponseAsUser( + URI uri, + Integer ignoredValue, + Integer expectedCount, + String userName, + AccessRight... accessRights) + throws ApiGatewayException { + + var accessRightList = + new java.util.ArrayList<>(nonNull(accessRights) ? Arrays.asList(accessRights) : List.of()); + + var mockedRequestInfoLocal = mock(RequestInfo.class); + when(mockedRequestInfoLocal.getUserName()).thenReturn(userName); + when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) + .thenReturn(Optional.of(testOrganizationId)); + when(mockedRequestInfoLocal.getAccessRights()).thenReturn(accessRightList); + + var response = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfoLocal) + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + + assertNotNull(pagedSearchResourceDto); + assertThat(pagedSearchResourceDto.hits().size(), is(equalTo(expectedCount))); + assertThat(pagedSearchResourceDto.totalHits(), is(equalTo(expectedCount))); + } + + @ParameterizedTest() + @MethodSource("uriProviderWithAggregationsAndAccessRights") + void uriRequestReturnsSuccessfulResponseWithAggregations( + URI uri, Integer pending, Integer expectedCount, String userName, AccessRight... accessRights) + throws ApiGatewayException { + var response = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .organization(testOrganizationId) + .user(userName) + .accessRights(accessRights) + .apply() + .doSearch(searchClient); + + var aggregations = response.toPagedResponse().aggregations(); + assertNotNull(aggregations); + + var actualPending = + aggregations.get(Constants.BY_USER_PENDING).stream().mapToInt(Facet::count).sum(); + var actualCount = aggregations.get(TYPE).stream().mapToInt(Facet::count).sum(); + assertThat(actualPending, is(equalTo(pending))); + assertThat(actualCount, is(equalTo(expectedCount))); + } + + @Test + void shouldThrowUnauthorizedWhenRequestingTicketsForOwnerWhichIsNotCurrentCustomer() + throws UnauthorizedException { + var currentUserUsername = randomString(); + var ownerToSearch = randomString(); + + var mockedRequestInfoLocal = mock(RequestInfo.class); + when(mockedRequestInfoLocal.getUserName()).thenReturn(currentUserUsername); + when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) + .thenReturn(Optional.of(testOrganizationId)); + + assertThrows( + UnauthorizedException.class, + () -> + TicketSearchQuery.builder() + .withParameter(OWNER, ownerToSearch) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfoLocal) + .doSearch(searchClient)); + } + + @ParameterizedTest + @MethodSource("uriProviderAsAdmin") + @Disabled( + "Does not work. When test was written it returned an empty string even if there were" + + " supposed to be hits. Now we throw an exception instead as the method is not" + + " implemented.") + void uriRequestReturnsCsvResponse(URI uri) throws ApiGatewayException { + var query = + queryToMapEntries(uri).stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + when(mockedRequestInfo.getQueryParameters()).thenReturn(query); + + var csvResult = + TicketSearchQuery.builder() + .fromRequestInfo(mockedRequestInfo) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(FROM, SIZE) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfo) + .doSearch(searchClient); + assertNotNull(csvResult); + } + + @ParameterizedTest + @MethodSource("uriSortingProvider") + void uriRequestWithSortingReturnsSuccessfulResponse(URI uri) throws ApiGatewayException { + var response = + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE, SORT, AGGREGATION) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .organization(testOrganizationId) + .user(CURRENT_USERNAME) + .accessRights(MANAGE_DOI, MANAGE_PUBLISHING_REQUESTS, SUPPORT) + .apply() + .doSearch(searchClient); + + var pagedSearchResourceDto = response.toPagedResponse(); + assertNotNull(pagedSearchResourceDto.id()); + assertNotNull(pagedSearchResourceDto.context()); + assertTrue(pagedSearchResourceDto.totalHits() >= 0); + } + + @ParameterizedTest + @MethodSource("uriInvalidProvider") + void uriRequestReturnsBadRequest(URI uri) { + assertThrows( + BadRequestException.class, + () -> + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri)) + .withRequiredParameters(FROM, SIZE) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .doSearch(searchClient)); + } + + @Test + void ownerEqualAssigneeReturnsUnauthorized() throws UnauthorizedException { + var uriBad = URI.create(REQUEST_BASE_URL + "owner=Kir+Truhacev&assignee=Kir+Truhacev"); + // var uriBad = URI.create(REQUEST_BASE_URL + + // "owner=Kir+Truhacev&assignee=Annette+Olli"); + + var mockedRequestInfoLocal = mock(RequestInfo.class); + when(mockedRequestInfoLocal.getUserName()).thenReturn("Kir Truhacev"); + when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) + .thenReturn(Optional.of(testOrganizationId)); + when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of(MANAGE_DOI)); + assertThrows( + UnauthorizedException.class, + () -> + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uriBad)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(SIZE, FROM) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfoLocal) + .doSearch(searchClient)); + } + + @Test + void notSiktAdminReturnsUnauthorized() throws UnauthorizedException { + var uriBad = URI.create(REQUEST_BASE_URL + "STATISTICS=true"); + + var mockedRequestInfoLocal = mock(RequestInfo.class); + when(mockedRequestInfoLocal.getUserName()).thenReturn(randomString()); + when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) + .thenReturn(Optional.of(testOrganizationId)); + when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of(MANAGE_DOI)); + assertThrows( + UnauthorizedException.class, + () -> + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uriBad)) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .withRequiredParameters(SIZE, FROM) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfoLocal) + .doSearch(searchClient)); + } + + @Test + void uriRequestReturnsUnauthorized() throws UnauthorizedException { + AtomicReference uri = new AtomicReference<>(); + uriSortingProvider().findFirst().ifPresent(uri::set); + var mockedRequestInfoLocal = mock(RequestInfo.class); + when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of()); + when(mockedRequestInfoLocal.getCurrentCustomer()).thenReturn(null); + assertThrows( + UnauthorizedException.class, + () -> + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri.get())) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfoLocal) + .doSearch(searchClient)); + } + + @Test + void missingUserNameReturnsUnauthorized() throws UnauthorizedException { + AtomicReference uri = new AtomicReference<>(); + uriSortingProvider().findFirst().ifPresent(uri::set); + var mockedRequestInfoLocal = mock(RequestInfo.class); + when(mockedRequestInfoLocal.getAccessRights()).thenReturn(List.of()); + when(mockedRequestInfoLocal.getTopLevelOrgCristinId()) + .thenReturn(Optional.of(testOrganizationId)); + when(mockedRequestInfoLocal.getCurrentCustomer()).thenReturn(null); + assertThrows( + UnauthorizedException.class, + () -> + TicketSearchQuery.builder() + .fromTestQueryParameters(queryToMapEntries(uri.get())) + .withDockerHostUri(URI.create(container.getHttpHostAddress())) + .build() + .withFilter() + .fromRequestInfo(mockedRequestInfoLocal) + .doSearch(searchClient)); + } } diff --git a/search-commons/src/test/resources/ContributorNodeReducer.java b/search-commons/src/test/resources/ContributorNodeReducer.java index 141d03482..85186a060 100644 --- a/search-commons/src/test/resources/ContributorNodeReducer.java +++ b/search-commons/src/test/resources/ContributorNodeReducer.java @@ -8,7 +8,6 @@ import static no.unit.nva.constants.Words.NO; import static no.unit.nva.constants.Words.VERIFICATION_STATUS; import static no.unit.nva.constants.Words.VERIFIED; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -17,16 +16,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.TextNode; - +import java.nio.file.Path; import no.unit.nva.search.common.records.JsonNodeMutator; import no.unit.nva.search.resource.Constants; - import nva.commons.core.ioutils.IoUtils; - import org.junit.jupiter.api.Test; -import java.nio.file.Path; - /** * Reduces the number of contributors in a JsonNode. * diff --git a/search-handlers/src/main/java/no/unit/nva/search/ExportResourceHandler.java b/search-handlers/src/main/java/no/unit/nva/search/ExportResourceHandler.java index ddc71a7dd..298489b22 100644 --- a/search-handlers/src/main/java/no/unit/nva/search/ExportResourceHandler.java +++ b/search-handlers/src/main/java/no/unit/nva/search/ExportResourceHandler.java @@ -11,18 +11,15 @@ import static no.unit.nva.search.resource.ResourceParameter.SORT; import com.amazonaws.services.lambda.runtime.Context; - import no.unit.nva.search.common.csv.ResourceCsvTransformer; import no.unit.nva.search.resource.ResourceClient; import no.unit.nva.search.resource.ResourceSearchQuery; import no.unit.nva.search.scroll.ScrollClient; import no.unit.nva.search.scroll.ScrollQuery; - import nva.commons.apigateway.ApiS3GatewayHandler; import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.BadRequestException; import nva.commons.core.JacocoGenerated; - import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.presigner.S3Presigner; diff --git a/search-handlers/src/main/java/no/unit/nva/search/SearchImportCandidateAuthHandler.java b/search-handlers/src/main/java/no/unit/nva/search/SearchImportCandidateAuthHandler.java index c937c8ce4..f4e70a27c 100644 --- a/search-handlers/src/main/java/no/unit/nva/search/SearchImportCandidateAuthHandler.java +++ b/search-handlers/src/main/java/no/unit/nva/search/SearchImportCandidateAuthHandler.java @@ -30,44 +30,44 @@ */ public class SearchImportCandidateAuthHandler extends ApiGatewayHandler { - private final ImportCandidateClient opensearchClient; + private final ImportCandidateClient opensearchClient; - @JacocoGenerated - public SearchImportCandidateAuthHandler() { - this(new Environment(), defaultClient()); - } + @JacocoGenerated + public SearchImportCandidateAuthHandler() { + this(new Environment(), defaultClient()); + } - public SearchImportCandidateAuthHandler( - Environment environment, ImportCandidateClient candidateClient) { - super(Void.class, environment); - this.opensearchClient = candidateClient; - } + public SearchImportCandidateAuthHandler( + Environment environment, ImportCandidateClient candidateClient) { + super(Void.class, environment); + this.opensearchClient = candidateClient; + } - @Override - protected String processInput(Void input, RequestInfo requestInfo, Context context) - throws BadRequestException { - return ImportCandidateSearchQuery.builder() - .fromRequestInfo(requestInfo) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .validate() - .build() - .doSearch(opensearchClient) - .toString(); - } + @Override + protected String processInput(Void input, RequestInfo requestInfo, Context context) + throws BadRequestException { + return ImportCandidateSearchQuery.builder() + .fromRequestInfo(requestInfo) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .validate() + .build() + .doSearch(opensearchClient) + .toString(); + } - @Override - protected Integer getSuccessStatusCode(Void input, String output) { - return HttpURLConnection.HTTP_OK; - } + @Override + protected Integer getSuccessStatusCode(Void input, String output) { + return HttpURLConnection.HTTP_OK; + } - @Override - protected List listSupportedMediaTypes() { - return DEFAULT_RESPONSE_MEDIA_TYPES; - } + @Override + protected List listSupportedMediaTypes() { + return DEFAULT_RESPONSE_MEDIA_TYPES; + } - @Override - protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) - throws ApiGatewayException { - requestInfo.getUserName(); - } + @Override + protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) + throws ApiGatewayException { + requestInfo.getUserName(); + } } diff --git a/search-handlers/src/main/java/no/unit/nva/search/SearchResource20241201Handler.java b/search-handlers/src/main/java/no/unit/nva/search/SearchResource20241201Handler.java index 804cf12e6..27d0af0ba 100644 --- a/search-handlers/src/main/java/no/unit/nva/search/SearchResource20241201Handler.java +++ b/search-handlers/src/main/java/no/unit/nva/search/SearchResource20241201Handler.java @@ -11,20 +11,17 @@ import com.amazonaws.services.lambda.runtime.Context; import com.google.common.net.MediaType; - +import java.net.HttpURLConnection; +import java.util.List; import no.unit.nva.search.resource.ResourceClient; import no.unit.nva.search.resource.ResourceSearchQuery; import no.unit.nva.search.resource.SimplifiedMutator; - import nva.commons.apigateway.ApiGatewayHandler; import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.BadRequestException; import nva.commons.core.Environment; import nva.commons.core.JacocoGenerated; -import java.net.HttpURLConnection; -import java.util.List; - /** * Handler for searching resources. * diff --git a/search-handlers/src/main/java/no/unit/nva/search/SearchResourceAuthHandler.java b/search-handlers/src/main/java/no/unit/nva/search/SearchResourceAuthHandler.java index 4e551a15c..9b3776593 100644 --- a/search-handlers/src/main/java/no/unit/nva/search/SearchResourceAuthHandler.java +++ b/search-handlers/src/main/java/no/unit/nva/search/SearchResourceAuthHandler.java @@ -37,60 +37,60 @@ */ public class SearchResourceAuthHandler extends ApiGatewayHandler { - private final ResourceClient opensearchClient; + private final ResourceClient opensearchClient; - @JacocoGenerated - public SearchResourceAuthHandler() { - this(new Environment(), defaultClient()); - } + @JacocoGenerated + public SearchResourceAuthHandler() { + this(new Environment(), defaultClient()); + } - public SearchResourceAuthHandler(Environment environment, ResourceClient resourceClient) { - super(Void.class, environment); - this.opensearchClient = resourceClient; - } + public SearchResourceAuthHandler(Environment environment, ResourceClient resourceClient) { + super(Void.class, environment); + this.opensearchClient = resourceClient; + } - @Override - protected List listSupportedMediaTypes() { - return DEFAULT_RESPONSE_MEDIA_TYPES; - } + @Override + protected List listSupportedMediaTypes() { + return DEFAULT_RESPONSE_MEDIA_TYPES; + } - @Override - protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) - throws ApiGatewayException { - validateAccessRight(requestInfo.getAccessRights()); - } + @Override + protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) + throws ApiGatewayException { + validateAccessRight(requestInfo.getAccessRights()); + } - @Override - protected String processInput(Void input, RequestInfo requestInfo, Context context) - throws BadRequestException, UnauthorizedException { + @Override + protected String processInput(Void input, RequestInfo requestInfo, Context context) + throws BadRequestException, UnauthorizedException { - return ResourceSearchQuery.builder() - .fromRequestInfo(requestInfo) - .withRequiredParameters(FROM, SIZE, AGGREGATION, SORT) - .withAlwaysExcludedFields(GLOBAL_EXCLUDED_FIELDS) - .validate() - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA, DELETED, UNPUBLISHED) - .customerCurationInstitutions(requestInfo) - .apply() - .doSearch(opensearchClient) - .toString(); - } + return ResourceSearchQuery.builder() + .fromRequestInfo(requestInfo) + .withRequiredParameters(FROM, SIZE, AGGREGATION, SORT) + .withAlwaysExcludedFields(GLOBAL_EXCLUDED_FIELDS) + .validate() + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA, DELETED, UNPUBLISHED) + .customerCurationInstitutions(requestInfo) + .apply() + .doSearch(opensearchClient) + .toString(); + } - @Override - protected Integer getSuccessStatusCode(Void input, String output) { - return HttpURLConnection.HTTP_OK; - } + @Override + protected Integer getSuccessStatusCode(Void input, String output) { + return HttpURLConnection.HTTP_OK; + } - private void validateAccessRight(List accessRights) throws UnauthorizedException { - if (accessRights.contains(AccessRight.MANAGE_RESOURCES_ALL) - || accessRights.contains(AccessRight.MANAGE_CUSTOMERS) - // || accessRights.contains(AccessRight.MANAGE_OWN_AFFILIATION) - // || accessRights.contains(AccessRight.MANAGE_RESOURCES_STANDARD) - ) { - return; - } - throw new UnauthorizedException(); + private void validateAccessRight(List accessRights) throws UnauthorizedException { + if (accessRights.contains(AccessRight.MANAGE_RESOURCES_ALL) + || accessRights.contains(AccessRight.MANAGE_CUSTOMERS) + // || accessRights.contains(AccessRight.MANAGE_OWN_AFFILIATION) + // || accessRights.contains(AccessRight.MANAGE_RESOURCES_STANDARD) + ) { + return; } + throw new UnauthorizedException(); + } } diff --git a/search-handlers/src/main/java/no/unit/nva/search/SearchResourceHandler.java b/search-handlers/src/main/java/no/unit/nva/search/SearchResourceHandler.java index 2bf7a6ca0..d43db485b 100644 --- a/search-handlers/src/main/java/no/unit/nva/search/SearchResourceHandler.java +++ b/search-handlers/src/main/java/no/unit/nva/search/SearchResourceHandler.java @@ -5,23 +5,19 @@ import com.amazonaws.services.lambda.runtime.Context; import com.google.common.net.MediaType; - +import java.net.HttpURLConnection; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; import no.unit.nva.search.common.ContentTypeUtils; import no.unit.nva.search.resource.ResourceClient; - import nva.commons.apigateway.ApiGatewayHandler; import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.BadRequestException; import nva.commons.core.Environment; import nva.commons.core.JacocoGenerated; - import org.apache.http.HttpHeaders; -import java.net.HttpURLConnection; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - /** * Handler for searching resources. * @@ -29,50 +25,50 @@ */ public class SearchResourceHandler extends ApiGatewayHandler { - private static final String V_2024_12_01_SIMPLER_MODEL = "2024-12-01"; - private final ResourceClient opensearchClient; - - @JacocoGenerated - public SearchResourceHandler() { - this(new Environment(), defaultClient()); - } - - public SearchResourceHandler(Environment environment, ResourceClient resourceClient) { - super(Void.class, environment); - this.opensearchClient = resourceClient; - } - - @Override - protected List listSupportedMediaTypes() { - return DEFAULT_RESPONSE_MEDIA_TYPES; - } - - @Override - protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) { - // Do nothing - } - - @Override - protected String processInput(Void input, RequestInfo requestInfo, Context context) - throws BadRequestException { - - return switch (ContentTypeUtils.extractVersionFromRequestInfo(requestInfo)) { - case V_2024_12_01_SIMPLER_MODEL -> - new SearchResource20241201Handler(environment, opensearchClient) - .processInput(input, requestInfo, context); - case null, default -> - new SearchResourceLegacyHandler(environment, opensearchClient) - .processInput(input, requestInfo, context); - }; - } - - @Override - protected void addAdditionalHeaders(Supplier> additionalHeaders) { - super.addAdditionalHeaders(() -> Map.of(HttpHeaders.VARY, HttpHeaders.ACCEPT)); - } - - @Override - protected Integer getSuccessStatusCode(Void input, String output) { - return HttpURLConnection.HTTP_OK; - } + private static final String V_2024_12_01_SIMPLER_MODEL = "2024-12-01"; + private final ResourceClient opensearchClient; + + @JacocoGenerated + public SearchResourceHandler() { + this(new Environment(), defaultClient()); + } + + public SearchResourceHandler(Environment environment, ResourceClient resourceClient) { + super(Void.class, environment); + this.opensearchClient = resourceClient; + } + + @Override + protected List listSupportedMediaTypes() { + return DEFAULT_RESPONSE_MEDIA_TYPES; + } + + @Override + protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) { + // Do nothing + } + + @Override + protected String processInput(Void input, RequestInfo requestInfo, Context context) + throws BadRequestException { + + return switch (ContentTypeUtils.extractVersionFromRequestInfo(requestInfo)) { + case V_2024_12_01_SIMPLER_MODEL -> + new SearchResource20241201Handler(environment, opensearchClient) + .processInput(input, requestInfo, context); + case null, default -> + new SearchResourceLegacyHandler(environment, opensearchClient) + .processInput(input, requestInfo, context); + }; + } + + @Override + protected void addAdditionalHeaders(Supplier> additionalHeaders) { + super.addAdditionalHeaders(() -> Map.of(HttpHeaders.VARY, HttpHeaders.ACCEPT)); + } + + @Override + protected Integer getSuccessStatusCode(Void input, String output) { + return HttpURLConnection.HTTP_OK; + } } diff --git a/search-handlers/src/main/java/no/unit/nva/search/SearchResourceLegacyHandler.java b/search-handlers/src/main/java/no/unit/nva/search/SearchResourceLegacyHandler.java index f7cd217bc..d5f8b4a80 100644 --- a/search-handlers/src/main/java/no/unit/nva/search/SearchResourceLegacyHandler.java +++ b/search-handlers/src/main/java/no/unit/nva/search/SearchResourceLegacyHandler.java @@ -35,54 +35,54 @@ */ public class SearchResourceLegacyHandler extends ApiGatewayHandler { - public static final String ENTITY_DESCRIPTION_CONTRIBUTORS = "entityDescription.contributors"; - private final ResourceClient opensearchClient; + public static final String ENTITY_DESCRIPTION_CONTRIBUTORS = "entityDescription.contributors"; + private final ResourceClient opensearchClient; - @JacocoGenerated - public SearchResourceLegacyHandler() { - this(new Environment(), defaultClient()); - } + @JacocoGenerated + public SearchResourceLegacyHandler() { + this(new Environment(), defaultClient()); + } - public SearchResourceLegacyHandler(Environment environment, ResourceClient resourceClient) { - super(Void.class, environment); - this.opensearchClient = resourceClient; - } + public SearchResourceLegacyHandler(Environment environment, ResourceClient resourceClient) { + super(Void.class, environment); + this.opensearchClient = resourceClient; + } - @Override - protected List listSupportedMediaTypes() { - return DEFAULT_RESPONSE_MEDIA_TYPES; - } + @Override + protected List listSupportedMediaTypes() { + return DEFAULT_RESPONSE_MEDIA_TYPES; + } - @Override - protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) { - // Do nothing - } + @Override + protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) { + // Do nothing + } - @Override - protected String processInput(Void input, RequestInfo requestInfo, Context context) - throws BadRequestException { - return ResourceSearchQuery.builder() - .fromRequestInfo(requestInfo) - .withRequiredParameters(FROM, SIZE, AGGREGATION, SORT) - .withAlwaysExcludedFields(getExcludedFields()) - .validate() - .build() - .withFilter() - .requiredStatus(PUBLISHED, PUBLISHED_METADATA) - .apply() - .doSearch(opensearchClient) - .withMutators(new LegacyMutator()) - .toString(); - } + @Override + protected String processInput(Void input, RequestInfo requestInfo, Context context) + throws BadRequestException { + return ResourceSearchQuery.builder() + .fromRequestInfo(requestInfo) + .withRequiredParameters(FROM, SIZE, AGGREGATION, SORT) + .withAlwaysExcludedFields(getExcludedFields()) + .validate() + .build() + .withFilter() + .requiredStatus(PUBLISHED, PUBLISHED_METADATA) + .apply() + .doSearch(opensearchClient) + .withMutators(new LegacyMutator()) + .toString(); + } - private List getExcludedFields() { - return Stream.of(GLOBAL_EXCLUDED_FIELDS, List.of(ENTITY_DESCRIPTION_CONTRIBUTORS)) - .flatMap(Collection::stream) - .toList(); - } + private List getExcludedFields() { + return Stream.of(GLOBAL_EXCLUDED_FIELDS, List.of(ENTITY_DESCRIPTION_CONTRIBUTORS)) + .flatMap(Collection::stream) + .toList(); + } - @Override - protected Integer getSuccessStatusCode(Void input, String output) { - return HttpURLConnection.HTTP_OK; - } + @Override + protected Integer getSuccessStatusCode(Void input, String output) { + return HttpURLConnection.HTTP_OK; + } } diff --git a/search-handlers/src/main/java/no/unit/nva/search/SearchTicketAuthHandler.java b/search-handlers/src/main/java/no/unit/nva/search/SearchTicketAuthHandler.java index 30651d43e..1a57f6541 100644 --- a/search-handlers/src/main/java/no/unit/nva/search/SearchTicketAuthHandler.java +++ b/search-handlers/src/main/java/no/unit/nva/search/SearchTicketAuthHandler.java @@ -8,10 +8,10 @@ import com.amazonaws.services.lambda.runtime.Context; import com.google.common.net.MediaType; - +import java.net.HttpURLConnection; +import java.util.List; import no.unit.nva.search.ticket.TicketClient; import no.unit.nva.search.ticket.TicketSearchQuery; - import nva.commons.apigateway.ApiGatewayHandler; import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.ApiGatewayException; @@ -20,9 +20,6 @@ import nva.commons.core.Environment; import nva.commons.core.JacocoGenerated; -import java.net.HttpURLConnection; -import java.util.List; - /** * Handler for searching tickets. * @@ -30,45 +27,45 @@ */ public class SearchTicketAuthHandler extends ApiGatewayHandler { - private final TicketClient opensearchClient; + private final TicketClient opensearchClient; - @JacocoGenerated - public SearchTicketAuthHandler() { - this(new Environment(), defaultClient()); - } + @JacocoGenerated + public SearchTicketAuthHandler() { + this(new Environment(), defaultClient()); + } - public SearchTicketAuthHandler(Environment environment, TicketClient ticketClient) { - super(Void.class, environment); - this.opensearchClient = ticketClient; - } + public SearchTicketAuthHandler(Environment environment, TicketClient ticketClient) { + super(Void.class, environment); + this.opensearchClient = ticketClient; + } - @Override - protected String processInput(Void input, RequestInfo requestInfo, Context context) - throws BadRequestException, UnauthorizedException { + @Override + protected String processInput(Void input, RequestInfo requestInfo, Context context) + throws BadRequestException, UnauthorizedException { - return TicketSearchQuery.builder() - .fromRequestInfo(requestInfo) - .withRequiredParameters(FROM, SIZE, AGGREGATION) - .build() - .withFilter() - .fromRequestInfo(requestInfo) - .doSearch(opensearchClient) - .toString(); - } + return TicketSearchQuery.builder() + .fromRequestInfo(requestInfo) + .withRequiredParameters(FROM, SIZE, AGGREGATION) + .build() + .withFilter() + .fromRequestInfo(requestInfo) + .doSearch(opensearchClient) + .toString(); + } - @Override - protected Integer getSuccessStatusCode(Void input, String output) { - return HttpURLConnection.HTTP_OK; - } + @Override + protected Integer getSuccessStatusCode(Void input, String output) { + return HttpURLConnection.HTTP_OK; + } - @Override - protected List listSupportedMediaTypes() { - return DEFAULT_RESPONSE_MEDIA_TYPES; - } + @Override + protected List listSupportedMediaTypes() { + return DEFAULT_RESPONSE_MEDIA_TYPES; + } - @Override - protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) - throws ApiGatewayException { - requestInfo.getUserName(); - } + @Override + protected void validateRequest(Void unused, RequestInfo requestInfo, Context context) + throws ApiGatewayException { + requestInfo.getUserName(); + } } diff --git a/search-handlers/src/test/java/no/unit/nva/search/ExportResourceHandlerTest.java b/search-handlers/src/test/java/no/unit/nva/search/ExportResourceHandlerTest.java index dff0d4f64..7219293af 100644 --- a/search-handlers/src/test/java/no/unit/nva/search/ExportResourceHandlerTest.java +++ b/search-handlers/src/test/java/no/unit/nva/search/ExportResourceHandlerTest.java @@ -5,7 +5,6 @@ import static no.unit.nva.search.resource.ResourceParameter.SEARCH_ALL; import static no.unit.nva.testutils.RandomDataGenerator.randomString; import static no.unit.nva.testutils.RandomDataGenerator.randomUri; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.mockito.ArgumentMatchers.any; @@ -14,26 +13,22 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; import no.unit.nva.indexing.testutils.FakeSearchResponse; import no.unit.nva.search.common.csv.ExportCsv; import no.unit.nva.search.common.records.SwsResponse; import no.unit.nva.search.resource.ResourceClient; import no.unit.nva.search.scroll.ScrollClient; import no.unit.nva.testutils.HandlerRequestBuilder; - import nva.commons.apigateway.RequestInfo; import nva.commons.apigateway.exceptions.BadRequestException; - import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; - class ExportResourceHandlerTest { private static final String SAMPLE_PATH = "search"; private static final String SAMPLE_DOMAIN_NAME = "localhost"; diff --git a/search-handlers/src/test/java/no/unit/nva/search/SearchImportCandidateAuthHandlerTest.java b/search-handlers/src/test/java/no/unit/nva/search/SearchImportCandidateAuthHandlerTest.java index 8ca18ada9..284718f73 100644 --- a/search-handlers/src/test/java/no/unit/nva/search/SearchImportCandidateAuthHandlerTest.java +++ b/search-handlers/src/test/java/no/unit/nva/search/SearchImportCandidateAuthHandlerTest.java @@ -55,256 +55,252 @@ class SearchImportCandidateAuthHandlerTest { - public static final String SAMPLE_PATH = "search"; - public static final String SAMPLE_DOMAIN_NAME = "localhost"; - public static final String SAMPLE_SEARCH_TERM = "searchTerm"; - public static final String SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON = - "sample_opensearch_importCandidate_response.json"; - public static final String ROUNDTRIP_RESPONSE_JSON = "roundTripImportCandidateResponse.json"; - public static final String EMPTY_OPENSEARCH_RESPONSE_JSON = "empty_opensearch_response.json"; - private SearchImportCandidateAuthHandler handler; - private Context contextMock; - private ByteArrayOutputStream outputStream; - private ImportCandidateClient mockedSearchClient; - - private static ExportCsv csvWithFullDate() { - var id = randomUri().toString(); - var title = randomString(); - var type = "AcademicArticle"; - var contributors = List.of(randomString(), randomString(), randomString()); - var date = "2022-01-22"; - - return new ExportCsv() - .withId(id) - .withMainTitle(title) - .withPublicationInstance(type) - .withPublicationDate(date) - .withContributors(String.join(COMMA, contributors)); - } - - public static Stream acceptHeaderValuesProducingTextCsvProvider() { - return Stream.of("text/*", Words.TEXT_CSV); - } - - public static Stream acceptHeaderValuesProducingApplicationJsonProvider() { - return Stream.of(null, "application/json", "application/json; charset=utf-8"); - } - - @BeforeEach - void setUp() { - - mockedSearchClient = mock(ImportCandidateClient.class); - handler = new SearchImportCandidateAuthHandler(new Environment(), mockedSearchClient); - contextMock = mock(Context.class); - outputStream = new ByteArrayOutputStream(); - } - - @Test - void shouldReturnSearchResultsWhenQueryIsSingleTerm() throws IOException { - prepareRestHighLevelClientOkResponse(); - - handler.handleRequest(getInputStream(), outputStream, contextMock); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - var actualBody = gatewayResponse.body(); - var expected = getSearchResourcesResponseFromFile(ROUNDTRIP_RESPONSE_JSON); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_OK, gatewayResponse.statusCode()); - assertThat(actualBody.hits(), is(equalTo(expected.hits()))); - } - - @ParameterizedTest(name = "should return text/csv for accept header {0}") - @MethodSource("acceptHeaderValuesProducingTextCsvProvider") - @Disabled( - "Does not work. When test was written it returned an empty string even if there were" - + " supposed to be hits. Now we throw an exception instead as the method is not" - + " implemented.") - void shouldReturnTextCsvWithGivenAcceptHeader(String acceptHeaderValue) throws IOException { - prepareRestHighLevelClientOkResponse(List.of(csvWithFullDate(), csvWithYearOnly())); - handler.handleRequest( - getRequestInputStreamAccepting(acceptHeaderValue), - outputStream, - mock(Context.class)); - - GatewayResponse gatewayResponse = - GatewayResponse.fromOutputStream(outputStream, String.class); - assertThat( - gatewayResponse.getHeaders().get("Content-Type"), - is(equalTo("text/csv; charset=utf-8"))); - } - - private ExportCsv csvWithYearOnly() { - var id = randomUri().toString(); - var title = randomString(); - var type = "AcademicArticle"; - var contributors = List.of(randomString(), randomString(), randomString()); - var date = "2022"; - - return new ExportCsv() - .withId(id) - .withMainTitle(title) - .withPublicationInstance(type) - .withPublicationDate(date) - .withContributors(String.join(COMMA, contributors)); - } - - @Test - void shouldReturnSortedSearchResultsWhenSendingContributorId() throws IOException { - prepareRestHighLevelClientOkResponse(); - handler.handleRequest(getInputStreamWithContributorId(), outputStream, contextMock); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_OK, gatewayResponse.statusCode()); - } - - @Test - void shouldNotReturnSortedSearchResultsWhenSendingMultipleContributorId() throws IOException { - prepareRestHighLevelClientOkResponse(); - - handler.handleRequest(getInputStreamWithMultipleContributorId(), outputStream, contextMock); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_OK, gatewayResponse.statusCode()); - } - - @Test - void shouldReturnSearchResultsWithEmptyHitsWhenQueryResultIsEmpty() throws IOException { - prepareRestHighLevelClientEmptyResponse(); - - handler.handleRequest(getInputStream(), outputStream, mock(Context.class)); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - var actualBody = gatewayResponse.body(); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_OK, gatewayResponse.statusCode()); - assertThat(actualBody.totalHits(), is(equalTo(0))); - assertThat(actualBody.hits(), is(empty())); - assertDoesNotThrow(() -> gatewayResponse.body().id().normalize()); - } - - @Test - void shouldReturn200WhenSortOrderIsNotSpecified() throws IOException { - prepareRestHighLevelClientEmptyResponseForSortOrder(); - - handler.handleRequest(getInputStream(), outputStream, mock(Context.class)); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - var actualBody = gatewayResponse.body(); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_OK, gatewayResponse.statusCode()); - assertThat(actualBody.totalHits(), is(equalTo(0))); - assertThat(actualBody.hits(), is(empty())); - assertDoesNotThrow(() -> gatewayResponse.body().id().normalize()); - } - - @ParameterizedTest(name = "Should return application/json for accept header {0}") - @MethodSource("acceptHeaderValuesProducingApplicationJsonProvider") - void shouldProduceWithHeader(String acceptHeaderValue) throws IOException { - prepareRestHighLevelClientOkResponse(); - var requestInput = - nonNull(acceptHeaderValue) - ? getRequestInputStreamAccepting(acceptHeaderValue) - : getInputStream(); - handler.handleRequest(requestInput, outputStream, mock(Context.class)); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - assertThat( - gatewayResponse.headers().get("Content-Type"), - is(equalTo("application/json; charset=utf-8"))); - } - - private InputStream getInputStream() throws JsonProcessingException { - return new HandlerRequestBuilder(objectMapperWithEmpty) - .withQueryParameters(Map.of(SEARCH_ALL.asCamelCase(), SAMPLE_SEARCH_TERM)) - .withRequestContext(getRequestContext()) - .withUserName(randomString()) - .build(); - } - - private InputStream getInputStreamWithContributorId() throws JsonProcessingException { - return new HandlerRequestBuilder(objectMapperWithEmpty) - .withQueryParameters( - Map.of( - SEARCH_ALL.asCamelCase(), - "entityDescription.contributors.identity.id:12345", - "results", - "10", - "from", - "0")) - .withHeaders(Map.of(ACCEPT, "application/json")) - .withRequestContext(getRequestContext()) - .withUserName(randomString()) - .build(); - } - - private InputStream getInputStreamWithMultipleContributorId() throws JsonProcessingException { - return new HandlerRequestBuilder(objectMapperWithEmpty) - .withQueryParameters( - Map.of( - SEARCH_ALL.name(), - "((entityDescription.contributors.identity.id:12345)" - + "+OR+(entityDescription.contributors.identity.id:54321))")) - .withRequestContext(getRequestContext()) - .withHeaders(Map.of(ACCEPT, "application/json")) - .withUserName(randomString()) - .build(); - } - - private InputStream getRequestInputStreamAccepting(String contentType) - throws JsonProcessingException { - return new HandlerRequestBuilder(objectMapperWithEmpty) - .withQueryParameters(Map.of(SEARCH_ALL.name(), SAMPLE_SEARCH_TERM)) - .withHeaders(Map.of(ACCEPT, contentType)) - .withRequestContext(getRequestContext()) - .withUserName(randomString()) - .build(); - } - - private ObjectNode getRequestContext() { - return objectMapperWithEmpty.convertValue( - Map.of("path", SAMPLE_PATH, "domainName", SAMPLE_DOMAIN_NAME), ObjectNode.class); - } - - private void prepareRestHighLevelClientOkResponse(List exportCsvs) - throws IOException { - var jsonResponse = FakeSearchResponse.generateSearchResponseString(exportCsvs, null); - var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); - - when(mockedSearchClient.doSearch(any())).thenReturn(body); - } - - private void prepareRestHighLevelClientOkResponse() throws IOException { - var jsonResponse = - stringFromResources(Path.of(SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON)); - var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); - - when(mockedSearchClient.doSearch(any())).thenReturn(body); - } - - private void prepareRestHighLevelClientEmptyResponse() throws IOException { - var jsonResponse = stringFromResources(Path.of(EMPTY_OPENSEARCH_RESPONSE_JSON)); - var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); - - when(mockedSearchClient.doSearch(any())).thenReturn(body); - } - - private void prepareRestHighLevelClientEmptyResponseForSortOrder() throws IOException { - var jsonResponse = stringFromResources(Path.of(EMPTY_OPENSEARCH_RESPONSE_JSON)); - var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); - - when(mockedSearchClient.doSearch(any())).thenReturn(body); - } - - private PagedSearch getSearchResourcesResponseFromFile(String filename) - throws JsonProcessingException { - return objectMapperWithEmpty.readValue( - stringFromResources(Path.of(filename)), PagedSearch.class); - } + public static final String SAMPLE_PATH = "search"; + public static final String SAMPLE_DOMAIN_NAME = "localhost"; + public static final String SAMPLE_SEARCH_TERM = "searchTerm"; + public static final String SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON = + "sample_opensearch_importCandidate_response.json"; + public static final String ROUNDTRIP_RESPONSE_JSON = "roundTripImportCandidateResponse.json"; + public static final String EMPTY_OPENSEARCH_RESPONSE_JSON = "empty_opensearch_response.json"; + private SearchImportCandidateAuthHandler handler; + private Context contextMock; + private ByteArrayOutputStream outputStream; + private ImportCandidateClient mockedSearchClient; + + private static ExportCsv csvWithFullDate() { + var id = randomUri().toString(); + var title = randomString(); + var type = "AcademicArticle"; + var contributors = List.of(randomString(), randomString(), randomString()); + var date = "2022-01-22"; + + return new ExportCsv() + .withId(id) + .withMainTitle(title) + .withPublicationInstance(type) + .withPublicationDate(date) + .withContributors(String.join(COMMA, contributors)); + } + + public static Stream acceptHeaderValuesProducingTextCsvProvider() { + return Stream.of("text/*", Words.TEXT_CSV); + } + + public static Stream acceptHeaderValuesProducingApplicationJsonProvider() { + return Stream.of(null, "application/json", "application/json; charset=utf-8"); + } + + @BeforeEach + void setUp() { + + mockedSearchClient = mock(ImportCandidateClient.class); + handler = new SearchImportCandidateAuthHandler(new Environment(), mockedSearchClient); + contextMock = mock(Context.class); + outputStream = new ByteArrayOutputStream(); + } + + @Test + void shouldReturnSearchResultsWhenQueryIsSingleTerm() throws IOException { + prepareRestHighLevelClientOkResponse(); + + handler.handleRequest(getInputStream(), outputStream, contextMock); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + var actualBody = gatewayResponse.body(); + var expected = getSearchResourcesResponseFromFile(ROUNDTRIP_RESPONSE_JSON); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_OK, gatewayResponse.statusCode()); + assertThat(actualBody.hits(), is(equalTo(expected.hits()))); + } + + @ParameterizedTest(name = "should return text/csv for accept header {0}") + @MethodSource("acceptHeaderValuesProducingTextCsvProvider") + @Disabled( + "Does not work. When test was written it returned an empty string even if there were" + + " supposed to be hits. Now we throw an exception instead as the method is not" + + " implemented.") + void shouldReturnTextCsvWithGivenAcceptHeader(String acceptHeaderValue) throws IOException { + prepareRestHighLevelClientOkResponse(List.of(csvWithFullDate(), csvWithYearOnly())); + handler.handleRequest( + getRequestInputStreamAccepting(acceptHeaderValue), outputStream, mock(Context.class)); + + GatewayResponse gatewayResponse = + GatewayResponse.fromOutputStream(outputStream, String.class); + assertThat( + gatewayResponse.getHeaders().get("Content-Type"), is(equalTo("text/csv; charset=utf-8"))); + } + + private ExportCsv csvWithYearOnly() { + var id = randomUri().toString(); + var title = randomString(); + var type = "AcademicArticle"; + var contributors = List.of(randomString(), randomString(), randomString()); + var date = "2022"; + + return new ExportCsv() + .withId(id) + .withMainTitle(title) + .withPublicationInstance(type) + .withPublicationDate(date) + .withContributors(String.join(COMMA, contributors)); + } + + @Test + void shouldReturnSortedSearchResultsWhenSendingContributorId() throws IOException { + prepareRestHighLevelClientOkResponse(); + handler.handleRequest(getInputStreamWithContributorId(), outputStream, contextMock); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_OK, gatewayResponse.statusCode()); + } + + @Test + void shouldNotReturnSortedSearchResultsWhenSendingMultipleContributorId() throws IOException { + prepareRestHighLevelClientOkResponse(); + + handler.handleRequest(getInputStreamWithMultipleContributorId(), outputStream, contextMock); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_OK, gatewayResponse.statusCode()); + } + + @Test + void shouldReturnSearchResultsWithEmptyHitsWhenQueryResultIsEmpty() throws IOException { + prepareRestHighLevelClientEmptyResponse(); + + handler.handleRequest(getInputStream(), outputStream, mock(Context.class)); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + var actualBody = gatewayResponse.body(); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_OK, gatewayResponse.statusCode()); + assertThat(actualBody.totalHits(), is(equalTo(0))); + assertThat(actualBody.hits(), is(empty())); + assertDoesNotThrow(() -> gatewayResponse.body().id().normalize()); + } + + @Test + void shouldReturn200WhenSortOrderIsNotSpecified() throws IOException { + prepareRestHighLevelClientEmptyResponseForSortOrder(); + + handler.handleRequest(getInputStream(), outputStream, mock(Context.class)); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + var actualBody = gatewayResponse.body(); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_OK, gatewayResponse.statusCode()); + assertThat(actualBody.totalHits(), is(equalTo(0))); + assertThat(actualBody.hits(), is(empty())); + assertDoesNotThrow(() -> gatewayResponse.body().id().normalize()); + } + + @ParameterizedTest(name = "Should return application/json for accept header {0}") + @MethodSource("acceptHeaderValuesProducingApplicationJsonProvider") + void shouldProduceWithHeader(String acceptHeaderValue) throws IOException { + prepareRestHighLevelClientOkResponse(); + var requestInput = + nonNull(acceptHeaderValue) + ? getRequestInputStreamAccepting(acceptHeaderValue) + : getInputStream(); + handler.handleRequest(requestInput, outputStream, mock(Context.class)); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + assertThat( + gatewayResponse.headers().get("Content-Type"), + is(equalTo("application/json; charset=utf-8"))); + } + + private InputStream getInputStream() throws JsonProcessingException { + return new HandlerRequestBuilder(objectMapperWithEmpty) + .withQueryParameters(Map.of(SEARCH_ALL.asCamelCase(), SAMPLE_SEARCH_TERM)) + .withRequestContext(getRequestContext()) + .withUserName(randomString()) + .build(); + } + + private InputStream getInputStreamWithContributorId() throws JsonProcessingException { + return new HandlerRequestBuilder(objectMapperWithEmpty) + .withQueryParameters( + Map.of( + SEARCH_ALL.asCamelCase(), + "entityDescription.contributors.identity.id:12345", + "results", + "10", + "from", + "0")) + .withHeaders(Map.of(ACCEPT, "application/json")) + .withRequestContext(getRequestContext()) + .withUserName(randomString()) + .build(); + } + + private InputStream getInputStreamWithMultipleContributorId() throws JsonProcessingException { + return new HandlerRequestBuilder(objectMapperWithEmpty) + .withQueryParameters( + Map.of( + SEARCH_ALL.name(), + "((entityDescription.contributors.identity.id:12345)" + + "+OR+(entityDescription.contributors.identity.id:54321))")) + .withRequestContext(getRequestContext()) + .withHeaders(Map.of(ACCEPT, "application/json")) + .withUserName(randomString()) + .build(); + } + + private InputStream getRequestInputStreamAccepting(String contentType) + throws JsonProcessingException { + return new HandlerRequestBuilder(objectMapperWithEmpty) + .withQueryParameters(Map.of(SEARCH_ALL.name(), SAMPLE_SEARCH_TERM)) + .withHeaders(Map.of(ACCEPT, contentType)) + .withRequestContext(getRequestContext()) + .withUserName(randomString()) + .build(); + } + + private ObjectNode getRequestContext() { + return objectMapperWithEmpty.convertValue( + Map.of("path", SAMPLE_PATH, "domainName", SAMPLE_DOMAIN_NAME), ObjectNode.class); + } + + private void prepareRestHighLevelClientOkResponse(List exportCsvs) throws IOException { + var jsonResponse = FakeSearchResponse.generateSearchResponseString(exportCsvs, null); + var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); + + when(mockedSearchClient.doSearch(any())).thenReturn(body); + } + + private void prepareRestHighLevelClientOkResponse() throws IOException { + var jsonResponse = + stringFromResources(Path.of(SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON)); + var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); + + when(mockedSearchClient.doSearch(any())).thenReturn(body); + } + + private void prepareRestHighLevelClientEmptyResponse() throws IOException { + var jsonResponse = stringFromResources(Path.of(EMPTY_OPENSEARCH_RESPONSE_JSON)); + var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); + + when(mockedSearchClient.doSearch(any())).thenReturn(body); + } + + private void prepareRestHighLevelClientEmptyResponseForSortOrder() throws IOException { + var jsonResponse = stringFromResources(Path.of(EMPTY_OPENSEARCH_RESPONSE_JSON)); + var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); + + when(mockedSearchClient.doSearch(any())).thenReturn(body); + } + + private PagedSearch getSearchResourcesResponseFromFile(String filename) + throws JsonProcessingException { + return objectMapperWithEmpty.readValue( + stringFromResources(Path.of(filename)), PagedSearch.class); + } } diff --git a/search-handlers/src/test/java/no/unit/nva/search/SearchResource20241201HandlerTest.java b/search-handlers/src/test/java/no/unit/nva/search/SearchResource20241201HandlerTest.java index f383fad1b..9cc7a73a8 100644 --- a/search-handlers/src/test/java/no/unit/nva/search/SearchResource20241201HandlerTest.java +++ b/search-handlers/src/test/java/no/unit/nva/search/SearchResource20241201HandlerTest.java @@ -1,10 +1,9 @@ package no.unit.nva.search; +import static java.net.HttpURLConnection.HTTP_OK; import static no.unit.nva.constants.Defaults.objectMapperWithEmpty; import static no.unit.nva.search.resource.ResourceParameter.SEARCH_ALL; - import static nva.commons.core.ioutils.IoUtils.stringFromResources; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -13,92 +12,84 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static java.net.HttpURLConnection.HTTP_OK; - import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Path; +import java.util.Map; import no.unit.nva.search.common.FakeGatewayResponse; import no.unit.nva.search.common.records.SwsResponse; import no.unit.nva.search.resource.ResourceClient; import no.unit.nva.search.resource.response.ResourceSearchResponse; import no.unit.nva.testutils.HandlerRequestBuilder; - import nva.commons.core.Environment; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.nio.file.Path; -import java.util.Map; - class SearchResource20241201HandlerTest { - public static final String SAMPLE_PATH = "search"; - public static final String SAMPLE_DOMAIN_NAME = "localhost"; - public static final String SAMPLE_SEARCH_TERM = "searchTerm"; - public static final String SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON = - "sample_opensearch_response.json"; - private SearchResource20241201Handler handler; - private Context contextMock; - private ByteArrayOutputStream outputStream; - private ResourceClient mockedSearchClient; - - @BeforeEach - void setUp() { - - mockedSearchClient = mock(ResourceClient.class); - handler = new SearchResource20241201Handler(new Environment(), mockedSearchClient); - contextMock = mock(Context.class); - outputStream = new ByteArrayOutputStream(); - } - - private void prepareRestHighLevelClientOkResponse() throws IOException { - var jsonResponse = - stringFromResources(Path.of(SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON)); - var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); - - when(mockedSearchClient.doSearch(any())).thenReturn(body); - } - - @Test - public void shouldReturnAResponseThatCanBeMappedToModelDto() throws IOException { - prepareRestHighLevelClientOkResponse(); - handler.handleRequest(getInputStream(), outputStream, contextMock); - var gatewayResponse = FakeGatewayResponse.of(outputStream); - - assertThat(gatewayResponse.statusCode(), is(equalTo(HTTP_OK))); - - var actualBody = gatewayResponse.body(); - var firstHitJson = actualBody.hits().getFirst(); - - assertNotNull(firstHitJson); - - var firstHitDto = - objectMapperWithEmpty.treeToValue(firstHitJson, ResourceSearchResponse.class); - assertNotNull(firstHitDto); - - assertThat( - firstHitDto.id(), - is( - equalTo( - URI.create( - "http://localhost/publication/f367b260-c15e-4d0f-b197-e1dc0e9eb0e8")))); - } - - private InputStream getInputStream() throws JsonProcessingException { - return new HandlerRequestBuilder(objectMapperWithEmpty) - .withQueryParameters(Map.of(SEARCH_ALL.name(), SAMPLE_SEARCH_TERM)) - .withRequestContext(getRequestContext()) - .build(); - } - - private ObjectNode getRequestContext() { - return objectMapperWithEmpty.convertValue( - Map.of("path", SAMPLE_PATH, "domainName", SAMPLE_DOMAIN_NAME), ObjectNode.class); - } + public static final String SAMPLE_PATH = "search"; + public static final String SAMPLE_DOMAIN_NAME = "localhost"; + public static final String SAMPLE_SEARCH_TERM = "searchTerm"; + public static final String SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON = + "sample_opensearch_response.json"; + private SearchResource20241201Handler handler; + private Context contextMock; + private ByteArrayOutputStream outputStream; + private ResourceClient mockedSearchClient; + + @BeforeEach + void setUp() { + + mockedSearchClient = mock(ResourceClient.class); + handler = new SearchResource20241201Handler(new Environment(), mockedSearchClient); + contextMock = mock(Context.class); + outputStream = new ByteArrayOutputStream(); + } + + private void prepareRestHighLevelClientOkResponse() throws IOException { + var jsonResponse = + stringFromResources(Path.of(SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON)); + var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); + + when(mockedSearchClient.doSearch(any())).thenReturn(body); + } + + @Test + public void shouldReturnAResponseThatCanBeMappedToModelDto() throws IOException { + prepareRestHighLevelClientOkResponse(); + handler.handleRequest(getInputStream(), outputStream, contextMock); + var gatewayResponse = FakeGatewayResponse.of(outputStream); + + assertThat(gatewayResponse.statusCode(), is(equalTo(HTTP_OK))); + + var actualBody = gatewayResponse.body(); + var firstHitJson = actualBody.hits().getFirst(); + + assertNotNull(firstHitJson); + + var firstHitDto = objectMapperWithEmpty.treeToValue(firstHitJson, ResourceSearchResponse.class); + assertNotNull(firstHitDto); + + assertThat( + firstHitDto.id(), + is( + equalTo( + URI.create("http://localhost/publication/f367b260-c15e-4d0f-b197-e1dc0e9eb0e8")))); + } + + private InputStream getInputStream() throws JsonProcessingException { + return new HandlerRequestBuilder(objectMapperWithEmpty) + .withQueryParameters(Map.of(SEARCH_ALL.name(), SAMPLE_SEARCH_TERM)) + .withRequestContext(getRequestContext()) + .build(); + } + + private ObjectNode getRequestContext() { + return objectMapperWithEmpty.convertValue( + Map.of("path", SAMPLE_PATH, "domainName", SAMPLE_DOMAIN_NAME), ObjectNode.class); + } } diff --git a/search-handlers/src/test/java/no/unit/nva/search/SearchResourceAuthHandlerTest.java b/search-handlers/src/test/java/no/unit/nva/search/SearchResourceAuthHandlerTest.java index de928d674..762ec02c4 100644 --- a/search-handlers/src/test/java/no/unit/nva/search/SearchResourceAuthHandlerTest.java +++ b/search-handlers/src/test/java/no/unit/nva/search/SearchResourceAuthHandlerTest.java @@ -44,100 +44,98 @@ class SearchResourceAuthHandlerTest { - public static final String SAMPLE_PATH = "search"; - public static final String SAMPLE_DOMAIN_NAME = "localhost"; - public static final String SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON = - "sample_opensearch_response.json"; - private SearchResourceAuthHandler handler; - private Context contextMock; - private ByteArrayOutputStream outputStream; - private ResourceClient mockedSearchClient; - - @BeforeEach - void setUp() { - - mockedSearchClient = mock(ResourceClient.class); - handler = new SearchResourceAuthHandler(new Environment(), mockedSearchClient); - contextMock = mock(Context.class); - outputStream = new ByteArrayOutputStream(); - } - - @Test - void shouldOnlyReturnPublicationsFromCuratorsOrganizationWhenQuerying() - throws IOException, URISyntaxException { - prepareRestHighLevelClientOkResponse(); - - var customer = - new URI( - "https://api.dev.nva.aws.unit.no/customer/f54c8aa9-073a-46a1-8f7c-dde66c853934"); - var curatorOrganization = - new URI("https://api.dev.nva.aws.unit.no/cristin/organization/184.0.0.0"); - - handler.handleRequest( - getInputStreamWithAccessRight( - customer, curatorOrganization, AccessRight.MANAGE_RESOURCES_ALL), - outputStream, - contextMock); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - var actualBody = gatewayResponse.body(); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_OK, gatewayResponse.statusCode()); - assertThat(actualBody.hits().size(), is(equalTo(2))); - } - - @Test - void shouldReturnOkWhenUserIsEditor() throws IOException { - prepareRestHighLevelClientOkResponse(); - - var input = - getInputStreamWithAccessRight( - randomUri(), randomUri(), AccessRight.MANAGE_RESOURCES_ALL); - handler.handleRequest(input, outputStream, contextMock); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_OK, gatewayResponse.statusCode()); - } - - @Test - void shouldReturnUnauthorizedWhenUserIsMissingAccessRight() throws IOException { - prepareRestHighLevelClientOkResponse(); - - var input = getInputStreamWithAccessRight(randomUri(), randomUri(), AccessRight.SUPPORT); - handler.handleRequest(input, outputStream, contextMock); - - var gatewayResponse = FakeGatewayResponse.of(outputStream); - - assertNotNull(gatewayResponse.headers()); - assertEquals(HTTP_UNAUTHORIZED, gatewayResponse.statusCode()); - } - - private void prepareRestHighLevelClientOkResponse() throws IOException { - var jsonResponse = - stringFromResources(Path.of(SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON)); - var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); - - when(mockedSearchClient.doSearch(any())).thenReturn(body); - } - - private InputStream getInputStreamWithAccessRight( - URI currentCustomer, URI topLevelCristinOrgId, AccessRight accessRight) - throws JsonProcessingException { - return new HandlerRequestBuilder(objectMapperWithEmpty) - .withHeaders(Map.of(ACCEPT, "application/json")) - .withRequestContext(getRequestContext()) - .withUserName(randomString()) - .withCurrentCustomer(currentCustomer) - .withTopLevelCristinOrgId(topLevelCristinOrgId) - .withAccessRights(currentCustomer, accessRight) - .build(); - } - - private ObjectNode getRequestContext() { - return objectMapperWithEmpty.convertValue( - Map.of("path", SAMPLE_PATH, "domainName", SAMPLE_DOMAIN_NAME), ObjectNode.class); - } + public static final String SAMPLE_PATH = "search"; + public static final String SAMPLE_DOMAIN_NAME = "localhost"; + public static final String SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON = + "sample_opensearch_response.json"; + private SearchResourceAuthHandler handler; + private Context contextMock; + private ByteArrayOutputStream outputStream; + private ResourceClient mockedSearchClient; + + @BeforeEach + void setUp() { + + mockedSearchClient = mock(ResourceClient.class); + handler = new SearchResourceAuthHandler(new Environment(), mockedSearchClient); + contextMock = mock(Context.class); + outputStream = new ByteArrayOutputStream(); + } + + @Test + void shouldOnlyReturnPublicationsFromCuratorsOrganizationWhenQuerying() + throws IOException, URISyntaxException { + prepareRestHighLevelClientOkResponse(); + + var customer = + new URI("https://api.dev.nva.aws.unit.no/customer/f54c8aa9-073a-46a1-8f7c-dde66c853934"); + var curatorOrganization = + new URI("https://api.dev.nva.aws.unit.no/cristin/organization/184.0.0.0"); + + handler.handleRequest( + getInputStreamWithAccessRight( + customer, curatorOrganization, AccessRight.MANAGE_RESOURCES_ALL), + outputStream, + contextMock); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + var actualBody = gatewayResponse.body(); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_OK, gatewayResponse.statusCode()); + assertThat(actualBody.hits().size(), is(equalTo(2))); + } + + @Test + void shouldReturnOkWhenUserIsEditor() throws IOException { + prepareRestHighLevelClientOkResponse(); + + var input = + getInputStreamWithAccessRight(randomUri(), randomUri(), AccessRight.MANAGE_RESOURCES_ALL); + handler.handleRequest(input, outputStream, contextMock); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_OK, gatewayResponse.statusCode()); + } + + @Test + void shouldReturnUnauthorizedWhenUserIsMissingAccessRight() throws IOException { + prepareRestHighLevelClientOkResponse(); + + var input = getInputStreamWithAccessRight(randomUri(), randomUri(), AccessRight.SUPPORT); + handler.handleRequest(input, outputStream, contextMock); + + var gatewayResponse = FakeGatewayResponse.of(outputStream); + + assertNotNull(gatewayResponse.headers()); + assertEquals(HTTP_UNAUTHORIZED, gatewayResponse.statusCode()); + } + + private void prepareRestHighLevelClientOkResponse() throws IOException { + var jsonResponse = + stringFromResources(Path.of(SAMPLE_OPENSEARCH_RESPONSE_WITH_AGGREGATION_JSON)); + var body = objectMapperWithEmpty.readValue(jsonResponse, SwsResponse.class); + + when(mockedSearchClient.doSearch(any())).thenReturn(body); + } + + private InputStream getInputStreamWithAccessRight( + URI currentCustomer, URI topLevelCristinOrgId, AccessRight accessRight) + throws JsonProcessingException { + return new HandlerRequestBuilder(objectMapperWithEmpty) + .withHeaders(Map.of(ACCEPT, "application/json")) + .withRequestContext(getRequestContext()) + .withUserName(randomString()) + .withCurrentCustomer(currentCustomer) + .withTopLevelCristinOrgId(topLevelCristinOrgId) + .withAccessRights(currentCustomer, accessRight) + .build(); + } + + private ObjectNode getRequestContext() { + return objectMapperWithEmpty.convertValue( + Map.of("path", SAMPLE_PATH, "domainName", SAMPLE_DOMAIN_NAME), ObjectNode.class); + } } diff --git a/search-handlers/src/test/java/no/unit/nva/search/SearchResourceLegacyHandlerTest.java b/search-handlers/src/test/java/no/unit/nva/search/SearchResourceLegacyHandlerTest.java index 2238342fb..7b70879ff 100644 --- a/search-handlers/src/test/java/no/unit/nva/search/SearchResourceLegacyHandlerTest.java +++ b/search-handlers/src/test/java/no/unit/nva/search/SearchResourceLegacyHandlerTest.java @@ -1,5 +1,7 @@ package no.unit.nva.search; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.util.Objects.nonNull; import static no.unit.nva.auth.uriretriever.UriRetriever.ACCEPT; import static no.unit.nva.constants.Defaults.objectMapperWithEmpty; import static no.unit.nva.constants.Words.COMMA; @@ -7,9 +9,7 @@ import static no.unit.nva.search.resource.ResourceParameter.SEARCH_ALL; import static no.unit.nva.testutils.RandomDataGenerator.randomString; import static no.unit.nva.testutils.RandomDataGenerator.randomUri; - import static nva.commons.core.ioutils.IoUtils.stringFromResources; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsEmptyCollection.empty; @@ -22,13 +22,16 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static java.net.HttpURLConnection.HTTP_OK; -import static java.util.Objects.nonNull; - import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import no.unit.nva.constants.Words; import no.unit.nva.indexing.testutils.FakeSearchResponse; import no.unit.nva.search.common.FakeGatewayResponse; @@ -36,23 +39,13 @@ import no.unit.nva.search.common.records.SwsResponse; import no.unit.nva.search.resource.ResourceClient; import no.unit.nva.testutils.HandlerRequestBuilder; - import nva.commons.apigateway.GatewayResponse; import nva.commons.core.Environment; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - class SearchResourceLegacyHandlerTest { public static final String SAMPLE_PATH = "search"; diff --git a/search-handlers/src/test/java/no/unit/nva/search/SearchTicketAuthHandlerTest.java b/search-handlers/src/test/java/no/unit/nva/search/SearchTicketAuthHandlerTest.java index 95a23f1d2..069207759 100644 --- a/search-handlers/src/test/java/no/unit/nva/search/SearchTicketAuthHandlerTest.java +++ b/search-handlers/src/test/java/no/unit/nva/search/SearchTicketAuthHandlerTest.java @@ -1,12 +1,11 @@ package no.unit.nva.search; +import static java.net.HttpURLConnection.HTTP_OK; import static no.unit.nva.auth.uriretriever.UriRetriever.ACCEPT; import static no.unit.nva.constants.Defaults.objectMapperWithEmpty; import static no.unit.nva.testutils.RandomDataGenerator.randomString; import static no.unit.nva.testutils.RandomDataGenerator.randomUri; - import static nva.commons.core.ioutils.IoUtils.stringFromResources; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -16,31 +15,25 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static java.net.HttpURLConnection.HTTP_OK; - import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; - +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.Map; import no.unit.nva.search.common.FakeGatewayResponse; import no.unit.nva.search.common.records.SwsResponse; import no.unit.nva.search.ticket.TicketClient; import no.unit.nva.testutils.HandlerRequestBuilder; - import nva.commons.apigateway.AccessRight; import nva.commons.core.Environment; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.util.Map; - class SearchTicketAuthHandlerTest { public static final String SAMPLE_PATH = "search"; diff --git a/search-testing/src/main/java/no/unit/nva/LogAppender.java b/search-testing/src/main/java/no/unit/nva/LogAppender.java index f165fc3c1..81c4833f3 100644 --- a/search-testing/src/main/java/no/unit/nva/LogAppender.java +++ b/search-testing/src/main/java/no/unit/nva/LogAppender.java @@ -17,42 +17,42 @@ @SuppressWarnings({"PMD.CloseResource"}) public final class LogAppender { - @JacocoGenerated - private LogAppender() {} - - public static String logToString(ListAppender appender) { - return String.join(SPACE, eventsToStrings(appender)); + @JacocoGenerated + private LogAppender() {} + + public static String logToString(ListAppender appender) { + return String.join(SPACE, eventsToStrings(appender)); + } + + public static ListAppender getAppender(Class clazz) { + + var loggerContext = LoggerContext.getContext(false); + if (loggerContext.getConfiguration().getAppenders().containsKey(clazz.getSimpleName())) { + return (ListAppender) + loggerContext.getConfiguration().getAppenders().get(clazz.getSimpleName()); + } else { + logHasStarted(clazz); + var logger = (Logger) loggerContext.getLogger(clazz); + var appender = new ListAppender(clazz.getSimpleName()); + appender.start(); + loggerContext.getConfiguration().addLoggerAppender(logger, appender); + return appender; } + } - public static ListAppender getAppender(Class clazz) { - - var loggerContext = LoggerContext.getContext(false); - if (loggerContext.getConfiguration().getAppenders().containsKey(clazz.getSimpleName())) { - return (ListAppender) - loggerContext.getConfiguration().getAppenders().get(clazz.getSimpleName()); - } else { - logHasStarted(clazz); - var logger = (Logger) loggerContext.getLogger(clazz); - var appender = new ListAppender(clazz.getSimpleName()); - appender.start(); - loggerContext.getConfiguration().addLoggerAppender(logger, appender); - return appender; - } - } + private static void logHasStarted(Class clazz) { + var logAppenderlogger = LoggerFactory.getLogger(LogAppender.class); + logAppenderlogger.info("Getting appender for class: {}", clazz.getSimpleName()); + } - private static void logHasStarted(Class clazz) { - var logAppenderlogger = LoggerFactory.getLogger(LogAppender.class); - logAppenderlogger.info("Getting appender for class: {}", clazz.getSimpleName()); - } - - private static List eventsToStrings(ListAppender appender) { - return appender.getEvents().stream().map(LogAppender::eventToString).toList(); - } + private static List eventsToStrings(ListAppender appender) { + return appender.getEvents().stream().map(LogAppender::eventToString).toList(); + } - private static String eventToString(LogEvent event) { - if (nonNull(event.getThrown())) { - return event.getMessage() + SPACE + event.getThrown().getMessage(); - } - return event.getMessage().getFormattedMessage(); + private static String eventToString(LogEvent event) { + if (nonNull(event.getThrown())) { + return event.getMessage() + SPACE + event.getThrown().getMessage(); } + return event.getMessage().getFormattedMessage(); + } } diff --git a/search-testing/src/main/java/no/unit/nva/indexing/testutils/Constants.java b/search-testing/src/main/java/no/unit/nva/indexing/testutils/Constants.java index 54484e7d1..6d2510abc 100644 --- a/search-testing/src/main/java/no/unit/nva/indexing/testutils/Constants.java +++ b/search-testing/src/main/java/no/unit/nva/indexing/testutils/Constants.java @@ -4,13 +4,13 @@ @SuppressWarnings("PMD.ModifierOrder") public final class Constants { - public static final String TEST_SCOPE = "example-scope"; - public static final String TEST_TOKEN = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2Njg1MTE4NTcsImV4cCI6MTcw" - + "MDA0Nzg1NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ue" - + "SIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjoiTWFuYWdlciIsInNjb3BlIjoiZX" - + "hhbXBsZS1zY29wZSJ9.ne8Jb4f2xao1zSJFZxIBRrh4WFNjkaBRV3-Ybp6fHZU"; + public static final String TEST_SCOPE = "example-scope"; + public static final String TEST_TOKEN = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2Njg1MTE4NTcsImV4cCI6MTcw" + + "MDA0Nzg1NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ue" + + "SIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjoiTWFuYWdlciIsInNjb3BlIjoiZX" + + "hhbXBsZS1zY29wZSJ9.ne8Jb4f2xao1zSJFZxIBRrh4WFNjkaBRV3-Ybp6fHZU"; - @JacocoGenerated - private Constants() {} + @JacocoGenerated + private Constants() {} } diff --git a/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeIndexingClient.java b/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeIndexingClient.java index 605ac4edd..458698b84 100644 --- a/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeIndexingClient.java +++ b/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeIndexingClient.java @@ -29,66 +29,65 @@ */ public class FakeIndexingClient extends IndexingClient { - private static final long IGNORED_PROCESSING_TIME = 0; - private final Map> indexContents; - - public FakeIndexingClient() { - super(null, null); - indexContents = new ConcurrentHashMap<>(); - } - - @Override - public Void addDocumentToIndex(IndexDocument indexDocument) throws IOException { - if (!indexContents.containsKey(indexDocument.getIndexName())) { - indexContents.put(indexDocument.getIndexName(), new HashMap<>()); - } - - indexContents - .get(indexDocument.getIndexName()) - .put(indexDocument.getDocumentIdentifier(), indexDocument.resource()); - return null; - } - - @Override - public void removeDocumentFromResourcesIndex(String identifier) throws IOException { - indexContents.forEach((index, set) -> removeDocument(set, identifier)); - } - - @Override - public void removeDocumentFromImportCandidateIndex(String identifier) throws IOException { - indexContents.forEach((index, set) -> removeDocument(set, identifier)); + private static final long IGNORED_PROCESSING_TIME = 0; + private final Map> indexContents; + + public FakeIndexingClient() { + super(null, null); + indexContents = new ConcurrentHashMap<>(); + } + + @Override + public Void addDocumentToIndex(IndexDocument indexDocument) throws IOException { + if (!indexContents.containsKey(indexDocument.getIndexName())) { + indexContents.put(indexDocument.getIndexName(), new HashMap<>()); } - @Override - public Stream batchInsert(Stream indexDocuments) { - var collectedDocuments = indexDocuments.collect(Collectors.toList()); - for (IndexDocument collectedDocument : collectedDocuments) { - attempt(() -> addDocumentToIndex(collectedDocument)).orElseThrow(); - } - - return constructSampleBulkResponse(collectedDocuments).stream(); - } - - public Set getIndex(String indexName) { - return new HashSet<>(this.indexContents.getOrDefault(indexName, new HashMap<>()).values()); - } - - public Set listAllDocuments(String indexName) { - return new HashSet<>(this.indexContents.get(indexName).values()); - } - - private void removeDocument(Map jsonNodes, String identifier) { - jsonNodes.remove(identifier); + indexContents + .get(indexDocument.getIndexName()) + .put(indexDocument.getDocumentIdentifier(), indexDocument.resource()); + return null; + } + + @Override + public void removeDocumentFromResourcesIndex(String identifier) throws IOException { + indexContents.forEach((index, set) -> removeDocument(set, identifier)); + } + + @Override + public void removeDocumentFromImportCandidateIndex(String identifier) throws IOException { + indexContents.forEach((index, set) -> removeDocument(set, identifier)); + } + + @Override + public Stream batchInsert(Stream indexDocuments) { + var collectedDocuments = indexDocuments.collect(Collectors.toList()); + for (IndexDocument collectedDocument : collectedDocuments) { + attempt(() -> addDocumentToIndex(collectedDocument)).orElseThrow(); } - private List constructSampleBulkResponse( - Collection indexDocuments) { - DocWriteResponse response = null; - List responses = - indexDocuments.stream() - .map(doc -> new BulkItemResponse(doc.hashCode(), OpType.UPDATE, response)) - .toList(); - BulkItemResponse[] responsesArray = responses.toArray(BulkItemResponse[]::new); - return List.of(new BulkResponse(responsesArray, IGNORED_PROCESSING_TIME)); - } + return constructSampleBulkResponse(collectedDocuments).stream(); + } + + public Set getIndex(String indexName) { + return new HashSet<>(this.indexContents.getOrDefault(indexName, new HashMap<>()).values()); + } + + public Set listAllDocuments(String indexName) { + return new HashSet<>(this.indexContents.get(indexName).values()); + } + + private void removeDocument(Map jsonNodes, String identifier) { + jsonNodes.remove(identifier); + } + + private List constructSampleBulkResponse(Collection indexDocuments) { + DocWriteResponse response = null; + List responses = + indexDocuments.stream() + .map(doc -> new BulkItemResponse(doc.hashCode(), OpType.UPDATE, response)) + .toList(); + BulkItemResponse[] responsesArray = responses.toArray(BulkItemResponse[]::new); + return List.of(new BulkResponse(responsesArray, IGNORED_PROCESSING_TIME)); + } } diff --git a/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSearchResponse.java b/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSearchResponse.java index a99e0ca9e..658fdab82 100644 --- a/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSearchResponse.java +++ b/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSearchResponse.java @@ -11,76 +11,75 @@ public final class FakeSearchResponse { - public static final String COMMA_DELIMITER = ", "; - public static final Path SEARCH_RESPONSE_TEMPLATE = - Path.of("search_response_template_json.tmpl"); - public static final Path SEARCH_HIT_TEMPLATE = Path.of("publication_hit_template_json.tmpl"); - public static final Path CONTRIBUTOR_TEMPLATE = Path.of("contributor_json.tmpl"); + public static final String COMMA_DELIMITER = ", "; + public static final Path SEARCH_RESPONSE_TEMPLATE = Path.of("search_response_template_json.tmpl"); + public static final Path SEARCH_HIT_TEMPLATE = Path.of("publication_hit_template_json.tmpl"); + public static final Path CONTRIBUTOR_TEMPLATE = Path.of("contributor_json.tmpl"); - @JacocoGenerated - private FakeSearchResponse() {} + @JacocoGenerated + private FakeSearchResponse() {} - public static String generateSearchResponseString(List csv, String scrollId) { - var template = IoUtils.stringFromResources(SEARCH_RESPONSE_TEMPLATE); + public static String generateSearchResponseString(List csv, String scrollId) { + var template = IoUtils.stringFromResources(SEARCH_RESPONSE_TEMPLATE); - var hits = - csv.stream() - .map(FakeSearchResponse::generateHit) - .collect(Collectors.joining(COMMA_DELIMITER)); + var hits = + csv.stream() + .map(FakeSearchResponse::generateHit) + .collect(Collectors.joining(COMMA_DELIMITER)); - var scrollData = Objects.isNull(scrollId) ? "" : ",\"_scroll_id\":\"" + scrollId + "\""; - var templateWithScroll = template.replace("__SCROLL__", scrollData); - return templateWithScroll.replace("__HITS__", hits); - } + var scrollData = Objects.isNull(scrollId) ? "" : ",\"_scroll_id\":\"" + scrollId + "\""; + var templateWithScroll = template.replace("__SCROLL__", scrollData); + return templateWithScroll.replace("__HITS__", hits); + } - private static String generateHit(ExportCsv item) { - // Not efficient - var hitTemplate = IoUtils.stringFromResources(SEARCH_HIT_TEMPLATE); - var date = new ParsedDate(item.getPublicationDate()); - var contributors = createContributorArray(item); - return hitTemplate - .replace("__ID__", item.getId()) - .replace("__TYPE__", item.getPublicationInstance()) - .replace("__TITLE__", item.getMainTitle()) - .replace("__YEAR__", "\"" + date.getYear() + "\"") - .replace("__MONTH__", date.getMonth()) - .replace("__DAY__", date.getDay()) - .replace("__CONTRIBUTORS__", contributors); - } + private static String generateHit(ExportCsv item) { + // Not efficient + var hitTemplate = IoUtils.stringFromResources(SEARCH_HIT_TEMPLATE); + var date = new ParsedDate(item.getPublicationDate()); + var contributors = createContributorArray(item); + return hitTemplate + .replace("__ID__", item.getId()) + .replace("__TYPE__", item.getPublicationInstance()) + .replace("__TITLE__", item.getMainTitle()) + .replace("__YEAR__", "\"" + date.getYear() + "\"") + .replace("__MONTH__", date.getMonth()) + .replace("__DAY__", date.getDay()) + .replace("__CONTRIBUTORS__", contributors); + } - private static String createContributorArray(ExportCsv item) { - return Arrays.stream(item.getContributors().split(COMMA_DELIMITER)) - .map(FakeSearchResponse::createContributor) - .collect(Collectors.joining(COMMA_DELIMITER)); - } + private static String createContributorArray(ExportCsv item) { + return Arrays.stream(item.getContributors().split(COMMA_DELIMITER)) + .map(FakeSearchResponse::createContributor) + .collect(Collectors.joining(COMMA_DELIMITER)); + } - private static String createContributor(String name) { - var contributorTemplate = IoUtils.stringFromResources(CONTRIBUTOR_TEMPLATE); - return contributorTemplate.replace("__AUTHOR__", name); - } + private static String createContributor(String name) { + var contributorTemplate = IoUtils.stringFromResources(CONTRIBUTOR_TEMPLATE); + return contributorTemplate.replace("__AUTHOR__", name); + } - private static class ParsedDate { - private final String year; - private final String month; - private final String day; + private static class ParsedDate { + private final String year; + private final String month; + private final String day; - public ParsedDate(String publicationDate) { - var date = publicationDate.split("-"); - this.year = date[0]; - this.month = date.length > 1 ? "\"" + date[1] + "\"" : "null"; - this.day = date.length > 2 ? "\"" + date[2] + "\"" : "null"; - } + public ParsedDate(String publicationDate) { + var date = publicationDate.split("-"); + this.year = date[0]; + this.month = date.length > 1 ? "\"" + date[1] + "\"" : "null"; + this.day = date.length > 2 ? "\"" + date[2] + "\"" : "null"; + } - public String getYear() { - return year; - } + public String getYear() { + return year; + } - public String getMonth() { - return month; - } + public String getMonth() { + return month; + } - public String getDay() { - return day; - } + public String getDay() { + return day; } + } } diff --git a/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSqsClient.java b/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSqsClient.java index bb8ee8b7a..213aef10f 100644 --- a/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSqsClient.java +++ b/search-testing/src/main/java/no/unit/nva/indexing/testutils/FakeSqsClient.java @@ -1,22 +1,20 @@ package no.unit.nva.indexing.testutils; -import no.unit.nva.indexingclient.models.QueueClient; - -import software.amazon.awssdk.services.sqs.model.SendMessageRequest; - import java.util.ArrayList; import java.util.List; +import no.unit.nva.indexingclient.models.QueueClient; +import software.amazon.awssdk.services.sqs.model.SendMessageRequest; public class FakeSqsClient implements QueueClient { - private final List deliveredMessages = new ArrayList<>(); + private final List deliveredMessages = new ArrayList<>(); - public List getDeliveredMessages() { - return deliveredMessages; - } + public List getDeliveredMessages() { + return deliveredMessages; + } - @Override - public void sendMessage(SendMessageRequest sendMessageRequest) { - deliveredMessages.add(sendMessageRequest); - } + @Override + public void sendMessage(SendMessageRequest sendMessageRequest) { + deliveredMessages.add(sendMessageRequest); + } } diff --git a/search-testing/src/main/java/no/unit/nva/indexing/testutils/MockedJwtProvider.java b/search-testing/src/main/java/no/unit/nva/indexing/testutils/MockedJwtProvider.java index 2254440df..170492f64 100644 --- a/search-testing/src/main/java/no/unit/nva/indexing/testutils/MockedJwtProvider.java +++ b/search-testing/src/main/java/no/unit/nva/indexing/testutils/MockedJwtProvider.java @@ -1,20 +1,16 @@ package no.unit.nva.indexing.testutils; import static no.unit.nva.indexing.testutils.Constants.TEST_TOKEN; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.auth0.jwt.interfaces.DecodedJWT; - -import no.unit.nva.search.common.jwt.CachedJwtProvider; -import no.unit.nva.search.common.jwt.CognitoAuthenticator; - -import nva.commons.core.JacocoGenerated; - import java.time.Duration; import java.time.Instant; import java.util.Date; +import no.unit.nva.search.common.jwt.CachedJwtProvider; +import no.unit.nva.search.common.jwt.CognitoAuthenticator; +import nva.commons.core.JacocoGenerated; public final class MockedJwtProvider { diff --git a/search-testing/src/main/java/no/unit/nva/search/common/FakeGatewayResponse.java b/search-testing/src/main/java/no/unit/nva/search/common/FakeGatewayResponse.java index ec4587192..2295c46f4 100644 --- a/search-testing/src/main/java/no/unit/nva/search/common/FakeGatewayResponse.java +++ b/search-testing/src/main/java/no/unit/nva/search/common/FakeGatewayResponse.java @@ -4,27 +4,24 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; - -import no.unit.nva.search.common.records.PagedSearch; - import java.io.IOException; import java.io.OutputStream; import java.util.Map; +import no.unit.nva.search.common.records.PagedSearch; @SuppressWarnings("PMD.ShortMethodName") public record FakeGatewayResponse(T body, int statusCode, Map headers) { - public static FakeGatewayResponse of(OutputStream outputStream) - throws IOException { - var response = ofString(outputStream); - var typeReference2 = new TypeReference() {}; - var body = dtoObjectMapper.readValue(response.body(), typeReference2); - return new FakeGatewayResponse<>(body, response.statusCode(), response.headers()); - } + public static FakeGatewayResponse of(OutputStream outputStream) throws IOException { + var response = ofString(outputStream); + var typeReference2 = new TypeReference() {}; + var body = dtoObjectMapper.readValue(response.body(), typeReference2); + return new FakeGatewayResponse<>(body, response.statusCode(), response.headers()); + } - private static FakeGatewayResponse ofString(OutputStream outputStream) - throws JsonProcessingException { - var typeReference = new TypeReference>() {}; - return dtoObjectMapper.readValue(outputStream.toString(), typeReference); - } + private static FakeGatewayResponse ofString(OutputStream outputStream) + throws JsonProcessingException { + var typeReference = new TypeReference>() {}; + return dtoObjectMapper.readValue(outputStream.toString(), typeReference); + } }