From 3379614ae0bf71525b649146c4e0fb9971b76c82 Mon Sep 17 00:00:00 2001 From: Marcos Lopez Gonzalez Date: Mon, 15 Jan 2024 14:21:30 +0100 Subject: [PATCH] #543 added institution keys as search parameter --- .../service/CollectionServiceIT.java | 75 ++++++---- .../service/InstitutionServiceIT.java | 25 +++- .../params/CollectionSearchParams.java | 1 - .../collections/params/SearchParams.java | 2 + .../mapper/collections/CollectionMapper.xml | 9 +- .../mapper/collections/InstitutionMapper.xml | 6 + .../collections/DefaultCollectionService.java | 12 +- .../DefaultInstitutionService.java | 1 + ...hRequestHandlerMethodArgumentResolver.java | 14 ++ .../BaseCollectionEntityResource.java | 132 ++++++++++-------- .../collections/CollectionResource.java | 3 +- 11 files changed, 193 insertions(+), 87 deletions(-) diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionServiceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionServiceIT.java index 1a5fe417f6..122d998beb 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionServiceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionServiceIT.java @@ -449,31 +449,31 @@ public void listTest() { .size()); assertEquals( - collection1.getKey(), - collectionService - .list( - CollectionSearchRequest.builder() - .sortBy(CollectionsSortField.NUMBER_SPECIMENS) - .page(DEFAULT_PAGE) - .build()) - .getResults() - .get(0) - .getCollection() - .getKey()); - - assertEquals( - collection2.getKey(), - collectionService - .list( - CollectionSearchRequest.builder() - .sortBy(CollectionsSortField.NUMBER_SPECIMENS) - .sortOrder(SortOrder.DESC) - .page(DEFAULT_PAGE) - .build()) - .getResults() - .get(0) - .getCollection() - .getKey()); + collection1.getKey(), + collectionService + .list( + CollectionSearchRequest.builder() + .sortBy(CollectionsSortField.NUMBER_SPECIMENS) + .page(DEFAULT_PAGE) + .build()) + .getResults() + .get(0) + .getCollection() + .getKey()); + + assertEquals( + collection2.getKey(), + collectionService + .list( + CollectionSearchRequest.builder() + .sortBy(CollectionsSortField.NUMBER_SPECIMENS) + .sortOrder(SortOrder.DESC) + .page(DEFAULT_PAGE) + .build()) + .getResults() + .get(0) + .getCollection() + .getKey()); collectionService.delete(key2); assertEquals( @@ -532,6 +532,31 @@ public void listByInstitutionTest() { .page(DEFAULT_PAGE) .build()); assertEquals(0, response.getResults().size()); + + response = + collectionService.list( + CollectionSearchRequest.builder() + .institutionKeys(Collections.singletonList(institutionKey1)) + .page(DEFAULT_PAGE) + .build()); + assertEquals(2, response.getResults().size()); + + response = + collectionService.list( + CollectionSearchRequest.builder() + .institutionKeys(Arrays.asList(institutionKey1, institutionKey2)) + .page(DEFAULT_PAGE) + .build()); + assertEquals(3, response.getResults().size()); + + response = + collectionService.list( + CollectionSearchRequest.builder() + .query(collection1.getCode()) + .institutionKeys(Arrays.asList(institutionKey1, institutionKey2)) + .page(DEFAULT_PAGE) + .build()); + assertEquals(1, response.getResults().size()); } @Test diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java index 14eea79966..21f51ae9dd 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/InstitutionServiceIT.java @@ -19,7 +19,6 @@ import org.gbif.api.model.collections.Institution; import org.gbif.api.model.collections.MasterSourceMetadata; import org.gbif.api.model.collections.UserId; -import org.gbif.api.model.collections.request.CollectionSearchRequest; import org.gbif.api.model.collections.request.InstitutionSearchRequest; import org.gbif.api.model.common.paging.PagingRequest; import org.gbif.api.model.common.paging.PagingResponse; @@ -148,6 +147,30 @@ public void listTest() { assertEquals(1, response.getResults().size()); assertEquals(key2, response.getResults().get(0).getKey()); + // subset of institutions + assertEquals( + 2, + institutionService + .list( + InstitutionSearchRequest.builder() + .institutionKeys(Arrays.asList(institution1.getKey(), institution2.getKey())) + .page(DEFAULT_PAGE) + .build()) + .getResults() + .size()); + + assertEquals( + 1, + institutionService + .list( + InstitutionSearchRequest.builder() + .query(institution1.getCode()) + .institutionKeys(Arrays.asList(institution1.getKey(), institution2.getKey())) + .page(DEFAULT_PAGE) + .build()) + .getResults() + .size()); + // code and name params assertEquals( 1, diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/CollectionSearchParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/CollectionSearchParams.java index 4bec6f267c..9ab546f3ad 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/CollectionSearchParams.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/CollectionSearchParams.java @@ -29,7 +29,6 @@ @SuperBuilder public class CollectionSearchParams extends SearchParams { - @Nullable UUID institutionKey; @Nullable List contentTypes; @Nullable List preservationTypes; @Nullable AccessionStatus accessionStatus; diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/SearchParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/SearchParams.java index e1b672b7d4..d55d2c90c1 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/SearchParams.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/SearchParams.java @@ -24,6 +24,7 @@ import java.util.UUID; import javax.annotation.Nullable; +import javax.validation.constraints.Null; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -53,6 +54,7 @@ public abstract class SearchParams { @Nullable private UUID replacedBy; @Nullable RangeParam occurrenceCount; @Nullable RangeParam typeSpecimenCount; + @Nullable List institutionKeys; @Nullable CollectionsSortField sortBy; @Nullable diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionMapper.xml index ccdaf99c18..eb73e904df 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionMapper.xml @@ -255,9 +255,6 @@ c.deleted IS NULL - - AND c.institution_key = #{params.institutionKey,jdbcType=OTHER} - AND lower(c.code) = lower(#{params.code,jdbcType=VARCHAR}) @@ -387,6 +384,12 @@ AND c.replaced_by = #{params.replacedBy,jdbcType=OTHER} + + AND c.institution_key IN + + #{item,jdbcType=VARCHAR} + + diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml index b46534e945..ca9fb5bc3f 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.xml @@ -399,6 +399,12 @@ AND i.replaced_by = #{params.replacedBy,jdbcType=OTHER} + + AND i.key IN + + #{item,jdbcType=VARCHAR} + + diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultCollectionService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultCollectionService.java index aaaa803051..efcbd447d6 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultCollectionService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultCollectionService.java @@ -45,6 +45,8 @@ import org.gbif.registry.service.WithMyBatis; import org.gbif.registry.service.collections.converters.CollectionConverter; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; @@ -148,9 +150,16 @@ private PagingResponse listInternal( ? Strings.emptyToNull(CharMatcher.WHITESPACE.trimFrom(searchRequest.getQ())) : searchRequest.getQ(); + Set institutionKeys = new HashSet<>(); + if (searchRequest.getInstitution() != null) { + institutionKeys.add(searchRequest.getInstitution()); + } + if (searchRequest.getInstitutionKeys() != null) { + institutionKeys.addAll(searchRequest.getInstitutionKeys()); + } + CollectionSearchParams params = CollectionSearchParams.builder() - .institutionKey(searchRequest.getInstitution()) .query(query) .code(searchRequest.getCode()) .name(searchRequest.getName()) @@ -176,6 +185,7 @@ private PagingResponse listInternal( .occurrenceCount(parseIntegerRangeParameter(searchRequest.getOccurrenceCount())) .typeSpecimenCount(parseIntegerRangeParameter(searchRequest.getTypeSpecimenCount())) .deleted(deleted) + .institutionKeys(new ArrayList<>(institutionKeys)) .sortBy(searchRequest.getSortBy()) .sortOrder(searchRequest.getSortOrder()) .page(page) diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java index a45b185f6b..34ef83bc7b 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/DefaultInstitutionService.java @@ -169,6 +169,7 @@ private InstitutionSearchParams buildSearchParams( .replacedBy(searchRequest.getReplacedBy()) .occurrenceCount(parseIntegerRangeParameter(searchRequest.getOccurrenceCount())) .typeSpecimenCount(parseIntegerRangeParameter(searchRequest.getTypeSpecimenCount())) + .institutionKeys(searchRequest.getInstitutionKeys()) .deleted(deleted) .sortBy(searchRequest.getSortBy()) .sortOrder(searchRequest.getSortOrder()) diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/provider/BaseGrSciCollSearchRequestHandlerMethodArgumentResolver.java b/registry-ws/src/main/java/org/gbif/registry/ws/provider/BaseGrSciCollSearchRequestHandlerMethodArgumentResolver.java index 093ae08095..1ed5809ca6 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/provider/BaseGrSciCollSearchRequestHandlerMethodArgumentResolver.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/provider/BaseGrSciCollSearchRequestHandlerMethodArgumentResolver.java @@ -13,6 +13,7 @@ */ package org.gbif.registry.ws.provider; + import org.gbif.api.model.collections.request.SearchRequest; import org.gbif.api.model.common.paging.Pageable; import org.gbif.api.util.VocabularyUtils; @@ -161,6 +162,19 @@ protected void fillSearchRequestParams( validateIntegerRange(typeSpecimenCountParam, "typeSpecimen"); request.setTypeSpecimenCount(typeSpecimenCountParam); } + + String[] institutionKeysParams = webRequest.getParameterValues("institutionKey"); + if (institutionKeysParams != null && institutionKeysParams.length > 0) { + request.setInstitutionKeys(new ArrayList<>()); + for (String keyParam : institutionKeysParams) { + try { + request.getInstitutionKeys().add(UUID.fromString(keyParam)); + } catch (Exception ex) { + throw new IllegalArgumentException( + "Invalid UUID for institution key parameter: " + keyParam); + } + } + } } private static void validateIntegerRange(String param, String paramName) { diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/BaseCollectionEntityResource.java b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/BaseCollectionEntityResource.java index 292f996764..06ee78cd81 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/BaseCollectionEntityResource.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/BaseCollectionEntityResource.java @@ -13,15 +13,24 @@ */ package org.gbif.registry.ws.resources.collections; +import com.google.common.base.Preconditions; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.Explode; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import lombok.SneakyThrows; import org.gbif.api.annotation.EmptyToNull; import org.gbif.api.annotation.NullToNotFound; import org.gbif.api.annotation.Trim; import org.gbif.api.documentation.CommonParameters; -import org.gbif.api.model.collections.Batch; -import org.gbif.api.model.collections.CollectionEntity; import org.gbif.api.model.collections.Contact; -import org.gbif.api.model.collections.MasterSourceMetadata; -import org.gbif.api.model.collections.OccurrenceMapping; +import org.gbif.api.model.collections.*; import org.gbif.api.model.collections.duplicates.DuplicatesRequest; import org.gbif.api.model.collections.duplicates.DuplicatesResult; import org.gbif.api.model.collections.merge.MergeParams; @@ -33,25 +42,30 @@ import org.gbif.api.model.common.export.ExportFormat; import org.gbif.api.model.common.paging.Pageable; import org.gbif.api.model.common.paging.PagingResponse; -import org.gbif.api.model.registry.Comment; -import org.gbif.api.model.registry.Commentable; -import org.gbif.api.model.registry.Identifiable; -import org.gbif.api.model.registry.Identifier; -import org.gbif.api.model.registry.MachineTag; -import org.gbif.api.model.registry.MachineTaggable; -import org.gbif.api.model.registry.Tag; -import org.gbif.api.model.registry.Taggable; +import org.gbif.api.model.registry.*; import org.gbif.api.service.collections.BatchService; import org.gbif.api.service.collections.ChangeSuggestionService; import org.gbif.api.service.collections.CollectionEntityService; -import org.gbif.api.vocabulary.Country; -import org.gbif.api.vocabulary.IdentifierType; +import org.gbif.api.vocabulary.*; import org.gbif.api.vocabulary.collections.MasterSourceType; import org.gbif.registry.persistence.mapper.collections.params.DuplicatesSearchParams; import org.gbif.registry.service.collections.duplicates.DuplicatesService; import org.gbif.registry.service.collections.merge.MergeService; import org.gbif.registry.ws.resources.Docs; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -64,42 +78,6 @@ import java.util.List; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.multipart.MultipartFile; - -import com.google.common.base.Preconditions; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.extensions.Extension; -import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import lombok.SneakyThrows; - import static com.google.common.base.Preconditions.checkArgument; /** Base class to implement the CRUD methods of a {@link CollectionEntity}. */ @@ -192,15 +170,24 @@ protected BaseCollectionEntityResource( name = "country", description = "Filters by country given as a ISO 639-1 (2 letter) country code.", schema = @Schema(implementation = Country.class), - in = ParameterIn.QUERY), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "gbifRegion", + description = "Filters by a gbif region", + schema = @Schema(implementation = GbifRegion.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), @Parameter( name = "city", - description = "TODO", + description = + "Filters by the city of the address. It searches in both the physical and the mailing address.", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY), @Parameter( name = "fuzzyName", - description = "TODO", + description = + "It searches by name fuzzily so the parameter doesn't have to be the exact name", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY), @Parameter( @@ -215,14 +202,49 @@ protected BaseCollectionEntityResource( in = ParameterIn.QUERY), @Parameter( name = "numberSpecimens", - description = "TODO", + description = + "Number of specimens. It supports ranges and a '*' can be used as a wildcard", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY), @Parameter( name = "displayOnNHCPortal", - description = "TODO", + description = "Flag to show this record in the NHC portal", schema = @Schema(implementation = Boolean.class), in = ParameterIn.QUERY), + @Parameter( + name = "replacedBy", + description = "Key of the entity that replaced another entity", + schema = @Schema(implementation = UUID.class), + in = ParameterIn.QUERY), + @Parameter( + name = "occurrenceCount", + description = + "Count of occurrences linked. It supports ranges and a '*' can be used as a wildcard", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY), + @Parameter( + name = "typeSpecimenCount", + description = + "Count of type specimens linked. It supports ranges and a '*' can be used as a wildcard", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY), + @Parameter( + name = "institutionKey", + description = "Keys of institutions to filter by", + schema = @Schema(implementation = UUID.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "sortBy", + description = + "Field to sort the results by. It only supports the fields contained in the enum.", + schema = @Schema(implementation = CollectionsSortField.class), + in = ParameterIn.QUERY), + @Parameter( + name = "sortOrder", + description = "Sort order to use with the sortBy parameter", + schema = @Schema(implementation = SortOrder.class), + in = ParameterIn.QUERY), @Parameter(name = "searchRequest", hidden = true) }) @CommonParameters.QParameter diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionResource.java b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionResource.java index dd6554207c..538504715e 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionResource.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionResource.java @@ -138,7 +138,8 @@ public CollectionResource( name = "institution", description = "A key for the institution.", schema = @Schema(implementation = UUID.class), - in = ParameterIn.QUERY), + in = ParameterIn.QUERY, + deprecated = true), @Parameter( name = "contentTypes", description =