diff --git a/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateClient.java b/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateClient.java index d26fcfa41..de39eda76 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateClient.java +++ b/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateClient.java @@ -14,7 +14,7 @@ import java.nio.charset.StandardCharsets; import no.unit.nva.search.CachedJwtProvider; import no.unit.nva.search2.common.OpenSearchClient; -import no.unit.nva.search2.common.QueryBuilderSourceWrapper; +import no.unit.nva.search2.common.QueryContentWrapper; import no.unit.nva.search2.common.SwsResponse; import nva.commons.core.JacocoGenerated; import nva.commons.secrets.SecretsReader; @@ -55,7 +55,7 @@ public SwsResponse doSearch(ImportCandidateQuery query) { } @JacocoGenerated - private HttpRequest createRequest(QueryBuilderSourceWrapper qbs) { + private HttpRequest createRequest(QueryContentWrapper qbs) { logger.info(qbs.source().query().toString()); return HttpRequest .newBuilder(qbs.requestUri()) diff --git a/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateQuery.java b/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateQuery.java index 489e2bed8..5f16c7bff 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search2/ImportCandidateQuery.java @@ -1,5 +1,25 @@ package no.unit.nva.search2; +import no.unit.nva.search2.common.Query; +import no.unit.nva.search2.common.QueryBuilder; +import no.unit.nva.search2.common.QueryContentWrapper; +import no.unit.nva.search2.enums.ImportCandidateParameter; +import no.unit.nva.search2.enums.ImportCandidateSort; +import no.unit.nva.search2.enums.ParameterKey; +import nva.commons.apigateway.exceptions.BadRequestException; +import nva.commons.core.JacocoGenerated; +import org.opensearch.common.collect.Tuple; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.search.sort.SortOrder; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import static no.unit.nva.search2.constant.Defaults.DEFAULT_IMPORT_CANDIDATE_SORT; @@ -7,7 +27,6 @@ import static no.unit.nva.search2.constant.Defaults.DEFAULT_SORT_ORDER; import static no.unit.nva.search2.constant.Defaults.DEFAULT_VALUE_PER_PAGE; import static no.unit.nva.search2.constant.ErrorMessages.INVALID_VALUE_WITH_SORT; -import static no.unit.nva.search2.constant.ErrorMessages.UNEXPECTED_VALUE; import static no.unit.nva.search2.constant.ImportCandidate.IMPORT_CANDIDATES_AGGREGATIONS; import static no.unit.nva.search2.constant.ImportCandidate.IMPORT_CANDIDATES_INDEX_NAME; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_IGNORE_CASE; @@ -19,11 +38,9 @@ import static no.unit.nva.search2.constant.Words.KEYWORD; import static no.unit.nva.search2.constant.Words.SEARCH; import static no.unit.nva.search2.constant.Words.ZERO; -import static no.unit.nva.search2.enums.ImportCandidateParameter.FIELDS; import static no.unit.nva.search2.enums.ImportCandidateParameter.FROM; import static no.unit.nva.search2.enums.ImportCandidateParameter.PAGE; import static no.unit.nva.search2.enums.ImportCandidateParameter.SEARCH_AFTER; -import static no.unit.nva.search2.enums.ImportCandidateParameter.SEARCH_ALL; import static no.unit.nva.search2.enums.ImportCandidateParameter.SIZE; import static no.unit.nva.search2.enums.ImportCandidateParameter.SORT; import static no.unit.nva.search2.enums.ImportCandidateParameter.SORT_ORDER; @@ -32,36 +49,6 @@ import static no.unit.nva.search2.enums.ImportCandidateSort.INVALID; import static no.unit.nva.search2.enums.ImportCandidateSort.validSortKeys; import static nva.commons.core.paths.UriWrapper.fromUri; -import com.google.common.net.MediaType; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import no.unit.nva.search.CsvTransformer; -import no.unit.nva.search2.common.Query; -import no.unit.nva.search2.common.QueryBuilder; -import no.unit.nva.search2.common.QueryBuilderSourceWrapper; -import no.unit.nva.search2.common.QueryBuilderTools; -import no.unit.nva.search2.common.SwsResponse; -import no.unit.nva.search2.dto.PagedSearch; -import no.unit.nva.search2.dto.PagedSearchBuilder; -import no.unit.nva.search2.enums.ImportCandidateParameter; -import no.unit.nva.search2.enums.ImportCandidateSort; -import no.unit.nva.search2.enums.ParameterKey; -import nva.commons.apigateway.exceptions.BadRequestException; -import nva.commons.core.JacocoGenerated; -import org.jetbrains.annotations.NotNull; -import org.opensearch.common.collect.Tuple; -import org.opensearch.index.query.BoolQueryBuilder; -import org.opensearch.index.query.MultiMatchQueryBuilder; -import org.opensearch.index.query.MultiMatchQueryBuilder.Type; -import org.opensearch.index.query.Operator; -import org.opensearch.index.query.QueryBuilders; -import org.opensearch.search.builder.SearchSourceBuilder; -import org.opensearch.search.sort.SortOrder; public final class ImportCandidateQuery extends Query { @@ -73,153 +60,81 @@ static Builder builder() { return new Builder(); } - @Override - public URI getOpenSearchUri() { - return - fromUri(openSearchUri) - .addChild(IMPORT_CANDIDATES_INDEX_NAME, SEARCH) - .getUri(); - } - - public String doSearch(ImportCandidateClient queryClient) { - final var response = queryClient.doSearch(this); - return MediaType.CSV_UTF_8.is(this.getMediaType()) - ? toCsvText(response) - : toPagedResponse(response).toJsonString(); - } - - private String toCsvText(SwsResponse response) { - return CsvTransformer.transform(response.getSearchHits()); - } - - PagedSearch toPagedResponse(SwsResponse response) { - final var requestParameter = toNvaSearchApiRequestParameter(); - final var source = URI.create(getNvaSearchApiUri().toString().split("\\?")[0]); - - return - new PagedSearchBuilder() - .withTotalHits(response.getTotalSize()) - .withHits(response.getSearchHits()) - .withIds(source, requestParameter, getValue(FROM).as(), getValue(SIZE).as()) - .withNextResultsBySortKey(nextResultsBySortKey(response, requestParameter, source)) - .withAggregations(response.getAggregationsStructured()) - .build(); - } - - public Stream createQueryBuilderStream() { + public Stream createQueryBuilderStream() { var queryBuilder = this.hasNoSearchValue() ? QueryBuilders.matchAllQuery() : boolQuery(); - + var builder = new SearchSourceBuilder().query(queryBuilder); - + var searchAfter = removeKey(SEARCH_AFTER); if (nonNull(searchAfter)) { var sortKeys = searchAfter.split(COMMA); builder.searchAfter(sortKeys); } - + if (isFirstPage()) { IMPORT_CANDIDATES_AGGREGATIONS.forEach(builder::aggregation); } - + builder.size(getValue(SIZE).as()); builder.from(getValue(FROM).as()); - getSortStream().forEach(orderTuple -> builder.sort(orderTuple.v1(), orderTuple.v2())); - - return Stream.of(new QueryBuilderSourceWrapper(builder, this.getOpenSearchUri())); - } - - /** - * Creates a boolean query, with all the search parameters. - * - * @return a BoolQueryBuilder - */ - @SuppressWarnings({"PMD.SwitchStmtsShouldHaveDefault"}) - private BoolQueryBuilder boolQuery() { - var bq = QueryBuilders.boolQuery(); - getOpenSearchParameters() - .forEach((key, value) -> { - if (key.equals(SEARCH_ALL)) { - bq.must(multiMatchQuery()); - } else if (key.fieldType().equals(ParameterKey.ParamKind.KEYWORD)) { - QueryBuilderTools.addKeywordQuery(key, value, bq); - } else { - switch (key.searchOperator()) { - case MUST -> bq.must(QueryBuilderTools.buildQuery(key, value)); - case MUST_NOT -> bq.mustNot(QueryBuilderTools.buildQuery(key, value)); - case SHOULD -> bq.should(QueryBuilderTools.buildQuery(key, value)); - case GREATER_THAN_OR_EQUAL_TO, LESS_THAN -> bq.must(QueryBuilderTools.rangeQuery(key, value)); - default -> throw new IllegalStateException(UNEXPECTED_VALUE + key.searchOperator()); - } - } - }); - return bq; + getSortStream(SORT).forEach(orderTuple -> builder.sort(orderTuple.v1(), orderTuple.v2())); + + return Stream.of(new QueryContentWrapper(builder, this.getOpenSearchUri())); } - - @NotNull - private Stream> getSortStream() { + + @Override + public URI getOpenSearchUri() { return - getOptional(SORT).stream() - .flatMap(sort -> Arrays.stream(sort.split(COMMA))) - .map(sort -> sort.split(COLON)) - .map(this::expandSortKeys); + fromUri(openSearchUri) + .addChild(IMPORT_CANDIDATES_INDEX_NAME, SEARCH) + .getUri(); + } + + @Override + protected ImportCandidateParameter getFieldsKey() { + return getValue(ImportCandidateParameter.FIELDS).getKey(); } - public static URI nextResultsBySortKey( - SwsResponse response, Map requestParameter, URI gatewayUri) { + @Override + protected String[] fieldsToKeyNames(String field) { + return ALL.equals(field) || isNull(field) + ? ASTERISK.split(COMMA) + : Arrays.stream(field.split(COMMA)) + .map(ImportCandidateParameter::keyFromString) + .map(ParameterKey::searchFields) + .flatMap(Collection::stream) + .map(fieldPath -> fieldPath.replace(DOT + KEYWORD, "")) + .toArray(String[]::new); - requestParameter.remove(FROM.fieldName()); - var sortedP = - response.getSort().stream() - .map(Object::toString) - .collect(Collectors.joining(COMMA)); - requestParameter.put(SEARCH_AFTER.fieldName(), sortedP); - return fromUri(gatewayUri) - .addQueryParameters(requestParameter) - .getUri(); } - Tuple expandSortKeys(String... strings) { - var sortOrder = strings.length == 2 ? SortOrder.fromString(strings[1]) : SortOrder.ASC; - var fieldName = ImportCandidateSort.fromSortKey(strings[0]).getFieldName(); - return new Tuple<>(fieldName, sortOrder); + @Override + protected boolean isFirstPage() { + return ZERO.equals(getValue(FROM).toString()); } - /** - * Creates a multi match query, all words needs to be present, within a document. - * - * @return a MultiMatchQueryBuilder - */ - private MultiMatchQueryBuilder multiMatchQuery() { - var fields = extractFields(getValue(FIELDS).toString()); - var value = getValue(SEARCH_ALL).toString(); - return QueryBuilders - .multiMatchQuery(value, fields) - .type(Type.CROSS_FIELDS) - .operator(Operator.AND); + @Override + protected Integer getFrom() { + return getValue(FROM).as(); } - - boolean isFirstPage() { - return ZERO.equals(getValue(FROM).toString()); + + @Override + protected Integer getSize() { + return getValue(SIZE).as(); } - @NotNull - public static String[] extractFields(String field) { - return ALL.equals(field) || isNull(field) - ? ASTERISK.split(COMMA) - : Arrays.stream(field.split(COMMA)) - .map(ImportCandidateParameter::keyFromString) - .map(ParameterKey::searchFields) - .flatMap(Collection::stream) - .map(fieldPath -> fieldPath.replace(DOT + KEYWORD, "")) - .map(String::strip) - .toArray(String[]::new); + @Override + protected Tuple expandSortKeys(String... strings) { + var sortOrder = strings.length == 2 ? SortOrder.fromString(strings[1]) : SortOrder.ASC; + var fieldName = ImportCandidateSort.fromSortKey(strings[0]).getFieldName(); + return new Tuple<>(fieldName, sortOrder); } - + @SuppressWarnings("PMD.GodClass") protected static class Builder extends QueryBuilder { diff --git a/search-commons/src/main/java/no/unit/nva/search2/ResourceClient.java b/search-commons/src/main/java/no/unit/nva/search2/ResourceClient.java index 584b6e151..e85544092 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/ResourceClient.java +++ b/search-commons/src/main/java/no/unit/nva/search2/ResourceClient.java @@ -14,7 +14,7 @@ import java.nio.charset.StandardCharsets; import no.unit.nva.search.CachedJwtProvider; import no.unit.nva.search2.common.OpenSearchClient; -import no.unit.nva.search2.common.QueryBuilderSourceWrapper; +import no.unit.nva.search2.common.QueryContentWrapper; import no.unit.nva.search2.common.SwsResponse; import nva.commons.core.JacocoGenerated; import nva.commons.secrets.SecretsReader; @@ -58,7 +58,7 @@ public SwsResponse doSearch(ResourceQuery query) { @JacocoGenerated - private HttpRequest createRequest(QueryBuilderSourceWrapper qbs) { + private HttpRequest createRequest(QueryContentWrapper qbs) { logger.info(qbs.source().query().toString()); return HttpRequest .newBuilder(qbs.requestUri()) diff --git a/search-commons/src/main/java/no/unit/nva/search2/ResourceQuery.java b/search-commons/src/main/java/no/unit/nva/search2/ResourceQuery.java index c10efeec5..6aae06fda 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/ResourceQuery.java +++ b/search-commons/src/main/java/no/unit/nva/search2/ResourceQuery.java @@ -1,5 +1,27 @@ package no.unit.nva.search2; +import no.unit.nva.search2.common.Query; +import no.unit.nva.search2.common.QueryBuilder; +import no.unit.nva.search2.common.QueryContentWrapper; +import no.unit.nva.search2.enums.ParameterKey; +import no.unit.nva.search2.enums.ResourceParameter; +import no.unit.nva.search2.enums.ResourceSort; +import nva.commons.apigateway.exceptions.BadRequestException; +import nva.commons.core.JacocoGenerated; +import org.jetbrains.annotations.NotNull; +import org.opensearch.common.collect.Tuple; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.search.sort.SortOrder; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import static no.unit.nva.search2.constant.Defaults.DEFAULT_OFFSET; @@ -7,11 +29,9 @@ import static no.unit.nva.search2.constant.Defaults.DEFAULT_SORT_ORDER; import static no.unit.nva.search2.constant.Defaults.DEFAULT_VALUE_PER_PAGE; import static no.unit.nva.search2.constant.ErrorMessages.INVALID_VALUE_WITH_SORT; -import static no.unit.nva.search2.constant.ErrorMessages.UNEXPECTED_VALUE; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_ASC_OR_DESC_GROUP; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_SELECTED_GROUP; -import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_URL_PARAM_INDICATOR; -import static no.unit.nva.search2.constant.Resource.RESOURCES_AGGREGATIONS; +import static no.unit.nva.search2.constant.ResourcePaths.RESOURCES_AGGREGATIONS; import static no.unit.nva.search2.constant.Words.ALL; import static no.unit.nva.search2.constant.Words.ASTERISK; import static no.unit.nva.search2.constant.Words.COLON; @@ -26,50 +46,15 @@ import static no.unit.nva.search2.enums.ResourceParameter.FUNDING; import static no.unit.nva.search2.enums.ResourceParameter.PAGE; import static no.unit.nva.search2.enums.ResourceParameter.SEARCH_AFTER; -import static no.unit.nva.search2.enums.ResourceParameter.SEARCH_ALL; import static no.unit.nva.search2.enums.ResourceParameter.SIZE; import static no.unit.nva.search2.enums.ResourceParameter.SORT; import static no.unit.nva.search2.enums.ResourceParameter.SORT_ORDER; import static no.unit.nva.search2.enums.ResourceParameter.keyFromString; import static no.unit.nva.search2.enums.ResourceSort.INVALID; import static no.unit.nva.search2.enums.ResourceSort.validSortKeys; -import com.google.common.net.MediaType; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; -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.search.CsvTransformer; -import no.unit.nva.search2.common.Query; -import no.unit.nva.search2.common.QueryBuilder; -import no.unit.nva.search2.common.QueryBuilderSourceWrapper; -import no.unit.nva.search2.common.QueryBuilderTools; -import no.unit.nva.search2.common.SwsResponse; -import no.unit.nva.search2.dto.PagedSearch; -import no.unit.nva.search2.dto.PagedSearchBuilder; -import no.unit.nva.search2.enums.ParameterKey; -import no.unit.nva.search2.enums.ResourceParameter; -import no.unit.nva.search2.enums.ResourceSort; -import nva.commons.apigateway.exceptions.BadRequestException; -import nva.commons.core.JacocoGenerated; -import nva.commons.core.paths.UriWrapper; -import org.jetbrains.annotations.NotNull; -import org.opensearch.common.collect.Tuple; -import org.opensearch.index.query.BoolQueryBuilder; -import org.opensearch.index.query.MultiMatchQueryBuilder; -import org.opensearch.index.query.MultiMatchQueryBuilder.Type; -import org.opensearch.index.query.Operator; -import org.opensearch.index.query.QueryBuilders; -import org.opensearch.search.builder.SearchSourceBuilder; -import org.opensearch.search.sort.SortOrder; public final class ResourceQuery extends Query { - - private ResourceQuery() { super(); } @@ -78,32 +63,7 @@ static Builder builder() { return new Builder(); } - public String doSearch(ResourceClient queryClient) { - final var response = queryClient.doSearch(this); - return MediaType.CSV_UTF_8.is(this.getMediaType()) - ? toCsvText(response) - : toPagedResponse(response).toJsonString(); - } - - private String toCsvText(SwsResponse response) { - return CsvTransformer.transform(response.getSearchHits()); - } - - PagedSearch toPagedResponse(SwsResponse response) { - final var requestParameter = toNvaSearchApiRequestParameter(); - final var source = URI.create(getNvaSearchApiUri().toString().split(PATTERN_IS_URL_PARAM_INDICATOR)[0]); - - return - new PagedSearchBuilder() - .withTotalHits(response.getTotalSize()) - .withHits(response.getSearchHits()) - .withIds(source, requestParameter, getValue(FROM).as(), getValue(SIZE).as()) - .withNextResultsBySortKey(nextResultsBySortKey(response, requestParameter, source)) - .withAggregations(response.getAggregationsStructured()) - .build(); - } - - public Stream createQueryBuilderStream(UserSettingsClient userSettingsClient) { + public Stream createQueryBuilderStream(UserSettingsClient userSettingsClient) { var queryBuilder = this.hasNoSearchValue() ? QueryBuilders.matchAllQuery() @@ -128,36 +88,9 @@ public Stream createQueryBuilderStream(UserSettingsCl builder.size(getValue(SIZE).as()); builder.from(getValue(FROM).as()); - getSortStream().forEach(orderTuple -> builder.sort(orderTuple.v1(), orderTuple.v2())); + getSortStream(SORT).forEach(orderTuple -> builder.sort(orderTuple.v1(), orderTuple.v2())); - return Stream.of(new QueryBuilderSourceWrapper(builder, this.getOpenSearchUri())); - } - - /** - * Creates a boolean query, with all the search parameters. - * - * @return a BoolQueryBuilder - */ - @SuppressWarnings({"PMD.SwitchStmtsShouldHaveDefault"}) - private BoolQueryBuilder boolQuery() { - var bq = QueryBuilders.boolQuery(); - getOpenSearchParameters() - .forEach((key, value) -> { - if (key.equals(SEARCH_ALL)) { - bq.must(multiMatchQuery()); - } else if (key.fieldType().equals(ParameterKey.ParamKind.KEYWORD)) { - QueryBuilderTools.addKeywordQuery(key, value, bq); - } else { - switch (key.searchOperator()) { - case MUST -> bq.must(QueryBuilderTools.buildQuery(key, value)); - case MUST_NOT -> bq.mustNot(QueryBuilderTools.buildQuery(key, value)); - case SHOULD -> bq.should(QueryBuilderTools.buildQuery(key, value)); - case GREATER_THAN_OR_EQUAL_TO, LESS_THAN -> bq.must(QueryBuilderTools.rangeQuery(key, value)); - default -> throw new IllegalStateException(UNEXPECTED_VALUE + key.searchOperator()); - } - } - }); - return bq; + return Stream.of(new QueryContentWrapper(builder, this.getOpenSearchUri())); } private void addPromotedPublications(UserSettingsClient userSettingsClient, BoolQueryBuilder bq) { @@ -174,54 +107,27 @@ private void addPromotedPublications(UserSettingsClient userSettingsClient, Bool } } - @NotNull - private Stream> getSortStream() { - return - getOptional(SORT).stream() - .flatMap(sort -> Arrays.stream(sort.split(COMMA))) - .map(sort -> sort.split(COLON)) - .map(this::expandSortKeys); - } - - /** - * Creates a multi match query, all words needs to be present, within a document. - * - * @return a MultiMatchQueryBuilder - */ - private MultiMatchQueryBuilder multiMatchQuery() { - var fields = extractFields(getValue(FIELDS).toString()); - var value = getValue(SEARCH_ALL).toString(); - return QueryBuilders - .multiMatchQuery(value, fields) - .type(Type.CROSS_FIELDS) - .operator(Operator.AND); - } - - public static URI nextResultsBySortKey( - SwsResponse response, Map requestParameter, URI gatewayUri) { - - requestParameter.remove(FROM.fieldName()); - var sortedP = - response.getSort().stream() - .map(Object::toString) - .collect(Collectors.joining(COMMA)); - requestParameter.put(SEARCH_AFTER.fieldName(), sortedP); - return UriWrapper.fromUri(gatewayUri) - .addQueryParameters(requestParameter) - .getUri(); + private boolean hasPromotedPublications(List promotedPublications) { + return nonNull(promotedPublications) && !promotedPublications.isEmpty(); } - @JacocoGenerated - Tuple expandSortKeys(String... strings) { + @Override + protected Tuple expandSortKeys(String... strings) { var sortOrder = strings.length == 2 ? SortOrder.fromString(strings[1]) : SortOrder.ASC; var fieldName = ResourceSort.fromSortKey(strings[0]).getFieldName(); return new Tuple<>(fieldName, sortOrder); } + @Override + protected ResourceParameter getFieldsKey() { + return getValue(FIELDS).getKey(); + } + @NotNull - public static String[] extractFields(String field) { + @Override + protected String[] fieldsToKeyNames(String field) { return ALL.equals(field) || isNull(field) - ? ASTERISK.split(COMMA) + ? ASTERISK.split(COMMA) // NONE or ALL -> ['*'] : Arrays.stream(field.split(COMMA)) .map(ResourceParameter::keyFromString) .map(ParameterKey::searchFields) @@ -230,21 +136,23 @@ public static String[] extractFields(String field) { } - public boolean hasPromotedPublications(List promotedPublications) { - return nonNull(promotedPublications) && !promotedPublications.isEmpty(); - } - + @Override public boolean isFirstPage() { return ZERO.equals(getValue(FROM).toString()); } - @SuppressWarnings("PMD.GodClass") - protected static class Builder extends - QueryBuilder { - - + @Override + protected Integer getFrom() { + return getValue(FROM).as(); + } + @Override + protected Integer getSize() { + return getValue(SIZE).as(); + } + @SuppressWarnings("PMD.GodClass") + protected static class Builder extends QueryBuilder { Builder() { super(new ResourceQuery()); } diff --git a/search-commons/src/main/java/no/unit/nva/search2/common/AggregationFormat.java b/search-commons/src/main/java/no/unit/nva/search2/common/AggregationFormat.java new file mode 100644 index 000000000..14851a754 --- /dev/null +++ b/search-commons/src/main/java/no/unit/nva/search2/common/AggregationFormat.java @@ -0,0 +1,115 @@ +package no.unit.nva.search2.common; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Streams; +import no.unit.nva.commons.json.JsonUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Optional; + +import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_IGNORE_CASE; +import static no.unit.nva.search2.constant.Words.COUNT; +import static no.unit.nva.search2.constant.Words.DOC_COUNT_ERROR_UPPER_BOUND; +import static no.unit.nva.search2.constant.Words.ENGLISH_CODE; +import static no.unit.nva.search2.constant.Words.LABELS; +import static no.unit.nva.search2.constant.Words.NAME; +import static no.unit.nva.search2.constant.Words.SUM_OTHER_DOC_COUNT; +import static nva.commons.core.StringUtils.EMPTY_STRING; + +public final class AggregationFormat { + public static JsonNode apply(JsonNode aggregations) { + + var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); + + Streams.stream(aggregations.fields()) + .filter(AggregationFormat::ignoreDocCountErrors) + .filter(AggregationFormat::ignoreSumOtherDoc) + .map(AggregationFormat::getJsonNodeEntry) + .forEach(entry -> { + if (LABELS.equals(entry.getKey())) { + outputAggregationNode.set(entry.getKey(), formatLabels(entry.getValue())); + } else if (NAME.equals(entry.getKey())) { + outputAggregationNode.set(LABELS, formatName(entry.getValue())); + } else if (entry.getValue().isValueNode()) { + outputAggregationNode.set(entry.getKey(), entry.getValue()); + } else if (entry.getValue().isArray()) { + var arrayNode = JsonUtils.dtoObjectMapper.createArrayNode(); + entry.getValue().forEach(element -> arrayNode.add(apply(element))); + outputAggregationNode.set(entry.getKey(), arrayNode); + } else { + outputAggregationNode.set(entry.getKey(), apply(entry.getValue())); + } + }); + return outputAggregationNode; + } + + @NotNull + private static Map.Entry getJsonNodeEntry(Map.Entry entry) { + return Map.entry(getNormalizedFieldName(entry.getKey()), getBucketOrValue(entry.getValue())); + } + + @NotNull + private static Map.Entry getNormalizedJsonNodeEntry(Map.Entry entry) { + return Map.entry(getNormalizedFieldName(entry.getKey()), entry.getValue()); + } + + private static boolean ignoreSumOtherDoc(Map.Entry item) { + return !item.getKey().matches(PATTERN_IS_IGNORE_CASE + SUM_OTHER_DOC_COUNT); + } + + private static boolean ignoreDocCountErrors(Map.Entry item) { + return !item.getKey().matches(PATTERN_IS_IGNORE_CASE + DOC_COUNT_ERROR_UPPER_BOUND); + } + + private static JsonNode getBucketOrValue(JsonNode node) { + if (node.at(Constants.ID_BUCKETS).isArray()) { + return node.at(Constants.ID_BUCKETS); + } + if (node.has(Constants.BUCKETS)) { + return node.at("/buckets"); + } + return node; + } + + private static JsonNode formatName(JsonNode nodeEntry) { + var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); + var keyValue = nodeEntry.at(Constants.BUCKETS_0_KEY); + outputAggregationNode.set(ENGLISH_CODE, keyValue); + return outputAggregationNode; + } + + private static JsonNode formatLabels(JsonNode value) { + var outputAggregationNode = JsonUtils.dtoObjectMapper.createObjectNode(); + + Streams.stream(value.fields()) + .filter(AggregationFormat::ignoreDocCountErrors) + .filter(AggregationFormat::ignoreSumOtherDoc) + .map(AggregationFormat::getNormalizedJsonNodeEntry) + .filter(entry -> !COUNT.equals(entry.getKey())) + .forEach(node -> { + var keyValue = node.getValue().at(Constants.BUCKETS_0_KEY); + outputAggregationNode.set(node.getKey(), keyValue); + }); + return outputAggregationNode; + } + + @NotNull + private static String getNormalizedFieldName(String fieldName) { + return Optional.ofNullable(Constants.AGGREGATION_FIELDS_TO_CHANGE.get(fieldName)) + .orElse(fieldName.replaceFirst(Constants.WORD_ENDING_WITH_HASHTAG_REGEX, EMPTY_STRING)); + } + + static final class Constants { + + public static final String BUCKETS_0_KEY = "/buckets/0/key"; + public static final String ID_BUCKETS = "/id/buckets"; + public static final String BUCKETS = "buckets"; + public static final String WORD_ENDING_WITH_HASHTAG_REGEX = "[A-za-z0-9]*#"; + + private static final Map AGGREGATION_FIELDS_TO_CHANGE = Map.of( + "docCount", COUNT, + "doc_count", COUNT); + } + +} diff --git a/search-commons/src/main/java/no/unit/nva/search2/common/Query.java b/search-commons/src/main/java/no/unit/nva/search2/common/Query.java index 892987e34..1e4d6c471 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/common/Query.java +++ b/search-commons/src/main/java/no/unit/nva/search2/common/Query.java @@ -1,20 +1,23 @@ package no.unit.nva.search2.common; -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; -import static no.unit.nva.search2.constant.Functions.readSearchInfrastructureApiUri; -import static no.unit.nva.search2.constant.Words.AMPERSAND; -import static no.unit.nva.search2.constant.Words.COLON; -import static no.unit.nva.search2.constant.Words.COMMA; -import static no.unit.nva.search2.constant.Words.EQUAL; -import static no.unit.nva.search2.constant.Words.PLUS; -import static no.unit.nva.search2.constant.Words.RESOURCES; -import static no.unit.nva.search2.constant.Words.SEARCH; -import static nva.commons.core.StringUtils.EMPTY_STRING; -import static nva.commons.core.StringUtils.SPACE; -import static nva.commons.core.attempt.Try.attempt; -import static nva.commons.core.paths.UriWrapper.fromUri; import com.google.common.net.MediaType; +import no.unit.nva.search.CsvTransformer; +import no.unit.nva.search2.dto.PagedSearch; +import no.unit.nva.search2.dto.PagedSearchBuilder; +import no.unit.nva.search2.enums.ParameterKey; +import no.unit.nva.search2.enums.ParameterKey.ValueEncoding; +import nva.commons.core.JacocoGenerated; +import org.jetbrains.annotations.NotNull; +import org.joda.time.DateTime; +import org.opensearch.common.collect.Tuple; +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.search.sort.SortOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -30,16 +33,27 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import java.util.stream.Stream; -import no.unit.nva.search2.enums.ParameterKey; -import no.unit.nva.search2.enums.ParameterKey.ValueEncoding; -import nva.commons.core.JacocoGenerated; -import org.jetbrains.annotations.NotNull; -import org.joda.time.DateTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class Query & ParameterKey> { +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; +import static no.unit.nva.search2.constant.ErrorMessages.UNEXPECTED_VALUE; +import static no.unit.nva.search2.constant.Functions.readSearchInfrastructureApiUri; +import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_URL_PARAM_INDICATOR; +import static no.unit.nva.search2.constant.Words.AMPERSAND; +import static no.unit.nva.search2.constant.Words.COLON; +import static no.unit.nva.search2.constant.Words.COMMA; +import static no.unit.nva.search2.constant.Words.EQUAL; +import static no.unit.nva.search2.constant.Words.PLUS; +import static no.unit.nva.search2.constant.Words.RESOURCES; +import static no.unit.nva.search2.constant.Words.SEARCH; +import static nva.commons.core.StringUtils.EMPTY_STRING; +import static nva.commons.core.StringUtils.SPACE; +import static nva.commons.core.attempt.Try.attempt; +import static nva.commons.core.paths.UriWrapper.fromUri; + +public abstract class Query & ParameterKey> { protected static final Logger logger = LoggerFactory.getLogger(Query.class); protected final transient Map pageParameters; @@ -49,6 +63,19 @@ public class Query & ParameterKey> { private transient MediaType mediaType; private transient URI gatewayUri = URI.create("https://unset/resource/search"); + protected abstract K getFieldsKey(); + + protected abstract String[] fieldsToKeyNames(String field); + + protected abstract boolean isFirstPage(); + + protected abstract Integer getFrom(); + + protected abstract Integer getSize(); + + protected abstract Tuple expandSortKeys(String... strings); + + protected Query() { searchParameters = new ConcurrentHashMap<>(); pageParameters = new ConcurrentHashMap<>(); @@ -56,6 +83,31 @@ protected Query() { mediaType = MediaType.JSON_UTF_8; } + public String doSearch(OpenSearchClient queryClient) { + final var response = queryClient.doSearch(this); + return MediaType.CSV_UTF_8.is(this.getMediaType()) + ? toCsvText(response) + : toPagedResponse(response).toJsonString(); + } + + private String toCsvText(SwsResponse response) { + return CsvTransformer.transform(response.getSearchHits()); + } + + public PagedSearch toPagedResponse(SwsResponse response) { + final var requestParameter = toNvaSearchApiRequestParameter(); + final var source = URI.create(getNvaSearchApiUri().toString().split(PATTERN_IS_URL_PARAM_INDICATOR)[0]); + + return + new PagedSearchBuilder() + .withTotalHits(response.getTotalSize()) + .withHits(response.getSearchHits()) + .withIds(source, requestParameter, getFrom(), getSize()) + .withNextResultsBySortKey(nextResultsBySortKey(response, requestParameter, source)) + .withAggregations(response.getAggregationsStructured()) + .build(); + } + /** * Builds URI to query SWS based on post body. * @@ -72,7 +124,6 @@ public Map getOpenSearchParameters() { return searchParameters; } - /** * Query Parameters with string Keys. * @@ -150,7 +201,7 @@ public void setQueryValue(K key, String value) { } } - public MediaType getMediaType() { + private MediaType getMediaType() { return mediaType; } @@ -206,6 +257,73 @@ public static Collection> queryToMapEntries(String query) : Collections.emptyList(); } + protected static URI nextResultsBySortKey( + SwsResponse response, Map requestParameter, URI gatewayUri) { + + requestParameter.remove("FROM"); + var sortedP = + response.getSort().stream() + .map(Object::toString) + .collect(Collectors.joining(COMMA)); + requestParameter.put("SEARCH_AFTER", sortedP); + return fromUri(gatewayUri) + .addQueryParameters(requestParameter) + .getUri(); + } + + protected Stream> getSortStream(K sortKey) { + return + getOptional(sortKey).stream() + .flatMap(sort -> Arrays.stream(sort.split(COMMA))) + .map(sort -> sort.split(COLON)) + .map(this::expandSortKeys); + } + + + /** + * Creates a boolean query, with all the search parameters. + * + * @return a BoolQueryBuilder + */ + @SuppressWarnings({"PMD.SwitchStmtsShouldHaveDefault"}) + protected BoolQueryBuilder boolQuery() { + var bq = QueryBuilders.boolQuery(); + getOpenSearchParameters() + .forEach((key, value) -> { + if (key.name().equals("SEARCH_ALL")) { + + bq.must(multiMatchQuery(key,getFieldsKey())); + } else if (key.fieldType().equals(ParameterKey.ParamKind.KEYWORD)) { + QueryBuilderTools.addKeywordQuery(key, value, bq); + } else { + switch (key.searchOperator()) { + case MUST -> bq.must(QueryBuilderTools.buildQuery(key, value)); + case MUST_NOT -> bq.mustNot(QueryBuilderTools.buildQuery(key, value)); + case SHOULD -> bq.should(QueryBuilderTools.buildQuery(key, value)); + case GREATER_THAN_OR_EQUAL_TO, LESS_THAN -> bq.must(QueryBuilderTools.rangeQuery(key, value)); + default -> throw new IllegalStateException(UNEXPECTED_VALUE + key.searchOperator()); + } + } + }); + return bq; + } + + + /** + * Creates a multi match query, all words needs to be present, within a document. + * + * @return a MultiMatchQueryBuilder + */ + private MultiMatchQueryBuilder multiMatchQuery(K searchAllKey, K fieldsKey) { + var fields = fieldsToKeyNames(getValue(fieldsKey).toString()); + var value = getValue(searchAllKey).toString(); + return QueryBuilders + .multiMatchQuery(value, fields) + .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) + .operator(Operator.AND); + } + + @NotNull private static Entry stringsToEntry(String... strings) { return new Entry<>() { @@ -228,12 +346,12 @@ public String setValue(String value) { } @SuppressWarnings({"PMD.ShortMethodName"}) - public static class AsType { + public class AsType { private final String value; - private final ParameterKey key; + private final K key; - public AsType(String value, ParameterKey key) { + public AsType(String value, K key) { this.value = value; this.key = key; } @@ -249,6 +367,10 @@ public T as() { }; } + public K getKey() { + return key; + } + @Override public String toString() { return value; diff --git a/search-commons/src/main/java/no/unit/nva/search2/common/QueryBuilderSourceWrapper.java b/search-commons/src/main/java/no/unit/nva/search2/common/QueryContentWrapper.java similarity index 58% rename from search-commons/src/main/java/no/unit/nva/search2/common/QueryBuilderSourceWrapper.java rename to search-commons/src/main/java/no/unit/nva/search2/common/QueryContentWrapper.java index ba91e481b..4772c0954 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/common/QueryBuilderSourceWrapper.java +++ b/search-commons/src/main/java/no/unit/nva/search2/common/QueryContentWrapper.java @@ -3,6 +3,6 @@ import java.net.URI; import org.opensearch.search.builder.SearchSourceBuilder; -public record QueryBuilderSourceWrapper(SearchSourceBuilder source, URI requestUri) { +public record QueryContentWrapper(SearchSourceBuilder source, URI requestUri) { } diff --git a/search-commons/src/main/java/no/unit/nva/search2/common/SwsResponse.java b/search-commons/src/main/java/no/unit/nva/search2/common/SwsResponse.java index 7eb06edac..cce01a8dc 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/common/SwsResponse.java +++ b/search-commons/src/main/java/no/unit/nva/search2/common/SwsResponse.java @@ -1,25 +1,17 @@ package no.unit.nva.search2.common; -import static java.util.Objects.nonNull; -import static no.unit.nva.search.constants.ApplicationConstants.LABELS; -import static no.unit.nva.search.constants.ApplicationConstants.NAME; -import static no.unit.nva.search.constants.ApplicationConstants.objectMapperWithEmpty; -import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_IGNORE_CASE; -import static no.unit.nva.search2.constant.Words.COUNT; -import static no.unit.nva.search2.constant.Words.DOC_COUNT_ERROR_UPPER_BOUND; -import static no.unit.nva.search2.constant.Words.SUM_OTHER_DOC_COUNT; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.Streams; -import java.beans.Transient; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; import no.unit.nva.search2.common.SwsResponse.HitsInfo.Hit; import nva.commons.core.JacocoGenerated; import org.jetbrains.annotations.NotNull; +import java.beans.Transient; +import java.util.List; +import java.util.Optional; + +import static java.util.Objects.nonNull; + public record SwsResponse( int took, boolean timed_out, @@ -27,9 +19,6 @@ public record SwsResponse( HitsInfo hits, JsonNode aggregations) { - public static final String BUCKETS_0_KEY = "/buckets/0/key"; - public static final String ID_BUCKETS = "/id/buckets"; - public static final String BUCKETS = "buckets"; public record ShardsInfo( Long total, @@ -94,99 +83,8 @@ public List getSort() { @Transient public JsonNode getAggregationsStructured() { return nonNull(aggregations) - ? formatAggregations(aggregations) + ? AggregationFormat.apply(aggregations) : null; } - public static JsonNode formatAggregations(JsonNode aggregations) { - var outputAggregationNode = objectMapperWithEmpty.createObjectNode(); - - Streams.stream(aggregations.fields()) - .filter(SwsResponse::ignoreDocCountErrors) - .filter(SwsResponse::ignoreSumOtherDoc) - .map(SwsResponse::getJsonNodeEntry) - .forEach(entry -> { - if (LABELS.equals(entry.getKey())) { - outputAggregationNode.set(entry.getKey(), formatLabels(entry.getValue())); - } else if (NAME.equals(entry.getKey())) { - outputAggregationNode.set(LABELS, formatName(entry.getValue())); - } else if (entry.getValue().isValueNode()) { - outputAggregationNode.set(entry.getKey(), entry.getValue()); - } else if (entry.getValue().isArray()) { - var arrayNode = objectMapperWithEmpty.createArrayNode(); - entry.getValue().forEach(element -> arrayNode.add(formatAggregations(element))); - outputAggregationNode.set(entry.getKey(), arrayNode); - } else { - outputAggregationNode.set(entry.getKey(), formatAggregations(entry.getValue())); - } - }); - return outputAggregationNode; - } - - @NotNull - private static Entry getJsonNodeEntry(Entry entry) { - return Map.entry(getNormalizedFieldName(entry.getKey()), getBucketOrValue(entry.getValue())); - } - - private static boolean ignoreSumOtherDoc(Entry item) { - return !item.getKey().matches(PATTERN_IS_IGNORE_CASE + SUM_OTHER_DOC_COUNT); - } - - private static boolean ignoreDocCountErrors(Entry item) { - return !item.getKey().matches(PATTERN_IS_IGNORE_CASE + DOC_COUNT_ERROR_UPPER_BOUND); - } - - private static JsonNode getBucketOrValue(JsonNode node) { - if (node.at(ID_BUCKETS).isArray()) { - return node.at(ID_BUCKETS); - } - if (node.has(BUCKETS)) { - return node.at("/buckets"); - } - return node; - } - - private static JsonNode formatName(JsonNode nodeEntry) { - var outputAggregationNode = objectMapperWithEmpty.createObjectNode(); - var keyValue = nodeEntry.at(BUCKETS_0_KEY); - outputAggregationNode.set("en", keyValue); - return outputAggregationNode; - } - - private static JsonNode formatLabels(JsonNode value) { - var outputAggregationNode = objectMapperWithEmpty.createObjectNode(); - value.fields().forEachRemaining(node -> { - var fieldName = node.getKey(); - if (fieldName.matches(PATTERN_IS_IGNORE_CASE + DOC_COUNT_ERROR_UPPER_BOUND)) { - return; - } - if (fieldName.matches(PATTERN_IS_IGNORE_CASE + SUM_OTHER_DOC_COUNT)) { - return; - } - var newName = getNormalizedFieldName(fieldName); - - if (COUNT.equals(newName)) { - return; - } - var keyValue = node.getValue().at(BUCKETS_0_KEY); - outputAggregationNode.set(newName, keyValue); - }); - - return outputAggregationNode; - } - - @NotNull - private static String getNormalizedFieldName(String fieldName) { - return Optional.ofNullable(Constants.AGGREGATION_FIELDS_TO_CHANGE.get(fieldName)) - .orElse(fieldName.replaceFirst(Constants.WORD_ENDING_WITH_HASHTAG_REGEX, "")); - } - - static final class Constants { - - public static final String WORD_ENDING_WITH_HASHTAG_REGEX = "[A-za-z0-9]*#"; - - private static final Map AGGREGATION_FIELDS_TO_CHANGE = Map.of( - "docCount", COUNT, - "doc_count", COUNT); - } } diff --git a/search-commons/src/main/java/no/unit/nva/search2/constant/Functions.java b/search-commons/src/main/java/no/unit/nva/search2/constant/Functions.java index 33b4b8d79..4d1021944 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/constant/Functions.java +++ b/search-commons/src/main/java/no/unit/nva/search2/constant/Functions.java @@ -53,7 +53,7 @@ static NestedAggregationBuilder generateObjectLabelsAggregation(String name, Str static TermsAggregationBuilder generateIdAggregation(String object) { return new TermsAggregationBuilder(ID) - .field(jsonPath(object, ID, KEYWORD)) + .field(jsonPath(object, ID)) .size(Defaults.DEFAULT_AGGREGATION_SIZE) .subAggregation(generateLabelsAggregation(object)); } diff --git a/search-commons/src/main/java/no/unit/nva/search2/constant/Patterns.java b/search-commons/src/main/java/no/unit/nva/search2/constant/Patterns.java index bce1f83b8..73c2e90e3 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/constant/Patterns.java +++ b/search-commons/src/main/java/no/unit/nva/search2/constant/Patterns.java @@ -6,7 +6,6 @@ public class Patterns { public static final String PATTERN_IS_IGNORE_CASE = "(?i)"; - public static final String PATTERN_IS_BOOLEAN = PATTERN_IS_IGNORE_CASE + "(true|false|1|0)"; /** @@ -34,6 +33,7 @@ public class Patterns { public static final String PATTERN_IS_SORT_ORDER_KEY = "(?i)sort.?order|order"; public static final String PATTERN_IS_URI = "https?://[^\\s/$.?#].[^\\s]*"; public static final String PATTERN_IS_URL_PARAM_INDICATOR = "\\?"; + /** * Pattern for matching a funding string. * funding source and project_id together separated by ':' diff --git a/search-commons/src/main/java/no/unit/nva/search2/constant/Resource.java b/search-commons/src/main/java/no/unit/nva/search2/constant/ResourcePaths.java similarity index 97% rename from search-commons/src/main/java/no/unit/nva/search2/constant/Resource.java rename to search-commons/src/main/java/no/unit/nva/search2/constant/ResourcePaths.java index 98395a647..e2a278b53 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/constant/Resource.java +++ b/search-commons/src/main/java/no/unit/nva/search2/constant/ResourcePaths.java @@ -43,7 +43,7 @@ import java.util.List; import org.opensearch.search.aggregations.AbstractAggregationBuilder; -public class Resource { +public class ResourcePaths { public static final String IDENTIFIER_KEYWORD = IDENTIFIER + DOT + KEYWORD; public static final String ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION_ID = @@ -119,6 +119,8 @@ public class Resource { generateSimpleAggregation(FUNDING_SOURCE, jsonPath(FUNDINGS, SOURCE, IDENTIFIER)) .subAggregation(generateLabelsAggregation(jsonPath(FUNDINGS, SOURCE))), generateObjectLabelsAggregation(TOP_LEVEL_ORGANIZATION, TOP_LEVEL_ORGANIZATIONS), + generateSimpleAggregation(TOP_LEVEL_ORGANIZATION +"1", jsonPath(TOP_LEVEL_ORGANIZATIONS,ID)) + .subAggregation(generateLabelsAggregation(jsonPath(TOP_LEVEL_ORGANIZATIONS, ID))), generateHasFileAggregation() ); } diff --git a/search-commons/src/main/java/no/unit/nva/search2/dto/FacetBuilder.java b/search-commons/src/main/java/no/unit/nva/search2/dto/FacetBuilder.java new file mode 100644 index 000000000..8d2b107ba --- /dev/null +++ b/search-commons/src/main/java/no/unit/nva/search2/dto/FacetBuilder.java @@ -0,0 +1,47 @@ +package no.unit.nva.search2.dto; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import no.unit.nva.commons.json.JsonUtils; +import nva.commons.core.paths.UriWrapper; +import org.jetbrains.annotations.NotNull; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static nva.commons.core.attempt.Try.attempt; + +public final class FacetBuilder { + + public static Map> build(JsonNode aggregations, URI id) { + return jsonNodeToMapOfFacets(aggregations) + .entrySet().stream() + .map((entry) -> addIdToFacets(entry, id)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); +} + + private static Map> jsonNodeToMapOfFacets(JsonNode aggregations) { + final var typeReference = new TypeReference>>() { }; + return attempt(() -> JsonUtils.dtoObjectMapper.readValue(aggregations.toPrettyString(), typeReference)) + .orElseThrow(); + } + + @NotNull + private static Map.Entry> addIdToFacets(Map.Entry> entry, URI id) { + final var uriwrap = UriWrapper.fromUri(id); + var facets = entry.getValue().stream() + .map(facet -> new Facet( + createUriFilterByKey(entry.getKey(), facet.key(), uriwrap), + facet.key(), + facet.count(), + facet.labels()) + ).toList(); + return Map.entry(entry.getKey(), facets); + } + + private static URI createUriFilterByKey(String keyName, String keyValue, UriWrapper uriwrap) { + return uriwrap.addQueryParameter(keyName, keyValue).getUri(); + } +} diff --git a/search-commons/src/main/java/no/unit/nva/search2/dto/PagedSearchBuilder.java b/search-commons/src/main/java/no/unit/nva/search2/dto/PagedSearchBuilder.java index 2a5f679ff..2b8986e34 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/dto/PagedSearchBuilder.java +++ b/search-commons/src/main/java/no/unit/nva/search2/dto/PagedSearchBuilder.java @@ -1,17 +1,14 @@ package no.unit.nva.search2.dto; -import static java.util.Objects.isNull; -import static no.unit.nva.search2.enums.ResourceParameter.FROM; -import static nva.commons.core.attempt.Try.attempt; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import nva.commons.core.paths.UriWrapper; +import org.jetbrains.annotations.Nullable; + 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.paths.UriWrapper; -import org.jetbrains.annotations.Nullable; + +import static java.util.Objects.isNull; public class PagedSearchBuilder { @@ -28,19 +25,22 @@ public PagedSearch build() { if (isNull(this.nextResults)) { this.nextSearchAfterResults = null; // null values are not serialized } - return new PagedSearch(id, totalHits, hits, nextResults, nextSearchAfterResults, previousResults, - aggregations); + return new PagedSearch( + id, + totalHits, + hits, + nextResults, + nextSearchAfterResults, + previousResults, + aggregations + ); } public PagedSearchBuilder withIds(URI gatewayUri, Map requestParameter, Integer offset, Integer size) { - this.id = - createUriOffsetRef(requestParameter, offset, gatewayUri); - this.previousResults = - createUriOffsetRef(requestParameter, offset - size, gatewayUri); - - this.nextResults = - createNextResults(requestParameter, offset + size, totalHits, gatewayUri); + this.id = createUriOffsetRef(requestParameter, offset, gatewayUri); + this.previousResults = createUriOffsetRef(requestParameter, offset - size, gatewayUri); + this.nextResults = createNextResults(requestParameter, offset + size, totalHits, gatewayUri); return this; } @@ -64,18 +64,7 @@ public PagedSearchBuilder withAggregations(JsonNode aggregations) { if (isNull(aggregations)) { return this; } - var typeReference = new TypeReference>>() { - }; - var mappings = attempt(() -> JsonUtils.dtoObjectMapper.readValue(aggregations.toPrettyString(), typeReference)) - .orElseThrow(); - this.aggregations = mappings.entrySet().stream().map(entry -> { - final var uriwrap = UriWrapper.fromUri(this.id); - - var list = entry.getValue().stream() - .map(facet -> new Facet(uriwrap.addQueryParameter(entry.getKey(), facet.key()).getUri(), facet.key(), - facet.count(), facet.labels())).toList(); - return Map.entry(entry.getKey(), list); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + this.aggregations = FacetBuilder.build(aggregations,this.id); return this; } @@ -92,7 +81,7 @@ private URI createUriOffsetRef(Map params, Integer offset, URI g return null; } - params.put(FROM.fieldName(), String.valueOf(offset)); + params.put("FROM", String.valueOf(offset)); return UriWrapper.fromUri(gatewayUri) .addQueryParameters(params) .getUri(); diff --git a/search-commons/src/main/java/no/unit/nva/search2/enums/ParameterKey.java b/search-commons/src/main/java/no/unit/nva/search2/enums/ParameterKey.java index c772fa19d..2b8622aeb 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/enums/ParameterKey.java +++ b/search-commons/src/main/java/no/unit/nva/search2/enums/ParameterKey.java @@ -1,5 +1,11 @@ package no.unit.nva.search2.enums; +import nva.commons.core.JacocoGenerated; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.function.Predicate; + import static java.util.Objects.nonNull; import static no.unit.nva.search2.constant.ErrorMessages.INVALID_DATE; import static no.unit.nva.search2.constant.ErrorMessages.INVALID_NUMBER; @@ -13,10 +19,6 @@ import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_NON_EMPTY; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_NUMBER; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_SPECIAL_CHARACTERS; -import java.util.Collection; -import java.util.function.Predicate; -import nva.commons.core.JacocoGenerated; -import org.jetbrains.annotations.NotNull; public interface ParameterKey> { diff --git a/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceParameter.java b/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceParameter.java index 19b30f286..d613cfec5 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceParameter.java +++ b/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceParameter.java @@ -16,24 +16,24 @@ import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_SORT_KEY; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_SORT_ORDER_KEY; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_URI; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION_ID; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_CONTRIBUTORS_IDENTITY; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_CONTRIBUTORS_IDENTITY_ID; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_CONTRIBUTORS_IDENTITY_ORC_ID; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_REFERENCE_DOI; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISBN_LIST; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_TYPE_KEYWORD; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_INSTANCE_TYPE; -import static no.unit.nva.search2.constant.Resource.FUNDINGS_IDENTIFIER_FUNDINGS_SOURCE_IDENTIFIER; -import static no.unit.nva.search2.constant.Resource.FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS; -import static no.unit.nva.search2.constant.Resource.IDENTIFIER_KEYWORD; -import static no.unit.nva.search2.constant.Resource.MAIN_TITLE; -import static no.unit.nva.search2.constant.Resource.PARENT_PUBLICATION_ID; -import static no.unit.nva.search2.constant.Resource.RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD; -import static no.unit.nva.search2.constant.Resource.RESOURCE_OWNER_OWNER_KEYWORD; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_CONTRIBUTORS_AFFILIATION_ID; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_CONTRIBUTORS_IDENTITY; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_CONTRIBUTORS_IDENTITY_ID; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_CONTRIBUTORS_IDENTITY_ORC_ID; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_PUBLICATION_DATE_YEAR; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_REFERENCE_DOI; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISBN_LIST; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_ISSN; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_CONTEXT_TYPE_KEYWORD; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_INSTANCE_TYPE; +import static no.unit.nva.search2.constant.ResourcePaths.FUNDINGS_IDENTIFIER_FUNDINGS_SOURCE_IDENTIFIER; +import static no.unit.nva.search2.constant.ResourcePaths.FUNDINGS_SOURCE_IDENTIFIER_FUNDINGS_SOURCE_LABELS; +import static no.unit.nva.search2.constant.ResourcePaths.IDENTIFIER_KEYWORD; +import static no.unit.nva.search2.constant.ResourcePaths.MAIN_TITLE; +import static no.unit.nva.search2.constant.ResourcePaths.PARENT_PUBLICATION_ID; +import static no.unit.nva.search2.constant.ResourcePaths.RESOURCE_OWNER_OWNER_AFFILIATION_KEYWORD; +import static no.unit.nva.search2.constant.ResourcePaths.RESOURCE_OWNER_OWNER_KEYWORD; import static no.unit.nva.search2.constant.Words.ASTERISK; import static no.unit.nva.search2.constant.Words.COLON; import static no.unit.nva.search2.constant.Words.CREATED_DATE; diff --git a/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceSort.java b/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceSort.java index c770f1f7e..44b2f1952 100644 --- a/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceSort.java +++ b/search-commons/src/main/java/no/unit/nva/search2/enums/ResourceSort.java @@ -2,7 +2,7 @@ import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_IGNORE_CASE; import static no.unit.nva.search2.constant.Patterns.PATTERN_IS_NONE_OR_ONE; -import static no.unit.nva.search2.constant.Resource.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_INSTANCE_TYPE; +import static no.unit.nva.search2.constant.ResourcePaths.ENTITY_DESCRIPTION_REFERENCE_PUBLICATION_INSTANCE_TYPE; import static no.unit.nva.search2.constant.Words.UNDERSCORE; import java.util.Arrays; import java.util.Collection; diff --git a/search-commons/src/test/java/no/unit/nva/search2/FacetsTest.java b/search-commons/src/test/java/no/unit/nva/search2/FacetsTest.java index f065da729..ca93a1dba 100644 --- a/search-commons/src/test/java/no/unit/nva/search2/FacetsTest.java +++ b/search-commons/src/test/java/no/unit/nva/search2/FacetsTest.java @@ -1,11 +1,11 @@ package no.unit.nva.search2; import static no.unit.nva.search.constants.ApplicationConstants.objectMapperWithEmpty; -import static no.unit.nva.search2.common.SwsResponse.formatAggregations; import static nva.commons.core.attempt.Try.attempt; import static nva.commons.core.ioutils.IoUtils.inputStreamFromResources; import static nva.commons.core.ioutils.IoUtils.streamToString; import com.fasterxml.jackson.databind.JsonNode; +import no.unit.nva.search2.common.AggregationFormat; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,7 +19,7 @@ void shoulCheckMapping() { var jsonNode = stringToJsonNode(jsonString); assert jsonNode != null; - logger.info(formatAggregations(jsonNode).toPrettyString()); + logger.info(AggregationFormat.apply(jsonNode).toPrettyString()); } diff --git a/search-commons/src/test/java/no/unit/nva/search2/ResourceClientTest.java b/search-commons/src/test/java/no/unit/nva/search2/ResourceClientTest.java index df1eee5da..94f01d2dc 100644 --- a/search-commons/src/test/java/no/unit/nva/search2/ResourceClientTest.java +++ b/search-commons/src/test/java/no/unit/nva/search2/ResourceClientTest.java @@ -111,6 +111,7 @@ void searchWithUriReturnsOpenSearchAwsResponse(URI uri) throws ApiGatewayExcepti .build(); var response = searchClient.doSearch(query); + logger.info(response.aggregations().toPrettyString()); var pagedSearchResourceDto = query.toPagedResponse(response); assertNotNull(pagedSearchResourceDto);