diff --git a/pom.xml b/pom.xml index 25e60477c8..6530c95f5f 100644 --- a/pom.xml +++ b/pom.xml @@ -84,21 +84,21 @@ 2.2.8.RELEASE - 1.16.5 + 1.16.7 0.59 - 1.3 + 1.4 1.26 1.13 2.17 1.51.0 0.10 2.2 - 0.60 + 0.67 1.8.0 4.1.0-gbif-4 0.195.1 1.0.5 - 2.125 + 2.128 3.2 diff --git a/registry-events/src/main/java/org/gbif/registry/events/collections/SubEntityCollectionEvent.java b/registry-events/src/main/java/org/gbif/registry/events/collections/SubEntityCollectionEvent.java index c29bcfb116..114652320f 100644 --- a/registry-events/src/main/java/org/gbif/registry/events/collections/SubEntityCollectionEvent.java +++ b/registry-events/src/main/java/org/gbif/registry/events/collections/SubEntityCollectionEvent.java @@ -13,11 +13,9 @@ */ package org.gbif.registry.events.collections; -import org.gbif.api.model.collections.CollectionEntity; - -import java.util.UUID; - import com.google.common.base.Preconditions; +import java.util.UUID; +import org.gbif.api.model.collections.CollectionEntity; /** * This event is fired after collection entity components such as contacts, identifiers or tags have @@ -66,6 +64,22 @@ public static SubEntityCollectionEvent new eventType); } + public static SubEntityCollectionEvent newInstance( + UUID collectionEntityKey, + Class collectionEntityClass, + Class subEntity, + long subEntityKey, + EventType eventType) { + return new SubEntityCollectionEvent<>( + collectionEntityKey, + collectionEntityClass, + subEntity, + null, + null, + String.valueOf(subEntityKey), + eventType); + } + public static SubEntityCollectionEvent newInstance( UUID collectionEntityKey, Class collectionEntityClass, diff --git a/registry-integration-tests/pom.xml b/registry-integration-tests/pom.xml index 06e246132d..3abaedee5b 100644 --- a/registry-integration-tests/pom.xml +++ b/registry-integration-tests/pom.xml @@ -141,6 +141,13 @@ junit-jupiter-params test + + + net.java.dev.jna + jna + 5.9.0 + test + diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/database/BaseDBTest.java b/registry-integration-tests/src/test/java/org/gbif/registry/database/BaseDBTest.java index 64d4c0af1a..25697e8f95 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/database/BaseDBTest.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/database/BaseDBTest.java @@ -13,14 +13,8 @@ */ package org.gbif.registry.database; -import org.gbif.registry.ws.it.fixtures.TestConstants; - import java.sql.SQLException; import java.time.Duration; - -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.containers.wait.strategy.Wait; - import liquibase.Contexts; import liquibase.Liquibase; import liquibase.database.Database; @@ -28,6 +22,9 @@ import liquibase.database.jvm.JdbcConnection; import liquibase.exception.LiquibaseException; import liquibase.resource.ClassLoaderResourceAccessor; +import org.gbif.registry.ws.it.fixtures.TestConstants; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.containers.wait.strategy.Wait; public class BaseDBTest { diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java b/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java index f766eaa779..bc9fc4775f 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java @@ -13,23 +13,20 @@ */ package org.gbif.registry.database; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.sql.DataSource; - +import com.google.common.base.Throwables; +import lombok.Data; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.test.context.junit.jupiter.SpringExtension; -import com.google.common.base.Throwables; - -import lombok.Data; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * A Rule that will truncate the tables ready for a new test. It is expected to do this before each @@ -106,7 +103,10 @@ public class TestCaseDatabaseInitializer implements BeforeEachCallback { "collection_contact", "institution_collection_contact", "collection_collection_contact", - "collections_batch"); + "collections_batch", + "collection_descriptor_group", + "collection_descriptor", + "collection_descriptor_verbatim"); public TestCaseDatabaseInitializer() {} diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/test/mocks/NubResourceClientMock.java b/registry-integration-tests/src/test/java/org/gbif/registry/test/mocks/NubResourceClientMock.java new file mode 100644 index 0000000000..709055054a --- /dev/null +++ b/registry-integration-tests/src/test/java/org/gbif/registry/test/mocks/NubResourceClientMock.java @@ -0,0 +1,70 @@ +package org.gbif.registry.test.mocks; + +import java.util.Arrays; +import org.gbif.api.model.checklistbank.NameUsageMatch; +import org.gbif.api.model.common.LinneanClassification; +import org.gbif.api.v2.NameUsageMatch2; +import org.gbif.api.v2.RankedName; +import org.gbif.api.vocabulary.Rank; +import org.gbif.checklistbank.ws.client.NubResourceClient; + +public class NubResourceClientMock implements NubResourceClient { + + public static final RankedName DEFAULT_USAGE = new RankedName(100, "usage", Rank.SPECIES); + public static final RankedName DEFAULT_HIGHEST_USAGE = + new RankedName(1, "superHigherUsage", Rank.KINGDOM); + + @Override + public NameUsageMatch match( + String s, + String s1, + String s2, + String s3, + String s4, + String s5, + String s6, + String s7, + LinneanClassification linneanClassification, + Boolean aBoolean, + Boolean aBoolean1) { + return null; + } + + @Override + public NameUsageMatch2 match2( + String scientificName2, + String scientificName, + String authorship2, + String authorship, + String rank2, + String rank, + String genericName, + String specificEpithet, + String infraspecificEpithet, + LinneanClassification classification, + Boolean strict, + Boolean verbose) { + + if ("Aves".equalsIgnoreCase(scientificName)) { + NameUsageMatch2 nameUsageMatch2 = new NameUsageMatch2(); + nameUsageMatch2.setUsage(new RankedName(212, "Aves", Rank.CLASS)); + nameUsageMatch2.setClassification( + Arrays.asList( + new RankedName(1, "Animalia", Rank.KINGDOM), + new RankedName(44, "Chordata", Rank.PHYLUM), + new RankedName(212, "Aves", Rank.CLASS))); + NameUsageMatch2.Diagnostics diagnostics = new NameUsageMatch2.Diagnostics(); + diagnostics.setMatchType(NameUsageMatch.MatchType.EXACT); + nameUsageMatch2.setDiagnostics(diagnostics); + return nameUsageMatch2; + } + + NameUsageMatch2 nameUsageMatch2 = new NameUsageMatch2(); + nameUsageMatch2.setUsage(DEFAULT_USAGE); + + nameUsageMatch2.setClassification( + Arrays.asList(new RankedName(50, "higherUsage", Rank.GENUS), DEFAULT_HIGHEST_USAGE)); + + return nameUsageMatch2; + } +} diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/OrganizationIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/OrganizationIT.java index 56e21b6af2..8f0c0f8a57 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/OrganizationIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/OrganizationIT.java @@ -13,6 +13,8 @@ */ package org.gbif.registry.ws.it; +import java.math.BigDecimal; + import org.gbif.api.model.common.paging.PagingRequest; import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.registry.Dataset; @@ -44,9 +46,13 @@ import javax.annotation.Nullable; import org.apache.commons.beanutils.BeanUtils; +import org.geojson.Feature; +import org.geojson.FeatureCollection; +import org.geojson.Point; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.locationtech.jts.util.Assert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.server.LocalServerPort; @@ -266,6 +272,34 @@ public void testList(ServiceType serviceType) { assertResultsOfSize(service.listDeleted(searchParams), 1); } + @ParameterizedTest + @EnumSource(ServiceType.class) + public void testListPublishersAsGeoJson(ServiceType serviceType) { + OrganizationService service = (OrganizationService) getService(serviceType); + + Node node = testDataFactory.newNode(); + UUID nodeKey = nodeResource.create(node); + + Organization o1 = testDataFactory.newOrganization(nodeKey); + o1.setTitle("n1"); + o1.setEndorsementApproved(true); + o1.setLatitude(BigDecimal.valueOf(50d)); + o1.setLongitude(BigDecimal.valueOf(12d)); + + UUID key1 = getService(serviceType).create(o1); + + FeatureCollection expectedFeatureCollection = new FeatureCollection(); + Feature f1 = new Feature(); + f1.setGeometry(new Point(12d, 50d)); + f1.setProperty("organization", "n1"); + f1.setProperty("key", key1.toString()); + expectedFeatureCollection.add(f1); + + OrganizationRequestSearchParams searchParams = new OrganizationRequestSearchParams(); + FeatureCollection result = service.listGeoJson(searchParams); + Assert.equals(expectedFeatureCollection.getFeatures().size(),result.getFeatures().size()); + } + private void createOrgs(UUID nodeKey, ServiceType serviceType, Country... countries) { OrganizationService service = (OrganizationService) getService(serviceType); for (Country c : countries) { diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/RegistryIntegrationTestsConfiguration.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/RegistryIntegrationTestsConfiguration.java index 3b64caeaa8..b03cd0e823 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/RegistryIntegrationTestsConfiguration.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/RegistryIntegrationTestsConfiguration.java @@ -14,12 +14,15 @@ package org.gbif.registry.ws.it; import com.zaxxer.hikari.HikariDataSource; +import java.util.Collections; +import java.util.Date; import org.apache.commons.beanutils.BeanUtilsBean; import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.beanutils.ConvertUtilsBean; import org.apache.commons.beanutils.converters.DateConverter; import org.apache.commons.beanutils.converters.DateTimeConverter; import org.gbif.api.vocabulary.UserRole; +import org.gbif.checklistbank.ws.client.NubResourceClient; import org.gbif.registry.doi.config.TitleLookupConfiguration; import org.gbif.registry.events.config.VarnishPurgeConfiguration; import org.gbif.registry.mail.EmailSenderImpl; @@ -29,6 +32,7 @@ import org.gbif.registry.search.dataset.indexing.ws.GbifWsClient; import org.gbif.registry.surety.OrganizationEmailTemplateManagerIT; import org.gbif.registry.test.mocks.ConceptClientMock; +import org.gbif.registry.test.mocks.NubResourceClientMock; import org.gbif.registry.ws.config.DataSourcesConfiguration; import org.gbif.vocabulary.client.ConceptClient; import org.gbif.ws.client.filter.SimplePrincipalProvider; @@ -56,9 +60,6 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import java.util.Collections; -import java.util.Date; - @TestConfiguration @SpringBootApplication( exclude = { @@ -212,4 +213,9 @@ public static void main(String[] args) { public ConceptClient conceptClient() { return new ConceptClientMock(); } + + @Bean + public NubResourceClient nubResourceClient() { + return new NubResourceClientMock(); + } } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseCollectionEntityResourceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseCollectionEntityResourceIT.java index 27e111c908..c7234705e4 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseCollectionEntityResourceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseCollectionEntityResourceIT.java @@ -110,7 +110,7 @@ abstract class BaseCollectionEntityResourceIT< @Autowired protected ObjectMapper objectMapper; @Autowired protected MockMvc mockMvc; - @MockBean private ResourceNotFoundService resourceNotFoundService; + @MockBean protected ResourceNotFoundService resourceNotFoundService; public BaseCollectionEntityResourceIT( Class> cls, @@ -528,7 +528,7 @@ public void listChangeSuggestionTest() { UUID entityKey = UUID.randomUUID(); Pageable page = new PagingRequest(); - when(getMockChangeSuggestionService().list(status, type, proposerEmail, entityKey, page)) + when(getMockChangeSuggestionService().list(status, type, proposerEmail, entityKey, null, page)) .thenReturn( new PagingResponse<>( new PagingRequest(), 1L, Collections.singletonList(changeSuggestion))); diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseResourceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseResourceIT.java index a5339b391e..49422a4f39 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseResourceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/BaseResourceIT.java @@ -13,6 +13,12 @@ */ package org.gbif.registry.ws.it.collections.resource; + +import static org.gbif.registry.ws.it.fixtures.TestConstants.IT_APP_KEY2; + +import java.util.Arrays; +import java.util.Collections; +import javax.sql.DataSource; import org.gbif.api.vocabulary.UserRole; import org.gbif.registry.search.test.EsManageServer; import org.gbif.registry.test.mocks.IdentityServiceMock; @@ -26,12 +32,6 @@ import org.gbif.ws.security.GbifAuthenticationManager; import org.gbif.ws.security.GbifAuthenticationManagerImpl; import org.gbif.ws.security.KeyStore; - -import java.util.Arrays; -import java.util.Collections; - -import javax.sql.DataSource; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; @@ -55,8 +55,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.PlatformTransactionManager; -import static org.gbif.registry.ws.it.fixtures.TestConstants.IT_APP_KEY2; - /** Base class for IT tests that initializes data sources and basic security settings. */ @ExtendWith(SpringExtension.class) @SpringBootTest( @@ -146,6 +144,7 @@ protected T prepareClient(String username, int localServerPort, Class cls return clientBuilder .withUrl("http://localhost:" + localServerPort) .withCredentials(username, username) + .withFormEncoder() .build(cls); } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionResourceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionResourceIT.java index e09190d3fd..7ec8b956f1 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionResourceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionResourceIT.java @@ -13,13 +13,32 @@ */ package org.gbif.registry.ws.it.collections.resource; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.SneakyThrows; import org.gbif.api.model.collections.Collection; import org.gbif.api.model.collections.CollectionImportParams; +import org.gbif.api.model.collections.descriptors.Descriptor; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; import org.gbif.api.model.collections.latimercore.ObjectGroup; import org.gbif.api.model.collections.request.CollectionSearchRequest; +import org.gbif.api.model.collections.request.DescriptorGroupSearchRequest; +import org.gbif.api.model.collections.request.DescriptorSearchRequest; import org.gbif.api.model.collections.suggestions.CollectionChangeSuggestion; import org.gbif.api.model.collections.suggestions.Type; import org.gbif.api.model.collections.view.CollectionView; +import org.gbif.api.model.common.export.ExportFormat; import org.gbif.api.model.common.paging.PagingRequest; import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.registry.search.collections.KeyCodeNameResult; @@ -27,8 +46,10 @@ import org.gbif.api.service.collections.ChangeSuggestionService; import org.gbif.api.service.collections.CollectionEntityService; import org.gbif.api.service.collections.CollectionService; +import org.gbif.api.service.collections.DescriptorsService; import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.GbifRegion; +import org.gbif.api.vocabulary.Rank; import org.gbif.registry.service.collections.batch.CollectionBatchService; import org.gbif.registry.service.collections.duplicates.CollectionDuplicatesService; import org.gbif.registry.service.collections.duplicates.DuplicatesService; @@ -42,19 +63,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.web.server.LocalServerPort; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; public class CollectionResourceIT extends BaseCollectionEntityResourceIT { @@ -68,6 +80,7 @@ public class CollectionResourceIT @MockBean private CollectionChangeSuggestionService collectionChangeSuggestionService; @MockBean private CollectionBatchService collectionBatchService; + @MockBean private DescriptorsService descriptorsService; @Autowired public CollectionResourceIT( @@ -92,7 +105,7 @@ public void listTest() { when(collectionService.list(any(CollectionSearchRequest.class))) .thenReturn(new PagingResponse<>(new PagingRequest(), Long.valueOf(views.size()), views)); - CollectionSearchRequest req = new CollectionSearchRequest(); + CollectionSearchRequest req = CollectionSearchRequest.builder().build(); req.setCity("city"); req.setInstitution(UUID.randomUUID()); req.setCountry(Collections.singletonList(Country.DENMARK)); @@ -118,7 +131,7 @@ public void listAndGetAsLatimerCoreTest() { .thenReturn(new PagingResponse<>(new PagingRequest(), Long.valueOf(orgs.size()), orgs)); PagingResponse result = - getClient().listAsLatimerCore(new CollectionSearchRequest()); + getClient().listAsLatimerCore(CollectionSearchRequest.builder().build()); assertEquals(orgs.size(), result.getResults().size()); when(collectionService.getAsLatimerCore(any(UUID.class))).thenReturn(o1); @@ -169,7 +182,7 @@ public void listDeletedTest() { when(collectionService.listDeleted(any(CollectionSearchRequest.class))) .thenReturn(new PagingResponse<>(new PagingRequest(), Long.valueOf(views.size()), views)); - CollectionSearchRequest request = new CollectionSearchRequest(); + CollectionSearchRequest request = CollectionSearchRequest.builder().build(); request.setReplacedBy(UUID.randomUUID()); PagingResponse result = getClient().listDeleted(request); assertEquals(views.size(), result.getResults().size()); @@ -223,6 +236,136 @@ public void createFromDatasetTest() { assertEquals(institutionKey, getClient().createFromDataset(params)); } + @SneakyThrows + @Test + public void createDescriptorGroupTest() { + when(descriptorsService.createDescriptorGroup(any(), any(), any(), any(), any())) + .thenReturn(1L); + + Resource descriptorsResource = new ClassPathResource("collections/descriptors.csv"); + MultipartFile descriptorsFile = + new MockMultipartFile("descriptorsFile", descriptorsResource.getInputStream()); + + assertEquals( + 1L, + getClient() + .createDescriptorGroup( + UUID.randomUUID(), ExportFormat.CSV, descriptorsFile, "title", "desc")); + } + + @SneakyThrows + @Test + public void updateDescriptorGroupTest() { + UUID collectionKey = UUID.randomUUID(); + DescriptorGroup descriptorGroup = new DescriptorGroup(); + descriptorGroup.setCollectionKey(collectionKey); + descriptorGroup.setTitle("title"); + + when(descriptorsService.getDescriptorGroup(anyLong())).thenReturn(descriptorGroup); + doNothing() + .when(descriptorsService) + .updateDescriptorGroup(anyLong(), any(), any(), anyString(), anyString()); + + Resource descriptorsResource = new ClassPathResource("collections/descriptors.csv"); + MultipartFile descriptorsFile = + new MockMultipartFile("descriptorsFile", descriptorsResource.getInputStream()); + + assertDoesNotThrow( + () -> + getClient() + .updateDescriptorGroup( + collectionKey, 1L, ExportFormat.CSV, descriptorsFile, "title", "desc")); + } + + @Test + public void getDescriptorGroupTest() { + UUID collectionKey = UUID.randomUUID(); + DescriptorGroup descriptorGroup = new DescriptorGroup(); + descriptorGroup.setCollectionKey(collectionKey); + descriptorGroup.setTitle("title"); + + when(resourceNotFoundService.entityExists(any(), any())).thenReturn(true); + when(descriptorsService.getDescriptorGroup(1L)).thenReturn(descriptorGroup); + + assertEquals(descriptorGroup, getClient().getCollectionDescriptorGroup(collectionKey, 1L)); + } + + @Test + public void listDescriptorGroupTest() { + DescriptorGroup descriptorGroup = new DescriptorGroup(); + descriptorGroup.setCollectionKey(UUID.randomUUID()); + descriptorGroup.setTitle("title"); + + when(resourceNotFoundService.entityExists(any(), any())).thenReturn(true); + when(descriptorsService.listDescriptorGroups( + any(UUID.class), any(DescriptorGroupSearchRequest.class))) + .thenReturn(new PagingResponse<>(0, 10, 1L, Collections.singletonList(descriptorGroup))); + + assertEquals( + 1, + getClient() + .listCollectionDescriptorGroups( + UUID.randomUUID(), DescriptorGroupSearchRequest.builder().q("foo").build()) + .getResults() + .size()); + } + + @Test + public void listDescriptorsTest() { + Descriptor descriptor = new Descriptor(); + descriptor.setDescriptorGroupKey(1L); + descriptor.setUsageRank(Rank.ABERRATION); + descriptor.setCountry(Country.SPAIN); + + when(resourceNotFoundService.entityExists(any(), any())).thenReturn(true); + when(descriptorsService.listDescriptors(any())) + .thenReturn(new PagingResponse<>(0, 10, 1L, Collections.singletonList(descriptor))); + + assertEquals( + 1, + getClient() + .listCollectionDescriptors( + UUID.randomUUID(), + 1L, + DescriptorSearchRequest.builder().q("foo").individualCount("1,10").build()) + .getResults() + .size()); + } + + @Test + public void getDescriptorTest() { + UUID collectionKey = UUID.randomUUID(); + Descriptor descriptor = new Descriptor(); + descriptor.setDescriptorGroupKey(1L); + descriptor.setUsageRank(Rank.ABERRATION); + descriptor.setCountry(Country.SPAIN); + + DescriptorGroup descriptorGroup = new DescriptorGroup(); + descriptorGroup.setCollectionKey(collectionKey); + descriptorGroup.setTitle("title"); + + when(resourceNotFoundService.entityExists(any(), any())).thenReturn(true); + when(descriptorsService.getDescriptor(anyLong())).thenReturn(descriptor); + when(descriptorsService.getDescriptorGroup(anyLong())).thenReturn(descriptorGroup); + + assertEquals(descriptor, getClient().getCollectionDescriptor(collectionKey, 1L, 1L)); + } + + @Test + public void deleteDescriptorTest() { + UUID collectionKey = UUID.randomUUID(); + + DescriptorGroup descriptorGroup = new DescriptorGroup(); + descriptorGroup.setKey(1L); + descriptorGroup.setCollectionKey(collectionKey); + descriptorGroup.setTitle("title"); + + when(resourceNotFoundService.entityExists(any(), any())).thenReturn(true); + when(descriptorsService.getDescriptorGroup(anyLong())).thenReturn(descriptorGroup); + + assertDoesNotThrow(() -> getClient().deleteCollectionDescriptorGroup(collectionKey, 1L)); + } + protected CollectionClient getClient() { return (CollectionClient) baseClient; } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionsSearchResourceTest.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionsSearchResourceTest.java index 558ec3a056..056ef54a5b 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionsSearchResourceTest.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/CollectionsSearchResourceTest.java @@ -13,29 +13,31 @@ */ package org.gbif.registry.ws.it.collections.resource; -import org.gbif.api.model.collections.search.CollectionsSearchResponse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import org.gbif.api.model.collections.request.CollectionDescriptorsSearchRequest; +import org.gbif.api.model.collections.request.InstitutionSearchRequest; +import org.gbif.api.model.collections.search.CollectionSearchResponse; +import org.gbif.api.model.collections.search.CollectionsFullSearchResponse; +import org.gbif.api.model.collections.search.Highlight; +import org.gbif.api.model.collections.search.InstitutionSearchResponse; +import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.vocabulary.Country; -import org.gbif.registry.search.dataset.service.collections.CollectionsSearchService; +import org.gbif.registry.service.collections.CollectionsSearchService; import org.gbif.registry.ws.client.collections.CollectionsSearchClient; import org.gbif.registry.ws.it.fixtures.RequestTestFixture; import org.gbif.registry.ws.it.fixtures.TestConstants; import org.gbif.ws.client.filter.SimplePrincipalProvider; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; - import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.web.server.LocalServerPort; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -@Execution(ExecutionMode.CONCURRENT) public class CollectionsSearchResourceTest extends BaseResourceIT { @MockBean private CollectionsSearchService collectionsSearchService; @@ -56,23 +58,62 @@ public CollectionsSearchResourceTest( @Test public void searchTest() { String q = "foo"; - boolean highlight = true; + boolean hl = true; int limit = 10; - CollectionsSearchResponse response = new CollectionsSearchResponse(); + CollectionsFullSearchResponse response = new CollectionsFullSearchResponse(); response.setCode("c1"); response.setInstitutionKey(UUID.randomUUID()); - CollectionsSearchResponse.Match match = new CollectionsSearchResponse.Match(); - match.setField("field1"); - match.setSnippet("snippet"); - response.setMatches(Collections.singleton(match)); + Highlight highlight = new Highlight(); + highlight.setField("field1"); + highlight.setSnippet("snippet"); + response.setHighlights(Collections.singleton(highlight)); - when(collectionsSearchService.search(q, highlight, null, null, Country.SPAIN, limit)) + when(collectionsSearchService.search(q, hl, null, null, Country.SPAIN, limit)) .thenReturn(Collections.singletonList(response)); - List responseReturned = - collectionsSearchClient.searchCollections(q, highlight, null, null, Country.SPAIN, limit); + List responseReturned = + collectionsSearchClient.searchCrossEntities(q, hl, null, null, Country.SPAIN, limit); assertEquals(1, responseReturned.size()); assertEquals(response, responseReturned.get(0)); } + + @Test + public void searchInstitutionsTest() { + InstitutionSearchResponse response = new InstitutionSearchResponse(); + response.setCode("c1"); + response.setKey(UUID.randomUUID()); + Highlight highlight = new Highlight(); + highlight.setField("field1"); + highlight.setSnippet("snippet"); + response.setHighlights(Collections.singleton(highlight)); + + when(collectionsSearchService.searchInstitutions(any())) + .thenReturn(new PagingResponse<>(0, 20, 1L, Collections.singletonList(response))); + + PagingResponse responseReturned = + collectionsSearchClient.searchInstitutions(InstitutionSearchRequest.builder().build()); + assertEquals(1, responseReturned.getResults().size()); + assertEquals(response, responseReturned.getResults().get(0)); + } + + @Test + public void searchCollectionsTest() { + CollectionSearchResponse response = new CollectionSearchResponse(); + response.setCode("c1"); + response.setKey(UUID.randomUUID()); + Highlight highlight = new Highlight(); + highlight.setField("field1"); + highlight.setSnippet("snippet"); + response.setHighlights(Collections.singleton(highlight)); + + when(collectionsSearchService.searchCollections(any())) + .thenReturn(new PagingResponse<>(0, 20, 1L, Collections.singletonList(response))); + + PagingResponse responseReturned = + collectionsSearchClient.searchCollections( + CollectionDescriptorsSearchRequest.builder().build()); + assertEquals(1, responseReturned.getResults().size()); + assertEquals(response, responseReturned.getResults().get(0)); + } } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java index 286591e3e6..a0fd607946 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/resource/InstitutionResourceIT.java @@ -13,6 +13,17 @@ */ package org.gbif.registry.ws.it.collections.resource; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; import org.gbif.api.model.collections.Institution; import org.gbif.api.model.collections.InstitutionImportParams; import org.gbif.api.model.collections.latimercore.OrganisationalUnit; @@ -46,18 +57,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.web.server.LocalServerPort; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; - public class InstitutionResourceIT extends BaseCollectionEntityResourceIT { @@ -95,7 +94,7 @@ public void listTest() { new PagingResponse<>( new PagingRequest(), Long.valueOf(institutions.size()), institutions)); - InstitutionSearchRequest req = new InstitutionSearchRequest(); + InstitutionSearchRequest req = InstitutionSearchRequest.builder().build(); req.setCity("city"); req.setContact(UUID.randomUUID()); req.setCountry(Collections.singletonList(Country.DENMARK)); @@ -122,7 +121,7 @@ public void listAndGetAsLatimerCoreTest() { .thenReturn(new PagingResponse<>(new PagingRequest(), Long.valueOf(orgs.size()), orgs)); PagingResponse result = - getClient().listAsLatimerCore(new InstitutionSearchRequest()); + getClient().listAsLatimerCore(InstitutionSearchRequest.builder().build()); assertEquals(orgs.size(), result.getResults().size()); when(institutionService.getAsLatimerCore(any(UUID.class))).thenReturn(o1); @@ -162,7 +161,8 @@ public void listAsGeoJsonTest() { when(institutionService.listGeojson(any(InstitutionSearchRequest.class))) .thenReturn(featureCollection); - FeatureCollection result = getClient().listAsGeoJson(new InstitutionSearchRequest()); + FeatureCollection result = + getClient().listAsGeoJson(InstitutionSearchRequest.builder().build()); assertEquals(featureCollection.getFeatures().size(), result.getFeatures().size()); } @@ -195,7 +195,7 @@ public void listDeletedTest() { new PagingResponse<>( new PagingRequest(), Long.valueOf(institutions.size()), institutions)); - InstitutionSearchRequest request = new InstitutionSearchRequest(); + InstitutionSearchRequest request = InstitutionSearchRequest.builder().build(); request.setReplacedBy(UUID.randomUUID()); PagingResponse result = getClient().listDeleted(request); assertEquals(institutions.size(), result.getResults().size()); 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 366d9e9923..cfcb87cb23 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 @@ -13,15 +13,21 @@ */ package org.gbif.registry.ws.it.collections.service; +import static org.junit.jupiter.api.Assertions.*; + +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; +import javax.validation.ConstraintViolationException; +import javax.validation.ValidationException; +import org.gbif.api.model.collections.*; import org.gbif.api.model.collections.Address; import org.gbif.api.model.collections.Collection; -import org.gbif.api.model.collections.*; import org.gbif.api.model.collections.duplicates.Duplicate; import org.gbif.api.model.collections.duplicates.DuplicatesResult; import org.gbif.api.model.collections.latimercore.*; import org.gbif.api.model.collections.request.CollectionSearchRequest; import org.gbif.api.model.collections.view.CollectionView; -import org.gbif.api.model.common.paging.PagingRequest; import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.registry.Dataset; import org.gbif.api.model.registry.Identifier; @@ -43,14 +49,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import javax.validation.ConstraintViolationException; -import javax.validation.ValidationException; -import java.net.URI; -import java.util.*; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; - /** Tests the {@link CollectionService}. */ public class CollectionServiceIT extends BaseCollectionEntityServiceIT { @@ -132,7 +130,11 @@ public void listTest() { // query param PagingResponse response = collectionService.list( - CollectionSearchRequest.builder().query("dummy").page(DEFAULT_PAGE).build()); + CollectionSearchRequest.builder() + .q("dummy") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(3, response.getResults().size()); response = @@ -143,55 +145,96 @@ public void listTest() { // empty queries are ignored and return all elements response = collectionService.list( - CollectionSearchRequest.builder().query("").page(DEFAULT_PAGE).build()); + CollectionSearchRequest.builder() + .q("") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(3, response.getResults().size()); response = collectionService.list( - CollectionSearchRequest.builder().query("city").page(DEFAULT_PAGE).build()); + CollectionSearchRequest.builder() + .q("city") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(1, response.getResults().size()); assertEquals(key1, response.getResults().get(0).getCollection().getKey()); response = collectionService.list( - CollectionSearchRequest.builder().query("city2").page(DEFAULT_PAGE).build()); + CollectionSearchRequest.builder() + .q("city2") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(1, response.getResults().size()); assertEquals(key2, response.getResults().get(0).getCollection().getKey()); assertEquals( 3, collectionService - .list(CollectionSearchRequest.builder().query("c").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .q("c") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 2, collectionService - .list(CollectionSearchRequest.builder().query("dum add").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .q("dum add") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 0, collectionService - .list(CollectionSearchRequest.builder().query("<").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .q("<") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 0, collectionService - .list(CollectionSearchRequest.builder().query("\"<\"").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .q("\"<\"") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 3, collectionService - .list(CollectionSearchRequest.builder().page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 3, collectionService - .list(CollectionSearchRequest.builder().query(" ").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .q(" ") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -199,33 +242,58 @@ public void listTest() { assertEquals( 1, collectionService - .list(CollectionSearchRequest.builder().code("c1").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .code("c1") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 1, collectionService - .list(CollectionSearchRequest.builder().name("n2").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .name("n2") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 1, collectionService .list( - CollectionSearchRequest.builder().code("c1").name("n1").page(DEFAULT_PAGE).build()) + CollectionSearchRequest.builder() + .code("c1") + .name("n1") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 0, collectionService .list( - CollectionSearchRequest.builder().code("c2").name("n1").page(DEFAULT_PAGE).build()) + CollectionSearchRequest.builder() + .code("c2") + .name("n1") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 2, collectionService - .list(CollectionSearchRequest.builder().active(true).page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .active(true) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( @@ -234,7 +302,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .accessionStatus(Collections.singletonList("Institutional")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -244,7 +313,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .accessionStatus(Collections.singletonList("Project")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -254,7 +324,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .contentTypes(Collections.singletonList("Archaeological")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -264,7 +335,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .contentTypes(Arrays.asList("Archaeological", "Biological")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -274,7 +346,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .preservationTypes(Collections.singletonList("SampleDried")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -284,7 +357,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .personalCollection(true) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -294,14 +368,22 @@ public void listTest() { 1, collectionService .list( - CollectionSearchRequest.builder().alternativeCode("alt").page(DEFAULT_PAGE).build()) + CollectionSearchRequest.builder() + .alternativeCode("alt") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 0, collectionService .list( - CollectionSearchRequest.builder().alternativeCode("foo").page(DEFAULT_PAGE).build()) + CollectionSearchRequest.builder() + .alternativeCode("foo") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -311,7 +393,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .country(Collections.singletonList(Country.SPAIN)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults(); assertEquals(1, results.size()); @@ -322,7 +405,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .country(Collections.singletonList(Country.AFGHANISTAN)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -332,7 +416,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .country(Arrays.asList(Country.SPAIN, Country.DENMARK)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -342,7 +427,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .gbifRegion(Collections.singletonList(GbifRegion.EUROPE)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -353,7 +439,8 @@ public void listTest() { CollectionSearchRequest.builder() .country(Collections.singletonList(Country.SPAIN)) .gbifRegion(Collections.singletonList(GbifRegion.ASIA)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -361,14 +448,24 @@ public void listTest() { // city results = collectionService - .list(CollectionSearchRequest.builder().city("city2").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .city("city2") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults(); assertEquals(1, results.size()); assertEquals(key2, results.get(0).getCollection().getKey()); assertEquals( 0, collectionService - .list(CollectionSearchRequest.builder().city("foo").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .city("foo") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -380,7 +477,12 @@ public void listTest() { assertEquals( 1, collectionService - .list(CollectionSearchRequest.builder().query("city3").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .q("city3") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -390,7 +492,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .displayOnNHCPortal(true) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -400,7 +503,11 @@ public void listTest() { 1, collectionService .list( - CollectionSearchRequest.builder().numberSpecimens("100").page(DEFAULT_PAGE).build()) + CollectionSearchRequest.builder() + .numberSpecimens("100") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -408,7 +515,11 @@ public void listTest() { 0, collectionService .list( - CollectionSearchRequest.builder().numberSpecimens("98").page(DEFAULT_PAGE).build()) + CollectionSearchRequest.builder() + .numberSpecimens("98") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -418,7 +529,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .numberSpecimens("* , 100") - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -429,7 +541,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .numberSpecimens("97,300") - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -440,7 +553,8 @@ public void listTest() { .list( CollectionSearchRequest.builder() .sortBy(CollectionsSortField.NUMBER_SPECIMENS) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .get(0) @@ -454,7 +568,8 @@ public void listTest() { CollectionSearchRequest.builder() .sortBy(CollectionsSortField.NUMBER_SPECIMENS) .sortOrder(SortOrder.DESC) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .get(0) @@ -465,7 +580,12 @@ public void listTest() { assertEquals( 0, collectionService - .list(CollectionSearchRequest.builder().query("city3").page(DEFAULT_PAGE).build()) + .list( + CollectionSearchRequest.builder() + .q("city3") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); } @@ -499,7 +619,8 @@ public void listByInstitutionTest() { collectionService.list( CollectionSearchRequest.builder() .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(2, response.getResults().size()); @@ -507,7 +628,8 @@ public void listByInstitutionTest() { collectionService.list( CollectionSearchRequest.builder() .institution(institutionKey2) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); @@ -515,7 +637,8 @@ public void listByInstitutionTest() { collectionService.list( CollectionSearchRequest.builder() .institution(UUID.randomUUID()) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(0, response.getResults().size()); @@ -523,7 +646,8 @@ public void listByInstitutionTest() { collectionService.list( CollectionSearchRequest.builder() .institutionKeys(Collections.singletonList(institutionKey1)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(2, response.getResults().size()); @@ -531,16 +655,18 @@ public void listByInstitutionTest() { collectionService.list( CollectionSearchRequest.builder() .institutionKeys(Arrays.asList(institutionKey1, institutionKey2)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(3, response.getResults().size()); response = collectionService.list( CollectionSearchRequest.builder() - .query(collection1.getCode()) + .q(collection1.getCode()) .institutionKeys(Arrays.asList(institutionKey1, institutionKey2)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); } @@ -750,36 +876,40 @@ public void listMultipleParamsTest() { PagingResponse response = collectionService.list( CollectionSearchRequest.builder() - .query("code1") + .q("code1") .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); response = collectionService.list( CollectionSearchRequest.builder() - .query("foo") + .q("foo") .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(0, response.getResults().size()); response = collectionService.list( CollectionSearchRequest.builder() - .query("code2") + .q("code2") .institution(institutionKey2) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(0, response.getResults().size()); response = collectionService.list( CollectionSearchRequest.builder() - .query("code2") + .q("code2") .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); @@ -797,36 +927,40 @@ public void listMultipleParamsTest() { response = collectionService.list( CollectionSearchRequest.builder() - .query("Name1") + .q("Name1") .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); response = collectionService.list( CollectionSearchRequest.builder() - .query("abcde") + .q("abcde") .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); response = collectionService.list( CollectionSearchRequest.builder() - .query("aa1@aa.com") + .q("aa1@aa.com") .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); response = collectionService.list( CollectionSearchRequest.builder() - .query("aves") + .q("aves") .institution(institutionKey1) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); } @@ -885,7 +1019,7 @@ public void listDeletedTest() { collection4.setName("Collection name4"); UUID key4 = collectionService.create(collection4); - CollectionSearchRequest searchRequest = new CollectionSearchRequest(); + CollectionSearchRequest searchRequest = CollectionSearchRequest.builder().build(); searchRequest.setReplacedBy(key4); assertEquals(0, collectionService.listDeleted(searchRequest).getResults().size()); collectionService.replace(key3, key4); @@ -901,22 +1035,29 @@ public void listWithoutParametersTest() { UUID key3 = collectionService.create(collection3); PagingResponse response = - collectionService.list(CollectionSearchRequest.builder().page(DEFAULT_PAGE).build()); + collectionService.list( + CollectionSearchRequest.builder() + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(3, response.getResults().size()); collectionService.delete(key3); - response = collectionService.list(CollectionSearchRequest.builder().page(DEFAULT_PAGE).build()); + response = + collectionService.list( + CollectionSearchRequest.builder() + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(2, response.getResults().size()); response = - collectionService.list( - CollectionSearchRequest.builder().page(new PagingRequest(0L, 1)).build()); + collectionService.list(CollectionSearchRequest.builder().limit(1).offset(0L).build()); assertEquals(1, response.getResults().size()); response = - collectionService.list( - CollectionSearchRequest.builder().page(new PagingRequest(0L, 0)).build()); + collectionService.list(CollectionSearchRequest.builder().limit(0).offset(0L).build()); assertEquals(0, response.getResults().size()); } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionsSearchIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionsSearchIT.java index eabe0062c7..62cd06b7fc 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionsSearchIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/CollectionsSearchIT.java @@ -13,33 +13,46 @@ */ package org.gbif.registry.ws.it.collections.service; +import static org.gbif.registry.domain.collections.TypeParam.COLLECTION; +import static org.gbif.registry.domain.collections.TypeParam.INSTITUTION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import lombok.SneakyThrows; import org.gbif.api.model.collections.Address; import org.gbif.api.model.collections.AlternativeCode; import org.gbif.api.model.collections.Collection; import org.gbif.api.model.collections.Institution; -import org.gbif.api.model.collections.search.CollectionsSearchResponse; +import org.gbif.api.model.collections.request.CollectionDescriptorsSearchRequest; +import org.gbif.api.model.collections.request.InstitutionSearchRequest; +import org.gbif.api.model.collections.search.CollectionSearchResponse; +import org.gbif.api.model.collections.search.CollectionsFullSearchResponse; +import org.gbif.api.model.collections.search.InstitutionSearchResponse; +import org.gbif.api.model.common.export.ExportFormat; +import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.registry.Identifier; import org.gbif.api.service.collections.CollectionService; +import org.gbif.api.service.collections.DescriptorsService; import org.gbif.api.service.collections.InstitutionService; +import org.gbif.api.vocabulary.CollectionsSortField; import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.IdentifierType; +import org.gbif.api.vocabulary.SortOrder; +import org.gbif.api.vocabulary.TypeStatus; import org.gbif.registry.database.TestCaseDatabaseInitializer; -import org.gbif.registry.search.dataset.service.collections.CollectionsSearchService; +import org.gbif.registry.service.collections.CollectionsSearchService; +import org.gbif.registry.test.mocks.NubResourceClientMock; import org.gbif.ws.client.filter.SimplePrincipalProvider; - -import java.util.Collections; -import java.util.List; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; - -import static org.gbif.registry.domain.collections.TypeParam.COLLECTION; -import static org.gbif.registry.domain.collections.TypeParam.INSTITUTION; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.StreamUtils; /** Tests the {@link CollectionsSearchService} * */ public class CollectionsSearchIT extends BaseServiceIT { @@ -50,6 +63,7 @@ public class CollectionsSearchIT extends BaseServiceIT { private final CollectionsSearchService searchService; private final InstitutionService institutionService; private final CollectionService collectionService; + private final DescriptorsService descriptorsService; private final Institution i1 = new Institution(); private final Institution i11 = new Institution(); @@ -62,13 +76,16 @@ public CollectionsSearchIT( SimplePrincipalProvider simplePrincipalProvider, CollectionsSearchService collectionsSearchService, InstitutionService institutionService, - CollectionService collectionService) { + CollectionService collectionService, + DescriptorsService descriptorsService) { super(simplePrincipalProvider); this.searchService = collectionsSearchService; this.institutionService = institutionService; this.collectionService = collectionService; + this.descriptorsService = descriptorsService; } + @SneakyThrows @BeforeEach public void loadData() { i1.setCode("I1"); @@ -100,51 +117,77 @@ public void loadData() { addressC1.setProvince("Asturias"); addressC1.setAddress("fake street"); c1.setAddress(addressC1); + c1.setNumberSpecimens(4000); c1.setDisplayOnNHCPortal(false); collectionService.create(c1); + descriptorsService.createDescriptorGroup( + StreamUtils.copyToByteArray( + new ClassPathResource("collections/descriptors.csv").getInputStream()), + ExportFormat.TSV, + "My personal descriptor set", + "description", + c1.getKey()); + + descriptorsService.createDescriptorGroup( + StreamUtils.copyToByteArray( + new ClassPathResource("collections/descriptors2.csv").getInputStream()), + ExportFormat.TSV, + "My descriptor set 2", + "description", + c1.getKey()); + c2.setCode("C2"); c2.setName("Collection 2"); + c2.setNumberSpecimens(1000); c2.setInstitutionKey(i2.getKey()); c2.setAlternativeCodes(Collections.singletonList(new AlternativeCode("CC2", "test"))); c2.getIdentifiers().add(new Identifier(IdentifierType.LSID, "lsid-coll")); collectionService.create(c2); + + descriptorsService.createDescriptorGroup( + StreamUtils.copyToByteArray( + new ClassPathResource("collections/descriptors3.csv").getInputStream()), + ExportFormat.TSV, + "My descriptor set 3", + "unusual description", + c2.getKey()); } @Test public void searchByCodeTest() { - List responses = + List responses = searchService.search("I1", true, null, null, null, 10); assertEquals(3, responses.size()); assertEquals(i1.getKey(), responses.get(0).getKey()); - assertEquals(1, responses.get(0).getMatches().size()); + assertEquals(1, responses.get(0).getHighlights().size()); responses = searchService.search("i1", true, null, null, null, 10); assertEquals(3, responses.size()); assertEquals(i11.getKey(), responses.get(0).getKey()); - assertEquals(2, responses.get(0).getMatches().size()); + assertEquals(2, responses.get(0).getHighlights().size()); } @Test public void searchByNameTest() { - List responses = + List responses = searchService.search("Collection", true, null, null, null, 10); assertEquals(2, responses.size()); responses = searchService.search("Collection 2", true, null, null, null, 10); assertEquals(2, responses.size()); - assertEquals(1, responses.get(0).getMatches().size()); - assertEquals(1, responses.get(1).getMatches().size()); + assertEquals(1, responses.get(0).getHighlights().size()); + assertEquals(1, responses.get(1).getHighlights().size()); responses = searchService.search("Colllection 1", true, null, null, null, 10); assertEquals(2, responses.size()); - assertEquals(1, responses.get(0).getMatches().size()); - assertEquals(1, responses.get(1).getMatches().size()); + assertEquals(1, responses.get(0).getHighlights().size()); + assertEquals(1, responses.get(1).getHighlights().size()); } @Test public void searchByAlternativeCodesTest() { - List responses = + List responses = searchService.search("II2", true, null, null, null, 10); assertEquals(1, responses.size()); assertEquals(i2.getKey(), responses.get(0).getKey()); @@ -155,36 +198,36 @@ public void searchByAlternativeCodesTest() { @Test public void searchByAddressFieldsTest() { - List responses = + List responses = searchService.search("street", true, null, null, null, 10); assertEquals(2, responses.size()); responses = searchService.search(Country.SPAIN.getIso2LetterCode(), true, null, null, null, 10); assertEquals(1, responses.size()); assertEquals(c1.getKey(), responses.get(0).getKey()); - assertEquals(1, responses.get(0).getMatches().size()); + assertEquals(1, responses.get(0).getHighlights().size()); responses = searchService.search("oviedo", true, null, null, null, 10); assertEquals(1, responses.size()); assertEquals(c1.getKey(), responses.get(0).getKey()); - assertEquals(1, responses.get(0).getMatches().size()); + assertEquals(1, responses.get(0).getHighlights().size()); responses = searchService.search("street asturias", true, null, null, null, 10); assertEquals(1, responses.size()); assertEquals(c1.getKey(), responses.get(0).getKey()); - assertEquals(2, responses.get(0).getMatches().size()); + assertEquals(2, responses.get(0).getHighlights().size()); } @Test public void searchWithoutHighlightTest() { - List responses = + List responses = searchService.search("Collection", false, null, null, null, 10); - assertNull(responses.get(0).getMatches()); + assertTrue(responses.get(0).getHighlights().isEmpty()); } @Test public void noMatchesTest() { - List responses = + List responses = searchService.search("nothing", false, null, null, null, 10); assertEquals(0, responses.size()); @@ -197,7 +240,7 @@ public void noMatchesTest() { @Test public void displayOnNHCPortalTest() { - List responses = + List responses = searchService.search(null, false, null, true, null, 10); assertEquals(3, responses.size()); @@ -207,7 +250,7 @@ public void displayOnNHCPortalTest() { @Test public void typeParamTest() { - List responses = + List responses = searchService.search(null, false, INSTITUTION, null, null, 10); assertEquals(3, responses.size()); assertTrue(responses.stream().allMatch(d -> d.getType().equals("institution"))); @@ -222,7 +265,7 @@ public void typeParamTest() { @Test public void countryFilterTest() { - List responses = + List responses = searchService.search(null, true, null, null, Country.SPAIN, 10); assertEquals(1, responses.size()); assertEquals(c1.getKey(), responses.get(0).getKey()); @@ -239,9 +282,217 @@ public void countryFilterTest() { responses = searchService.search("C1", true, null, null, Country.SPAIN, 10); assertEquals(1, responses.size()); assertEquals(c1.getKey(), responses.get(0).getKey()); - assertEquals(1, responses.get(0).getMatches().size()); + assertEquals(1, responses.get(0).getHighlights().size()); responses = searchService.search("C1", true, null, null, Country.DENMARK, 10); assertEquals(0, responses.size()); } + + @Test + public void searchInstitutionsTest() { + PagingResponse response = + searchService.searchInstitutions(InstitutionSearchRequest.builder().build()); + assertEquals(3, response.getResults().size()); + assertEquals(3, response.getCount()); + + assertEquals( + 1, + searchService + .searchInstitutions( + InstitutionSearchRequest.builder() + .country(Collections.singletonList(i1.getAddress().getCountry())) + .build()) + .getResults() + .size()); + + assertEquals( + 1, + searchService + .searchInstitutions(InstitutionSearchRequest.builder().q(i1.getName()).build()) + .getResults() + .size()); + + response = + searchService.searchInstitutions( + InstitutionSearchRequest.builder().q("different").hl(true).build()); + assertEquals(1, response.getResults().size()); + assertEquals(1, response.getResults().get(0).getHighlights().size()); + } + + @Test + public void searchCollectionsTest() { + assertEquals( + 2, + searchService + .searchCollections(CollectionDescriptorsSearchRequest.builder().build()) + .getResults() + .size()); + + assertEquals( + 1, + searchService + .searchCollections(CollectionDescriptorsSearchRequest.builder().q("Asturias").build()) + .getResults() + .size()); + + PagingResponse response = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder().q("Asturias").hl(true).build()); + assertEquals(1, response.getResults().size()); + assertEquals(1, response.getCount()); + assertEquals(1, response.getResults().get(0).getHighlights().size()); + assertTrue(response.getResults().get(0).getDescriptorMatches().isEmpty()); + + response = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder().q("aves").build()); + assertEquals(1, response.getResults().size()); + assertEquals(1, response.getCount()); + assertTrue(response.getResults().get(0).getHighlights().isEmpty()); + assertEquals(2, response.getResults().get(0).getDescriptorMatches().size()); + + response = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder().q("aves").hl(true).build()); + assertEquals(1, response.getResults().size()); + assertEquals(1, response.getCount()); + assertEquals(2, response.getResults().get(0).getDescriptorMatches().size()); + assertEquals(1, response.getResults().get(0).getHighlights().size()); + + assertDescriptorSearch( + 1, + 1, + CollectionDescriptorsSearchRequest.builder() + .descriptorCountry(Collections.singletonList(Country.UNITED_STATES)) + .build()); + assertDescriptorSearch( + 1, + 2, + CollectionDescriptorsSearchRequest.builder() + .descriptorCountry(Arrays.asList(Country.PORTUGAL, Country.UNITED_STATES)) + .build()); + assertDescriptorSearch( + 0, + 0, + CollectionDescriptorsSearchRequest.builder() + .descriptorCountry(Collections.singletonList(Country.AFGHANISTAN)) + .build()); + assertDescriptorSearch( + 1, 2, CollectionDescriptorsSearchRequest.builder().individualCount("10, 50").build()); + assertDescriptorSearch( + 1, + 3, + CollectionDescriptorsSearchRequest.builder() + .recordedBy(Collections.singletonList("Marcos")) + .build()); + assertDescriptorSearch( + 1, + 2, + CollectionDescriptorsSearchRequest.builder() + .typeStatus(Arrays.asList(TypeStatus.COTYPE.name(), TypeStatus.EPITYPE.name())) + .build()); + + assertDescriptorSearch( + 1, + null, + CollectionDescriptorsSearchRequest.builder() + .usageName(Collections.singletonList(NubResourceClientMock.DEFAULT_USAGE.getName())) + .build()); + + assertDescriptorSearch( + 1, + 1, + CollectionDescriptorsSearchRequest.builder() + .descriptorCountry(Collections.singletonList(Country.PORTUGAL)) + .q("cc2") + .build()); + + assertDescriptorSearch( + 2, + null, + CollectionDescriptorsSearchRequest.builder() + .descriptorCountry(Collections.singletonList(Country.DENMARK)) + .build()); + + PagingResponse first = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder() + .descriptorCountry(Collections.singletonList(Country.DENMARK)) + .limit(1) + .build()); + assertEquals(1, first.getResults().size()); + PagingResponse second = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder() + .descriptorCountry(Collections.singletonList(Country.DENMARK)) + .offset(1L) + .limit(1) + .build()); + assertEquals(1, first.getResults().size()); + assertNotEquals(first.getResults().get(0).getKey(), second.getResults().get(0).getKey()); + + PagingResponse sorted = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder() + .sortBy(CollectionsSortField.NUMBER_SPECIMENS) + .sortOrder(SortOrder.ASC) + .offset(0L) + .limit(1) + .build()); + assertEquals(c2.getKey(), sorted.getResults().get(0).getKey()); + sorted = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder() + .sortBy(CollectionsSortField.NUMBER_SPECIMENS) + .sortOrder(SortOrder.ASC) + .offset(1L) + .limit(1) + .build()); + assertEquals(c1.getKey(), sorted.getResults().get(0).getKey()); + sorted = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder() + .sortBy(CollectionsSortField.NUMBER_SPECIMENS) + .sortOrder(SortOrder.DESC) + .offset(0L) + .limit(1) + .build()); + assertEquals(c1.getKey(), sorted.getResults().get(0).getKey()); + + PagingResponse result = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder().q("personal").hl(true).build()); + assertEquals(1, result.getResults().size()); + assertEquals(c1.getKey(), result.getResults().get(0).getKey()); + assertEquals( + "descriptorGroup.title", + result.getResults().get(0).getHighlights().iterator().next().getField()); + assertTrue(result.getResults().get(0).getDescriptorMatches().isEmpty()); + + result = + searchService.searchCollections( + CollectionDescriptorsSearchRequest.builder().q("unusual").hl(true).build()); + assertEquals(1, result.getResults().size()); + assertEquals(c2.getKey(), result.getResults().get(0).getKey()); + assertEquals( + "descriptorGroup.description", + result.getResults().get(0).getHighlights().iterator().next().getField()); + assertTrue(result.getResults().get(0).getDescriptorMatches().isEmpty()); + } + + private void assertDescriptorSearch( + int expectedResults, + Integer expectedDescriptors, + CollectionDescriptorsSearchRequest searchRequest) { + PagingResponse response = + searchService.searchCollections(searchRequest); + assertEquals(expectedResults, response.getResults().size()); + assertEquals(expectedResults, response.getCount()); + if (expectedResults > 0) { + assertTrue(response.getResults().stream().noneMatch(v -> v.getDescriptorMatches().isEmpty())); + } + if (expectedResults == 1 && expectedDescriptors != null) { + assertEquals(expectedDescriptors, response.getResults().get(0).getDescriptorMatches().size()); + } + } } 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 e257af5bee..3623e43cfa 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 @@ -13,12 +13,20 @@ */ package org.gbif.registry.ws.it.collections.service; +import static org.junit.jupiter.api.Assertions.*; + +import java.math.BigDecimal; +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import javax.validation.ConstraintViolationException; +import javax.validation.ValidationException; import org.gbif.api.model.collections.*; import org.gbif.api.model.collections.latimercore.ContactDetail; import org.gbif.api.model.collections.latimercore.MeasurementOrFact; import org.gbif.api.model.collections.latimercore.OrganisationalUnit; import org.gbif.api.model.collections.request.InstitutionSearchRequest; -import org.gbif.api.model.common.paging.PagingRequest; import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.registry.Identifier; import org.gbif.api.model.registry.Organization; @@ -39,16 +47,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import javax.validation.ConstraintViolationException; -import javax.validation.ValidationException; -import java.math.BigDecimal; -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; - /** Tests the {@link InstitutionService}. */ public class InstitutionServiceIT extends BaseCollectionEntityServiceIT { @@ -118,30 +116,46 @@ public void listTest() { sourceMetadata.setSource(Source.IH_IRN); institution3.setMasterSourceMetadata(sourceMetadata); UUID key3 = institutionService.create(institution3); - institutionService.addMasterSourceMetadata(key3,sourceMetadata); + institutionService.addMasterSourceMetadata(key3, sourceMetadata); PagingResponse response = institutionService.list( - InstitutionSearchRequest.builder().query("dummy").page(DEFAULT_PAGE).build()); + (InstitutionSearchRequest.builder().q("dummy").limit(DEFAULT_PAGE.getLimit())) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(3, response.getResults().size()); - response = institutionService.list(InstitutionSearchRequest.builder().source(Source.IH_IRN).sourceId("test-123").build()); - assertEquals(1,response.getResults().size()); + response = + institutionService.list( + InstitutionSearchRequest.builder().source(Source.IH_IRN).sourceId("test-123").build()); + assertEquals(1, response.getResults().size()); // empty queries are ignored and return all elements response = institutionService.list( - InstitutionSearchRequest.builder().query("").page(DEFAULT_PAGE).build()); + InstitutionSearchRequest.builder() + .q("") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(3, response.getResults().size()); response = institutionService.list( - InstitutionSearchRequest.builder().query("city").page(DEFAULT_PAGE).build()); + InstitutionSearchRequest.builder() + .q("city") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(1, response.getResults().size()); assertEquals(key1, response.getResults().get(0).getKey()); response = institutionService.list( - InstitutionSearchRequest.builder().query("city2").page(DEFAULT_PAGE).build()); + InstitutionSearchRequest.builder() + .q("city2") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(1, response.getResults().size()); assertEquals(key2, response.getResults().get(0).getKey()); @@ -152,7 +166,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .institutionKeys(Arrays.asList(institution1.getKey(), institution2.getKey())) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -162,9 +177,10 @@ public void listTest() { institutionService .list( InstitutionSearchRequest.builder() - .query(institution1.getCode()) + .q(institution1.getCode()) .institutionKeys(Arrays.asList(institution1.getKey(), institution2.getKey())) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -173,27 +189,47 @@ public void listTest() { assertEquals( 1, institutionService - .list(InstitutionSearchRequest.builder().code("c1").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .code("c1") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 1, institutionService - .list(InstitutionSearchRequest.builder().name("n2").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .name("n2") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 1, institutionService .list( - InstitutionSearchRequest.builder().code("c1").name("n1").page(DEFAULT_PAGE).build()) + InstitutionSearchRequest.builder() + .code("c1") + .name("n1") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 0, institutionService .list( - InstitutionSearchRequest.builder().code("c2").name("n1").page(DEFAULT_PAGE).build()) + InstitutionSearchRequest.builder() + .code("c2") + .name("n1") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -201,43 +237,77 @@ public void listTest() { assertEquals( 3, institutionService - .list(InstitutionSearchRequest.builder().query("c").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("c") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 2, institutionService - .list(InstitutionSearchRequest.builder().query("dum add").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("dum add") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 0, institutionService - .list(InstitutionSearchRequest.builder().query("<").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("<") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 0, institutionService - .list(InstitutionSearchRequest.builder().query("\"<\"").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("\"<\"") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 3, institutionService - .list(InstitutionSearchRequest.builder().page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 3, institutionService - .list(InstitutionSearchRequest.builder().query(" ").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q(" ") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 2, institutionService - .list(InstitutionSearchRequest.builder().active(true).page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .active(true) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( @@ -246,7 +316,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .type(Collections.singletonList("Herbarium")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -256,7 +327,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .institutionalGovernance(Collections.singletonList("Academic")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -266,7 +338,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .disciplines(Collections.singletonList("Anthropology")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -276,7 +349,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .disciplines(Arrays.asList("Archaeology", "Anthropology")) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -284,19 +358,28 @@ public void listTest() { // alternative code response = institutionService.list( - InstitutionSearchRequest.builder().alternativeCode("alt").page(DEFAULT_PAGE).build()); + InstitutionSearchRequest.builder() + .alternativeCode("alt") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(1, response.getResults().size()); response = institutionService.list( - InstitutionSearchRequest.builder().alternativeCode("foo").page(DEFAULT_PAGE).build()); + InstitutionSearchRequest.builder() + .alternativeCode("foo") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(0, response.getResults().size()); response = institutionService.list( InstitutionSearchRequest.builder() .country(Collections.singletonList(Country.SPAIN)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); assertEquals(key2, response.getResults().get(0).getKey()); @@ -304,28 +387,32 @@ public void listTest() { institutionService.list( InstitutionSearchRequest.builder() .country(Collections.singletonList(Country.AFGHANISTAN)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(0, response.getResults().size()); response = institutionService.list( InstitutionSearchRequest.builder() .country(Arrays.asList(Country.SPAIN, Country.AFGHANISTAN)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(1, response.getResults().size()); response = institutionService.list( InstitutionSearchRequest.builder() .country(Arrays.asList(Country.SPAIN, Country.DENMARK)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(2, response.getResults().size()); response = institutionService.list( InstitutionSearchRequest.builder() .gbifRegion(Collections.singletonList(GbifRegion.EUROPE)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(2, response.getResults().size()); response = @@ -333,18 +420,27 @@ public void listTest() { InstitutionSearchRequest.builder() .country(Collections.singletonList(Country.SPAIN)) .gbifRegion(Collections.singletonList(GbifRegion.ASIA)) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()); assertEquals(0, response.getResults().size()); response = institutionService.list( - InstitutionSearchRequest.builder().city("city2").page(DEFAULT_PAGE).build()); + InstitutionSearchRequest.builder() + .city("city2") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(1, response.getResults().size()); assertEquals(key2, response.getResults().get(0).getKey()); response = institutionService.list( - InstitutionSearchRequest.builder().city("foo").page(DEFAULT_PAGE).build()); + InstitutionSearchRequest.builder() + .city("foo") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(0, response.getResults().size()); // update address @@ -355,7 +451,12 @@ public void listTest() { assertEquals( 1, institutionService - .list(InstitutionSearchRequest.builder().query("city3").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("city3") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -365,7 +466,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .displayOnNHCPortal(true) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -377,7 +479,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .numberSpecimens("100") - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -386,7 +489,11 @@ public void listTest() { 0, institutionService .list( - InstitutionSearchRequest.builder().numberSpecimens("98").page(DEFAULT_PAGE).build()) + InstitutionSearchRequest.builder() + .numberSpecimens("98") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -396,7 +503,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .numberSpecimens("* , 100") - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -407,7 +515,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .numberSpecimens("97,300") - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .size()); @@ -418,7 +527,8 @@ public void listTest() { .list( InstitutionSearchRequest.builder() .sortBy(CollectionsSortField.NUMBER_SPECIMENS) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .get(0) @@ -431,7 +541,8 @@ public void listTest() { InstitutionSearchRequest.builder() .sortBy(CollectionsSortField.NUMBER_SPECIMENS) .sortOrder(SortOrder.DESC) - .page(DEFAULT_PAGE) + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) .build()) .getResults() .get(0) @@ -441,7 +552,12 @@ public void listTest() { assertEquals( 0, institutionService - .list(InstitutionSearchRequest.builder().query("city3").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("city3") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); @@ -459,28 +575,48 @@ public void listTest() { assertEquals( 1, institutionService - .list(InstitutionSearchRequest.builder().query("Name1").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("Name1") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 1, institutionService - .list(InstitutionSearchRequest.builder().query("aa1@aa.com").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("aa1@aa.com") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 1, institutionService - .list(InstitutionSearchRequest.builder().query("aves").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("aves") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); assertEquals( 1, institutionService - .list(InstitutionSearchRequest.builder().query("abcde").page(DEFAULT_PAGE).build()) + .list( + InstitutionSearchRequest.builder() + .q("abcde") + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()) .getResults() .size()); } @@ -539,7 +675,7 @@ public void listDeletedTest() { institution4.setName("Institution name4"); UUID key4 = institutionService.create(institution4); - InstitutionSearchRequest searchRequest = new InstitutionSearchRequest(); + InstitutionSearchRequest searchRequest = InstitutionSearchRequest.builder().build(); searchRequest.setReplacedBy(key4); assertEquals(0, institutionService.listDeleted(searchRequest).getResults().size()); institutionService.replace(key3, key4); @@ -558,23 +694,29 @@ public void listWithoutParametersTest() { UUID key3 = institutionService.create(institution3); PagingResponse response = - institutionService.list(InstitutionSearchRequest.builder().page(DEFAULT_PAGE).build()); + institutionService.list( + InstitutionSearchRequest.builder() + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(3, response.getResults().size()); institutionService.delete(key3); response = - institutionService.list(InstitutionSearchRequest.builder().page(DEFAULT_PAGE).build()); + institutionService.list( + InstitutionSearchRequest.builder() + .limit(DEFAULT_PAGE.getLimit()) + .offset(DEFAULT_PAGE.getOffset()) + .build()); assertEquals(2, response.getResults().size()); response = - institutionService.list( - InstitutionSearchRequest.builder().page(new PagingRequest(0L, 1)).build()); + institutionService.list(InstitutionSearchRequest.builder().limit(1).offset(0L).build()); assertEquals(1, response.getResults().size()); response = - institutionService.list( - InstitutionSearchRequest.builder().page(new PagingRequest(0L, 0)).build()); + institutionService.list(InstitutionSearchRequest.builder().limit(0).offset(0L).build()); assertEquals(0, response.getResults().size()); } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/descriptors/DescriptorsServiceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/descriptors/DescriptorsServiceIT.java new file mode 100644 index 0000000000..5279e08ab8 --- /dev/null +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/descriptors/DescriptorsServiceIT.java @@ -0,0 +1,208 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gbif.registry.ws.it.collections.service.descriptors; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import lombok.SneakyThrows; +import org.gbif.api.model.collections.Collection; +import org.gbif.api.model.collections.MasterSourceMetadata; +import org.gbif.api.model.collections.descriptors.Descriptor; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; +import org.gbif.api.model.collections.request.DescriptorGroupSearchRequest; +import org.gbif.api.model.collections.request.DescriptorSearchRequest; +import org.gbif.api.model.common.export.ExportFormat; +import org.gbif.api.model.common.paging.PagingResponse; +import org.gbif.api.model.registry.MachineTag; +import org.gbif.api.service.collections.CollectionService; +import org.gbif.api.service.collections.DescriptorsService; +import org.gbif.api.util.GrSciCollUtils; +import org.gbif.api.vocabulary.collections.Source; +import org.gbif.registry.database.TestCaseDatabaseInitializer; +import org.gbif.registry.test.mocks.NubResourceClientMock; +import org.gbif.registry.ws.it.collections.service.BaseServiceIT; +import org.gbif.ws.client.filter.SimplePrincipalProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.StreamUtils; + +/** Tests the {@link CollectionService}. */ +public class DescriptorsServiceIT extends BaseServiceIT { + + @RegisterExtension + protected TestCaseDatabaseInitializer databaseRule = new TestCaseDatabaseInitializer(); + + private final DescriptorsService descriptorsService; + private final CollectionService collectionService; + + @Autowired + public DescriptorsServiceIT( + DescriptorsService descriptorsService, + CollectionService collectionService, + SimplePrincipalProvider principalProvider) { + super(principalProvider); + this.descriptorsService = descriptorsService; + this.collectionService = collectionService; + } + + @Test + @SneakyThrows + public void descriptorsTest() { + Collection collection = new Collection(); + collection.setCode("c1"); + collection.setName("n1"); + collectionService.create(collection); + + Resource descriptorsFile = new ClassPathResource("collections/descriptors.csv"); + long descriptorGroupKey = + descriptorsService.createDescriptorGroup( + StreamUtils.copyToByteArray(descriptorsFile.getInputStream()), + ExportFormat.TSV, + "My descriptor set", + "description", + collection.getKey()); + assertTrue(descriptorGroupKey > 0); + + assertEquals( + 1, + descriptorsService + .listDescriptorGroups( + collection.getKey(), DescriptorGroupSearchRequest.builder().build()) + .getResults() + .size()); + + PagingResponse descriptors = + descriptorsService.listDescriptors(DescriptorSearchRequest.builder().build()); + assertEquals(5, descriptors.getResults().size()); + assertTrue(descriptors.getResults().stream().allMatch(r -> r.getVerbatim().size() == 4)); + + assertEquals( + 0, + descriptorsService + .listDescriptors( + DescriptorSearchRequest.builder() + .usageName(Collections.singletonList("foo")) + .build()) + .getResults() + .size()); + + assertEquals( + 5, + descriptorsService + .listDescriptors( + DescriptorSearchRequest.builder() + .usageKey( + Collections.singletonList(NubResourceClientMock.DEFAULT_USAGE.getKey())) + .build()) + .getResults() + .size()); + + assertEquals( + 5, + descriptorsService + .listDescriptors( + DescriptorSearchRequest.builder() + .taxonKey( + Collections.singletonList( + NubResourceClientMock.DEFAULT_HIGHEST_USAGE.getKey())) + .build()) + .getResults() + .size()); + + Resource descriptorsFile2 = new ClassPathResource("collections/descriptors2.csv"); + descriptorsService.updateDescriptorGroup( + descriptorGroupKey, + StreamUtils.copyToByteArray(descriptorsFile2.getInputStream()), + ExportFormat.TSV, + "My descriptor set", + "description"); + + descriptors = descriptorsService.listDescriptors(DescriptorSearchRequest.builder().build()); + assertEquals(4, descriptors.getResults().size()); + assertTrue(descriptors.getResults().stream().allMatch(r -> r.getVerbatim().size() == 4)); + + descriptorsService.deleteDescriptorGroup(descriptorGroupKey); + assertEquals( + 0, + descriptorsService.listDescriptors(DescriptorSearchRequest.builder().build()).getCount()); + assertEquals( + 0, + descriptorsService + .listDescriptorGroups( + collection.getKey(), DescriptorGroupSearchRequest.builder().build()) + .getCount()); + } + + @Test + @SneakyThrows + public void ihDescriptorsTest() { + Collection collection = new Collection(); + collection.setCode("c1"); + collection.setName("n1"); + collectionService.create(collection); + + collectionService.addMasterSourceMetadata( + collection.getKey(), new MasterSourceMetadata(Source.IH_IRN, "dsfgds")); + + String title = "My descriptor set"; + String description = "description"; + + Resource descriptorsFile = new ClassPathResource("collections/descriptors.csv"); + long descriptorGroupKey = + descriptorsService.createDescriptorGroup( + StreamUtils.copyToByteArray(descriptorsFile.getInputStream()), + ExportFormat.TSV, + title, + description, + collection.getKey()); + assertTrue(descriptorGroupKey > 0); + + assertEquals( + 1, + descriptorsService + .listDescriptorGroups( + collection.getKey(), DescriptorGroupSearchRequest.builder().build()) + .getResults() + .size()); + + PagingResponse descriptors = + descriptorsService.listDescriptors(DescriptorSearchRequest.builder().build()); + assertEquals(5, descriptors.getResults().size()); + + collectionService.addMachineTag( + collection.getKey(), + new MachineTag( + GrSciCollUtils.IH_NS, + GrSciCollUtils.COLL_SUMMARY_MT, + String.valueOf(descriptorGroupKey))); + + descriptorsService.updateDescriptorGroup( + descriptorGroupKey, + StreamUtils.copyToByteArray(descriptorsFile.getInputStream()), + ExportFormat.TSV, + "foo", + "foo"); + + DescriptorGroup updated = descriptorsService.getDescriptorGroup(descriptorGroupKey); + assertEquals(title, updated.getTitle()); + assertEquals(description, updated.getDescription()); + + descriptorsService.deleteDescriptorGroup(descriptorGroupKey); + assertNull(descriptorsService.getDescriptorGroup(descriptorGroupKey).getDeleted()); + } +} diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/suggestions/BaseChangeSuggestionServiceIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/suggestions/BaseChangeSuggestionServiceIT.java index 8ffb6118ad..6ff3fe574d 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/suggestions/BaseChangeSuggestionServiceIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/collections/service/suggestions/BaseChangeSuggestionServiceIT.java @@ -457,26 +457,26 @@ public void listTest() { // When PagingResponse results = - changeSuggestionService.list(Status.APPLIED, null, null, null, DEFAULT_PAGE); + changeSuggestionService.list(Status.APPLIED, null, null, null, null, DEFAULT_PAGE); // Then assertEquals(0, results.getResults().size()); assertEquals(0, results.getCount()); // When - results = changeSuggestionService.list(null, Type.CREATE, null, null, DEFAULT_PAGE); + results = changeSuggestionService.list(null, Type.CREATE, null, null, null, DEFAULT_PAGE); // Then assertEquals(1, results.getResults().size()); assertEquals(1, results.getCount()); // When - results = changeSuggestionService.list(null, null, null, entity2Key, DEFAULT_PAGE); + results = changeSuggestionService.list(null, null, null, entity2Key, null, DEFAULT_PAGE); // Then assertEquals(1, results.getResults().size()); assertEquals(1, results.getCount()); // When - user with no rights can't see the proposer email resetSecurityContext("user", UserRole.USER); - results = changeSuggestionService.list(null, null, null, entity2Key, DEFAULT_PAGE); + results = changeSuggestionService.list(null, null, null, entity2Key, null, DEFAULT_PAGE); // Then assertTrue(results.getResults().stream().allMatch(v -> v.getProposerEmail() == null)); } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/CollectionMapperIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/CollectionMapperIT.java index b5386818da..7a27058fc6 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/CollectionMapperIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/CollectionMapperIT.java @@ -36,7 +36,7 @@ import org.gbif.registry.persistence.mapper.collections.CollectionMapper; import org.gbif.registry.persistence.mapper.collections.MasterSourceSyncMetadataMapper; import org.gbif.registry.persistence.mapper.collections.dto.CollectionDto; -import org.gbif.registry.persistence.mapper.collections.params.CollectionSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.CollectionListParams; import org.gbif.registry.search.test.EsManageServer; import org.gbif.registry.ws.it.BaseItTest; import org.gbif.ws.client.filter.SimplePrincipalProvider; @@ -245,44 +245,44 @@ public void listTest() { Pageable page = PAGE.apply(2, 0L); List dtos = - collectionMapper.list(CollectionSearchParams.builder().page(page).build()); + collectionMapper.list(CollectionListParams.builder().page(page).build()); assertEquals(2, dtos.size()); page = PAGE.apply(5, 0L); - assertSearch(CollectionSearchParams.builder().sourceId("test-123").source(Source.IH_IRN).build(),1,col5.getKey()); - assertSearch(CollectionSearchParams.builder().page(page).build(), 5); - assertSearch(CollectionSearchParams.builder().code("c1").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().code("C1").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().name("n2").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().code("c3").name("n3").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().code("c1").name("n3").page(page).build(), 0); + assertSearch(CollectionListParams.builder().sourceId("test-123").source(Source.IH_IRN).build(),1,col5.getKey()); + assertSearch(CollectionListParams.builder().page(page).build(), 5); + assertSearch(CollectionListParams.builder().code("c1").page(page).build(), 1); + assertSearch(CollectionListParams.builder().code("C1").page(page).build(), 1); + assertSearch(CollectionListParams.builder().name("n2").page(page).build(), 1); + assertSearch(CollectionListParams.builder().code("c3").name("n3").page(page).build(), 1); + assertSearch(CollectionListParams.builder().code("c1").name("n3").page(page).build(), 0); assertSearch( - CollectionSearchParams.builder().fuzzyName("nime of fourth collection").page(page).build(), + CollectionListParams.builder().fuzzyName("nime of fourth collection").page(page).build(), 1); assertSearch( - CollectionSearchParams.builder().query("nime of fourth collection").page(page).build(), 0); + CollectionListParams.builder().query("nime of fourth collection").page(page).build(), 0); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .countries(Collections.singletonList(Country.DENMARK)) .page(page) .build(), 1); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .countries(Collections.singletonList(Country.SPAIN)) .page(page) .build(), 0); - assertSearch(CollectionSearchParams.builder().city("Odense").page(page).build(), 1); + assertSearch(CollectionListParams.builder().city("Odense").page(page).build(), 1); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .city("Copenhagen") .countries(Collections.singletonList(Country.DENMARK)) .page(page) .build(), 1); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .city("CPH") .countries(Collections.singletonList(Country.DENMARK)) .page(page) @@ -291,24 +291,24 @@ public void listTest() { // machine tags assertSearch( - CollectionSearchParams.builder().machineTagNamespace("dummy").page(page).build(), 0); + CollectionListParams.builder().machineTagNamespace("dummy").page(page).build(), 0); assertSearch( - CollectionSearchParams.builder().machineTagName(mt.getName()).page(page).build(), + CollectionListParams.builder().machineTagName(mt.getName()).page(page).build(), 1, col1.getKey()); assertSearch( - CollectionSearchParams.builder().machineTagName(mt.getName()).page(page).build(), + CollectionListParams.builder().machineTagName(mt.getName()).page(page).build(), 1, col1.getKey()); assertSearch( - CollectionSearchParams.builder().machineTagValue(mt.getValue()).page(page).build(), + CollectionListParams.builder().machineTagValue(mt.getValue()).page(page).build(), 1, col1.getKey()); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .machineTagName(mt.getName()) .machineTagName(mt.getName()) .machineTagValue(mt.getValue()) @@ -318,9 +318,9 @@ public void listTest() { col1.getKey()); // identifiers - assertSearch(CollectionSearchParams.builder().identifier("dummy").page(page).build(), 0); + assertSearch(CollectionListParams.builder().identifier("dummy").page(page).build(), 0); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .machineTagName(mt.getName()) .machineTagName(mt.getName()) .machineTagValue(mt.getValue()) @@ -330,17 +330,17 @@ public void listTest() { col1.getKey()); assertSearch( - CollectionSearchParams.builder().identifierType(identifier.getType()).page(page).build(), + CollectionListParams.builder().identifierType(identifier.getType()).page(page).build(), 1, col2.getKey()); assertSearch( - CollectionSearchParams.builder().identifier(identifier.getIdentifier()).page(page).build(), + CollectionListParams.builder().identifier(identifier.getIdentifier()).page(page).build(), 1, col2.getKey()); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .identifierType(identifier.getType()) .identifier(identifier.getIdentifier()) .page(page) @@ -349,12 +349,12 @@ public void listTest() { col2.getKey()); assertSearch( - CollectionSearchParams.builder().masterSourceType(MasterSourceType.IH).page(page).build(), + CollectionListParams.builder().masterSourceType(MasterSourceType.IH).page(page).build(), 1, col1.getKey()); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .masterSourceType(MasterSourceType.GBIF_REGISTRY) .page(page) .build(), @@ -362,7 +362,7 @@ public void listTest() { col2.getKey()); assertSearch( - CollectionSearchParams.builder() + CollectionListParams.builder() .masterSourceType(MasterSourceType.GRSCICOLL) .page(page) .build(), @@ -396,16 +396,16 @@ public void searchTest() { Pageable page = PAGE.apply(5, 0L); assertSearch( - CollectionSearchParams.builder().query("c1 n1").page(page).build(), 1, col1.getKey()); + CollectionListParams.builder().query("c1 n1").page(page).build(), 1, col1.getKey()); - assertSearch(CollectionSearchParams.builder().query("c2 c1").page(page).build(), 0); - assertSearch(CollectionSearchParams.builder().query("c3").page(page).build(), 0); - assertSearch(CollectionSearchParams.builder().query("n1").page(page).build(), 2); - assertSearch(CollectionSearchParams.builder().query("insecta").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().query("Hymenoptera").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("c2 c1").page(page).build(), 0); + assertSearch(CollectionListParams.builder().query("c3").page(page).build(), 0); + assertSearch(CollectionListParams.builder().query("n1").page(page).build(), 2); + assertSearch(CollectionListParams.builder().query("insecta").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("Hymenoptera").page(page).build(), 1); assertSearch( - CollectionSearchParams.builder().query("dummy address fo ").page(page).build(), + CollectionListParams.builder().query("dummy address fo ").page(page).build(), 1, col1.getKey()); @@ -428,13 +428,13 @@ public void searchTest() { assertNotNull(contact1.getModified()); collectionMapper.addContactPerson(col1.getKey(), contact1.getKey()); - assertSearch(CollectionSearchParams.builder().query("Name1").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().query("Name0").page(page).build(), 0); - assertSearch(CollectionSearchParams.builder().query("Surname1").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().query("aa1@aa.com").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().query("aves").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().query("12345").page(page).build(), 1); - assertSearch(CollectionSearchParams.builder().query("abcde").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("Name1").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("Name0").page(page).build(), 0); + assertSearch(CollectionListParams.builder().query("Surname1").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("aa1@aa.com").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("aves").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("12345").page(page).build(), 1); + assertSearch(CollectionListParams.builder().query("abcde").page(page).build(), 1); } @Test @@ -458,7 +458,7 @@ public void alternativeCodesTest() { collectionMapper.create(coll2); Pageable page = PAGE.apply(1, 0L); - CollectionSearchParams params = CollectionSearchParams.builder().query("c1").page(page).build(); + CollectionListParams params = CollectionListParams.builder().query("c1").page(page).build(); List dtos = collectionMapper.list(params); long count = collectionMapper.count(params); assertEquals(1, dtos.size()); @@ -466,10 +466,10 @@ public void alternativeCodesTest() { assertEquals(2, count); page = PAGE.apply(2, 0L); - assertSearch(CollectionSearchParams.builder().query("c1").page(page).build(), 2); + assertSearch(CollectionListParams.builder().query("c1").page(page).build(), 2); page = PAGE.apply(1, 0L); - params = CollectionSearchParams.builder().query("c2").page(page).build(); + params = CollectionListParams.builder().query("c2").page(page).build(); dtos = collectionMapper.list(params); count = collectionMapper.count(params); assertEquals(1, dtos.size()); @@ -477,15 +477,15 @@ public void alternativeCodesTest() { assertEquals(2, count); page = PAGE.apply(2, 0L); - assertSearch(CollectionSearchParams.builder().query("c2").page(page).build(), 2); + assertSearch(CollectionListParams.builder().query("c2").page(page).build(), 2); assertSearch( - CollectionSearchParams.builder().alternativeCode("c1").page(page).build(), + CollectionListParams.builder().alternativeCode("c1").page(page).build(), 1, coll2.getKey()); } - private List assertSearch(CollectionSearchParams params, int expected) { + private List assertSearch(CollectionListParams params, int expected) { List dtos = collectionMapper.list(params); long count = collectionMapper.count(params); assertEquals(expected, count); @@ -493,7 +493,7 @@ private List assertSearch(CollectionSearchParams params, int expe return dtos; } - private void assertSearch(CollectionSearchParams params, int expected, UUID expectedKey) { + private void assertSearch(CollectionListParams params, int expected, UUID expectedKey) { List dtos = assertSearch(params, expected); assertEquals(expectedKey, dtos.get(0).getCollection().getKey()); } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/CollectionsSearchMapperIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/CollectionsSearchMapperIT.java new file mode 100644 index 0000000000..3f7e0c0881 --- /dev/null +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/CollectionsSearchMapperIT.java @@ -0,0 +1,168 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gbif.registry.ws.it.persistence.mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import org.gbif.api.model.collections.Address; +import org.gbif.api.model.collections.AlternativeCode; +import org.gbif.api.model.collections.Collection; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.License; +import org.gbif.registry.database.TestCaseDatabaseInitializer; +import org.gbif.registry.persistence.mapper.collections.AddressMapper; +import org.gbif.registry.persistence.mapper.collections.CollectionMapper; +import org.gbif.registry.persistence.mapper.collections.CollectionsSearchMapper; +import org.gbif.registry.persistence.mapper.collections.DescriptorsMapper; +import org.gbif.registry.persistence.mapper.collections.dto.CollectionSearchDto; +import org.gbif.registry.persistence.mapper.collections.dto.DescriptorDto; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorsParams; +import org.gbif.registry.search.test.EsManageServer; +import org.gbif.registry.ws.it.BaseItTest; +import org.gbif.ws.client.filter.SimplePrincipalProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; + +public class CollectionsSearchMapperIT extends BaseItTest { + + @RegisterExtension + protected TestCaseDatabaseInitializer databaseRule = + new TestCaseDatabaseInitializer("collection"); + + private CollectionMapper collectionMapper; + private AddressMapper addressMapper; + private DescriptorsMapper descriptorsMapper; + private CollectionsSearchMapper collectionsSearchMapper; + + @Autowired + public CollectionsSearchMapperIT( + CollectionMapper collectionMapper, + AddressMapper addressMapper, + DescriptorsMapper descriptorsMapper, + CollectionsSearchMapper collectionsSearchMapper, + SimplePrincipalProvider principalProvider, + EsManageServer esServer) { + super(principalProvider, esServer); + this.collectionMapper = collectionMapper; + this.addressMapper = addressMapper; + this.descriptorsMapper = descriptorsMapper; + this.collectionsSearchMapper = collectionsSearchMapper; + } + + @Test + public void searchTest() { + UUID collectionKey = UUID.randomUUID(); + + Collection collection = new Collection(); + collection.setKey(collectionKey); + collection.setAccessionStatus("Institutional"); + collection.setCode("CODE"); + collection.setName("NAME"); + collection.setCreatedBy("test"); + collection.setModifiedBy("test"); + collection.setEmail(Collections.singletonList("test@test.com")); + collection.setPhone(Collections.singletonList("1234")); + collection.setNumberSpecimens(12); + collection.setTaxonomicCoverage("taxonomic coverage"); + collection.setGeographicCoverage("geography"); + collection.setNotes("notes for testing"); + collection.setIncorporatedCollections(Arrays.asList("col1", "col2")); + collection.setImportantCollectors(Arrays.asList("collector 1", "collector 2")); + collection.setCollectionSummary(Collections.singletonMap("collectionKey", 0)); + collection.setAlternativeCodes( + Collections.singletonList(new AlternativeCode("CODE2", "another code"))); + collection.setDivision("division"); + collection.setDepartment("department"); + collection.setDisplayOnNHCPortal(true); + collection.setFeaturedImageUrl(URI.create("http://test.com")); + collection.setFeaturedImageLicense(License.CC0_1_0); + collection.setTemporalCoverage("temporal coverage"); + collection.setFeaturedImageAttribution("dummy image attribution"); + + List preservationTypes = new ArrayList<>(); + preservationTypes.add("StorageControlledAtmosphere"); + preservationTypes.add("SampleCryopreserved"); + collection.setPreservationTypes(preservationTypes); + + Address address = new Address(); + address.setAddress("dummy address"); + address.setCountry(Country.SPAIN); + address.setCity("Oviedo"); + addressMapper.create(address); + assertNotNull(address.getKey()); + collection.setAddress(address); + + Address mailingAddress = new Address(); + mailingAddress.setAddress("dummy mailing address"); + mailingAddress.setCountry(Country.SPAIN); + mailingAddress.setCity("Oviedo"); + addressMapper.create(mailingAddress); + assertNotNull(mailingAddress.getKey()); + collection.setMailingAddress(mailingAddress); + + collectionMapper.create(collection); + + DescriptorGroup DescriptorGroup = new DescriptorGroup(); + DescriptorGroup.setCollectionKey(collectionKey); + DescriptorGroup.setTitle("title"); + DescriptorGroup.setCreatedBy("test"); + DescriptorGroup.setModifiedBy("test"); + descriptorsMapper.createDescriptorGroup(DescriptorGroup); + + DescriptorDto descriptorDto = new DescriptorDto(); + descriptorDto.setDescriptorGroupKey(DescriptorGroup.getKey()); + descriptorDto.setUsageName("aves"); + descriptorDto.setCountry(Country.SPAIN); + descriptorsMapper.createDescriptor(descriptorDto); + + List dtos = + collectionsSearchMapper.searchCollections( + DescriptorsParams.builder().query("aves").build()); + assertEquals(1, dtos.size()); + assertEquals(Country.SPAIN, dtos.get(0).getCountry()); + assertEquals(Country.SPAIN, dtos.get(0).getMailingCountry()); + assertEquals("Oviedo", dtos.get(0).getCity()); + assertEquals("Oviedo", dtos.get(0).getMailingCity()); + + dtos = + collectionsSearchMapper.searchCollections( + DescriptorsParams.builder().query("division").build()); + assertEquals(1, dtos.size()); + assertEquals(0, dtos.get(0).getQueryDescriptorRank()); + assertTrue(dtos.get(0).getQueryRank() > 0); + + dtos = + collectionsSearchMapper.searchCollections( + DescriptorsParams.builder().query("aves").build()); + assertEquals(1, dtos.size()); + assertEquals(0, dtos.get(0).getQueryRank()); + assertTrue(dtos.get(0).getQueryDescriptorRank() > 0); + + dtos = + collectionsSearchMapper.searchCollections( + DescriptorsParams.builder().usageName(Collections.singletonList("aves")).build()); + assertEquals(1, dtos.size()); + assertNotNull(dtos.get(0).getDescriptorKey()); + } +} diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/DescriptorsMapperIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/DescriptorsMapperIT.java new file mode 100644 index 0000000000..fc8482468c --- /dev/null +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/DescriptorsMapperIT.java @@ -0,0 +1,176 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gbif.registry.ws.it.persistence.mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import org.gbif.api.model.collections.Collection; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; +import org.gbif.api.v2.RankedName; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Rank; +import org.gbif.api.vocabulary.TypeStatus; +import org.gbif.registry.database.TestCaseDatabaseInitializer; +import org.gbif.registry.persistence.mapper.collections.CollectionMapper; +import org.gbif.registry.persistence.mapper.collections.DescriptorsMapper; +import org.gbif.registry.persistence.mapper.collections.dto.DescriptorDto; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorGroupParams; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorParams; +import org.gbif.registry.search.test.EsManageServer; +import org.gbif.registry.ws.it.BaseItTest; +import org.gbif.ws.client.filter.SimplePrincipalProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; + +public class DescriptorsMapperIT extends BaseItTest { + + @RegisterExtension + protected TestCaseDatabaseInitializer databaseRule = + new TestCaseDatabaseInitializer( + "collection", + "collection_descriptor_group", + "collection_descriptor", + "collection_descriptor_verbatim"); + + private final DescriptorsMapper descriptorsMapper; + private final CollectionMapper collectionMapper; + + @Autowired + public DescriptorsMapperIT( + DescriptorsMapper descriptorsMapper, + CollectionMapper collectionMapper, + SimplePrincipalProvider principalProvider, + EsManageServer esServer) { + super(principalProvider, esServer); + this.descriptorsMapper = descriptorsMapper; + this.collectionMapper = collectionMapper; + } + + @Test + public void crudTest() { + Collection collection = new Collection(); + collection.setCode("code"); + collection.setName("name"); + collection.setKey(UUID.randomUUID()); + collection.setCreatedBy("test"); + collection.setModifiedBy("test"); + collectionMapper.create(collection); + assertNotNull(collection.getKey()); + + DescriptorGroup descriptorGroup = new DescriptorGroup(); + descriptorGroup.setTitle("title"); + descriptorGroup.setDescription("description"); + descriptorGroup.setCreatedBy("user"); + descriptorGroup.setModifiedBy("user"); + descriptorGroup.setCollectionKey(collection.getKey()); + descriptorsMapper.createDescriptorGroup(descriptorGroup); + assertTrue(descriptorGroup.getKey() > 0); + + DescriptorGroup created = descriptorsMapper.getDescriptorGroup(descriptorGroup.getKey()); + assertTrue(descriptorGroup.lenientEquals(created)); + + created.setTitle("title2"); + descriptorsMapper.updateDescriptorGroup(created); + + DescriptorGroup updated = descriptorsMapper.getDescriptorGroup(descriptorGroup.getKey()); + assertTrue(updated.lenientEquals(created)); + + assertEquals( + 1, + descriptorsMapper + .listDescriptorGroups( + DescriptorGroupParams.builder().collectionKey(collection.getKey()).build()) + .size()); + + DescriptorDto descriptorDto = new DescriptorDto(); + descriptorDto.setDescriptorGroupKey(descriptorGroup.getKey()); + descriptorDto.setCountry(Country.DENMARK); + descriptorDto.setDiscipline("discipline"); + descriptorDto.setIssues(Arrays.asList("i1", "i2")); + descriptorDto.setTypeStatus(Collections.singletonList(TypeStatus.ALLOLECTOTYPE.name())); + descriptorDto.setUsageRank(Rank.ABERRATION); + descriptorDto.setUsageName("usage"); + descriptorDto.setUsageKey(5); + + descriptorDto.setTaxonClassification( + Arrays.asList( + new RankedName(1, "Kingdom", Rank.KINGDOM), new RankedName(3, "Phylum", Rank.PHYLUM))); + descriptorsMapper.createDescriptor(descriptorDto); + assertTrue(descriptorDto.getKey() > 0); + + descriptorsMapper.createVerbatim(descriptorDto.getKey(), "f1", "v1"); + descriptorsMapper.createVerbatim(descriptorDto.getKey(), "f2", "v2"); + + DescriptorDto createdDescriptor = descriptorsMapper.getDescriptor(descriptorDto.getKey()); + assertEquals(2, createdDescriptor.getIssues().size()); + assertEquals(1, createdDescriptor.getTypeStatus().size()); + assertEquals(Country.DENMARK, createdDescriptor.getCountry()); + assertEquals(2, createdDescriptor.getVerbatim().size()); + assertEquals(Rank.ABERRATION, createdDescriptor.getUsageRank()); + assertEquals(2, createdDescriptor.getTaxonClassification().size()); + + assertEquals(2, descriptorsMapper.getVerbatimNames(descriptorGroup.getKey()).size()); + + assertEquals( + 1, + descriptorsMapper + .listDescriptors( + DescriptorParams.builder().descriptorGroupKey(descriptorGroup.getKey()).build()) + .size()); + + assertEquals( + 1, + descriptorsMapper + .listDescriptors( + DescriptorParams.builder().usageKey(Collections.singletonList(5)).build()) + .size()); + + assertEquals( + 1, + descriptorsMapper + .listDescriptors( + DescriptorParams.builder() + .usageRank(Collections.singletonList(Rank.ABERRATION)) + .build()) + .size()); + + descriptorsMapper.deleteDescriptorGroup(descriptorGroup.getKey()); + assertTrue( + descriptorsMapper + .listDescriptorGroups( + DescriptorGroupParams.builder().collectionKey(collection.getKey()).build()) + .isEmpty()); + + assertEquals( + 1, + descriptorsMapper + .listDescriptorGroups(DescriptorGroupParams.builder().deleted(true).build()) + .size()); + + descriptorsMapper.deleteDescriptors(descriptorGroup.getKey()); + + assertEquals( + 0, + descriptorsMapper + .listDescriptors( + DescriptorParams.builder().descriptorGroupKey(descriptorGroup.getKey()).build()) + .size()); + } +} diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/InstitutionMapperIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/InstitutionMapperIT.java index 04bfd1d215..f3b7fa759f 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/InstitutionMapperIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/persistence/mapper/InstitutionMapperIT.java @@ -35,7 +35,7 @@ import org.gbif.registry.persistence.mapper.collections.CollectionContactMapper; import org.gbif.registry.persistence.mapper.collections.InstitutionMapper; import org.gbif.registry.persistence.mapper.collections.MasterSourceSyncMetadataMapper; -import org.gbif.registry.persistence.mapper.collections.params.InstitutionSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.InstitutionListParams; import org.gbif.registry.search.test.EsManageServer; import org.gbif.registry.ws.it.BaseItTest; import org.gbif.ws.client.filter.SimplePrincipalProvider; @@ -226,40 +226,40 @@ public void listTest() { Pageable page = PAGE.apply(5, 0L); - assertSearch(InstitutionSearchParams.builder().sourceId("test-123").source(Source.IH_IRN).build(),1,inst4.getKey()); - assertSearch(InstitutionSearchParams.builder().page(page).build(), 4); - assertSearch(InstitutionSearchParams.builder().code("i1").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().code("I1").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().name("n2").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().code("i2").name("n2").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().code("i1").name("n2").page(page).build(), 0); + assertSearch(InstitutionListParams.builder().sourceId("test-123").source(Source.IH_IRN).build(),1,inst4.getKey()); + assertSearch(InstitutionListParams.builder().page(page).build(), 4); + assertSearch(InstitutionListParams.builder().code("i1").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().code("I1").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().name("n2").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().code("i2").name("n2").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().code("i1").name("n2").page(page).build(), 0); assertSearch( - InstitutionSearchParams.builder().fuzzyName("nime of third institution").page(page).build(), + InstitutionListParams.builder().fuzzyName("nime of third institution").page(page).build(), 1); assertSearch( - InstitutionSearchParams.builder().query("nime of third institution").page(page).build(), 0); + InstitutionListParams.builder().query("nime of third institution").page(page).build(), 0); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .countries(Collections.singletonList(Country.DENMARK)) .page(page) .build(), 1); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .countries(Collections.singletonList(Country.SPAIN)) .page(page) .build(), 0); - assertSearch(InstitutionSearchParams.builder().city("Odense").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().city("Odense").page(page).build(), 1); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .city("Copenhagen") .countries(Collections.singletonList(Country.DENMARK)) .page(page) .build(), 1); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .city("CPH") .countries(Collections.singletonList(Country.DENMARK)) .page(page) @@ -268,24 +268,24 @@ public void listTest() { // machine tags assertSearch( - InstitutionSearchParams.builder().machineTagNamespace("dummy").page(page).build(), 0); + InstitutionListParams.builder().machineTagNamespace("dummy").page(page).build(), 0); assertSearch( - InstitutionSearchParams.builder().machineTagName(mt.getName()).page(page).build(), + InstitutionListParams.builder().machineTagName(mt.getName()).page(page).build(), 1, inst1.getKey()); assertSearch( - InstitutionSearchParams.builder().machineTagName(mt.getName()).page(page).build(), + InstitutionListParams.builder().machineTagName(mt.getName()).page(page).build(), 1, inst1.getKey()); assertSearch( - InstitutionSearchParams.builder().machineTagValue(mt.getValue()).page(page).build(), + InstitutionListParams.builder().machineTagValue(mt.getValue()).page(page).build(), 1, inst1.getKey()); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .machineTagName(mt.getName()) .machineTagName(mt.getName()) .machineTagValue(mt.getValue()) @@ -295,9 +295,9 @@ public void listTest() { inst1.getKey()); // identifiers - assertSearch(InstitutionSearchParams.builder().identifier("dummy").page(page).build(), 0); + assertSearch(InstitutionListParams.builder().identifier("dummy").page(page).build(), 0); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .machineTagName(mt.getName()) .machineTagName(mt.getName()) .machineTagValue(mt.getValue()) @@ -307,17 +307,17 @@ public void listTest() { inst1.getKey()); assertSearch( - InstitutionSearchParams.builder().identifierType(identifier.getType()).page(page).build(), + InstitutionListParams.builder().identifierType(identifier.getType()).page(page).build(), 1, inst2.getKey()); assertSearch( - InstitutionSearchParams.builder().identifier(identifier.getIdentifier()).page(page).build(), + InstitutionListParams.builder().identifier(identifier.getIdentifier()).page(page).build(), 1, inst2.getKey()); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .identifierType(identifier.getType()) .identifier(identifier.getIdentifier()) .page(page) @@ -326,12 +326,12 @@ public void listTest() { inst2.getKey()); assertSearch( - InstitutionSearchParams.builder().masterSourceType(MasterSourceType.IH).page(page).build(), + InstitutionListParams.builder().masterSourceType(MasterSourceType.IH).page(page).build(), 1, inst1.getKey()); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .masterSourceType(MasterSourceType.GBIF_REGISTRY) .page(page) .build(), @@ -339,7 +339,7 @@ public void listTest() { inst2.getKey()); assertSearch( - InstitutionSearchParams.builder() + InstitutionListParams.builder() .masterSourceType(MasterSourceType.GRSCICOLL) .page(page) .build(), @@ -373,12 +373,12 @@ public void searchTest() { Pageable page = PAGE.apply(5, 0L); assertSearch( - InstitutionSearchParams.builder().query("i1 n1").page(page).build(), 1, inst1.getKey()); - assertSearch(InstitutionSearchParams.builder().query("i2 i1").page(page).build(), 0); - assertSearch(InstitutionSearchParams.builder().query("i3").page(page).build(), 0); - assertSearch(InstitutionSearchParams.builder().query("n1").page(page).build(), 2); + InstitutionListParams.builder().query("i1 n1").page(page).build(), 1, inst1.getKey()); + assertSearch(InstitutionListParams.builder().query("i2 i1").page(page).build(), 0); + assertSearch(InstitutionListParams.builder().query("i3").page(page).build(), 0); + assertSearch(InstitutionListParams.builder().query("n1").page(page).build(), 2); assertSearch( - InstitutionSearchParams.builder().query("dummy address fo ").page(page).build(), + InstitutionListParams.builder().query("dummy address fo ").page(page).build(), 1, inst1.getKey()); @@ -401,13 +401,13 @@ public void searchTest() { assertNotNull(contact1.getModified()); institutionMapper.addContactPerson(inst1.getKey(), contact1.getKey()); - assertSearch(InstitutionSearchParams.builder().query("Name1").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().query("Name0").page(page).build(), 0); - assertSearch(InstitutionSearchParams.builder().query("Surname1").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().query("aa1@aa.com").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().query("aves").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().query("12345").page(page).build(), 1); - assertSearch(InstitutionSearchParams.builder().query("abcde").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().query("Name1").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().query("Name0").page(page).build(), 0); + assertSearch(InstitutionListParams.builder().query("Surname1").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().query("aa1@aa.com").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().query("aves").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().query("12345").page(page).build(), 1); + assertSearch(InstitutionListParams.builder().query("abcde").page(page).build(), 1); } @Test @@ -432,10 +432,10 @@ public void alternativeCodesTest() { Pageable page = PAGE.apply(1, 0L); assertSearch( - InstitutionSearchParams.builder().query("i1 n1").page(page).build(), 1, inst1.getKey()); + InstitutionListParams.builder().query("i1 n1").page(page).build(), 1, inst1.getKey()); - InstitutionSearchParams params = - InstitutionSearchParams.builder().query("i1").page(page).build(); + InstitutionListParams params = + InstitutionListParams.builder().query("i1").page(page).build(); List institutions = institutionMapper.list(params); long count = institutionMapper.count(params); assertEquals(1, institutions.size()); @@ -444,7 +444,7 @@ public void alternativeCodesTest() { // there are 2 insts with i1 assertEquals(2, count); - params = InstitutionSearchParams.builder().query("i2").page(page).build(); + params = InstitutionListParams.builder().query("i2").page(page).build(); institutions = institutionMapper.list(params); count = institutionMapper.count(params); assertEquals(1, institutions.size()); @@ -454,12 +454,12 @@ public void alternativeCodesTest() { assertEquals(2, count); assertSearch( - InstitutionSearchParams.builder().alternativeCode("i1").page(page).page(page).build(), + InstitutionListParams.builder().alternativeCode("i1").page(page).page(page).build(), 1, inst2.getKey()); } - private List assertSearch(InstitutionSearchParams params, int expected) { + private List assertSearch(InstitutionListParams params, int expected) { List res = institutionMapper.list(params); long count = institutionMapper.count(params); assertEquals(expected, count); @@ -467,7 +467,7 @@ private List assertSearch(InstitutionSearchParams params, int expec return res; } - private void assertSearch(InstitutionSearchParams params, int expected, UUID expectedKey) { + private void assertSearch(InstitutionListParams params, int expected, UUID expectedKey) { List res = assertSearch(params, expected); assertEquals(expectedKey, res.get(0).getKey()); } diff --git a/registry-integration-tests/src/test/resources/collections/descriptors.csv b/registry-integration-tests/src/test/resources/collections/descriptors.csv new file mode 100644 index 0000000000..b4ad9e8512 --- /dev/null +++ b/registry-integration-tests/src/test/resources/collections/descriptors.csv @@ -0,0 +1,6 @@ +dwc:scientificName Num. of Specimens Num. Databased Num. Imaged +Algae 244262 244000 45660 +Bryophytes 5476 8821 2085 +Fungi/Lichens 117358 106427 16269 +Pteridophytes 269359 280000 266443 +Seed Plants 2202411 2242328 1407799 \ No newline at end of file diff --git a/registry-integration-tests/src/test/resources/collections/descriptors2.csv b/registry-integration-tests/src/test/resources/collections/descriptors2.csv new file mode 100644 index 0000000000..4d37a7f405 --- /dev/null +++ b/registry-integration-tests/src/test/resources/collections/descriptors2.csv @@ -0,0 +1,5 @@ +dwc:scientificName Num. of Specimens Num. Databased dwc:country +Aves 244262 244000 DK +Bryophytes 5476 8821 +Fungi/Lichens 117358 106427 +Aves 269359 280000 diff --git a/registry-integration-tests/src/test/resources/collections/descriptors3.csv b/registry-integration-tests/src/test/resources/collections/descriptors3.csv new file mode 100644 index 0000000000..5b51293e19 --- /dev/null +++ b/registry-integration-tests/src/test/resources/collections/descriptors3.csv @@ -0,0 +1,5 @@ +dwc:country dwc:individualCount dwc:recordedBy dwc:typeStatus +DK 124 Tim|Marcos COTYPE +DK 15 Tim|Marcos COTYPE|EPITYPE +PT 56 Tim|Marcos|Marie +US 34 Joe ISOTYPE diff --git a/registry-integration-tests/src/test/resources/logback-test.xml b/registry-integration-tests/src/test/resources/logback-test.xml index 050495d804..bffcae77ec 100644 --- a/registry-integration-tests/src/test/resources/logback-test.xml +++ b/registry-integration-tests/src/test/resources/logback-test.xml @@ -7,7 +7,7 @@ - + diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java index b4236d3ae3..d9482c8463 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/config/MyBatisConfiguration.java @@ -13,9 +13,13 @@ */ package org.gbif.registry.persistence.config; +import java.net.URI; +import java.util.UUID; import org.gbif.api.model.collections.Address; import org.gbif.api.model.collections.Collection; import org.gbif.api.model.collections.Institution; +import org.gbif.api.model.collections.descriptors.Descriptor; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; import org.gbif.api.model.common.DOI; import org.gbif.api.model.common.DoiData; import org.gbif.api.model.common.paging.Pageable; @@ -30,20 +34,7 @@ import org.gbif.api.model.pipelines.PipelineProcess; import org.gbif.api.model.pipelines.PipelineStep; import org.gbif.api.model.predicate.Predicate; -import org.gbif.api.model.registry.Citation; -import org.gbif.api.model.registry.Comment; -import org.gbif.api.model.registry.Contact; -import org.gbif.api.model.registry.Dataset; -import org.gbif.api.model.registry.DatasetOccurrenceDownloadUsage; -import org.gbif.api.model.registry.Endpoint; -import org.gbif.api.model.registry.Identifier; -import org.gbif.api.model.registry.Installation; -import org.gbif.api.model.registry.MachineTag; -import org.gbif.api.model.registry.Metadata; -import org.gbif.api.model.registry.Network; -import org.gbif.api.model.registry.Node; -import org.gbif.api.model.registry.Organization; -import org.gbif.api.model.registry.Tag; +import org.gbif.api.model.registry.*; import org.gbif.api.model.registry.metasync.MetasyncHistory; import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.Language; @@ -58,13 +49,29 @@ import org.gbif.registry.persistence.mapper.collections.external.IdentifierDto; import org.gbif.registry.persistence.mapper.collections.external.MachineTagDto; import org.gbif.registry.persistence.mapper.handler.*; -import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.gbif.registry.persistence.mapper.dto.OrganizationGeoJsonDto; +import org.gbif.registry.persistence.mapper.handler.*; +import org.gbif.registry.persistence.mapper.handler.CollectionContentTypeArrayTypeHandler; +import org.gbif.registry.persistence.mapper.handler.CountryNotNullTypeHandler; +import org.gbif.registry.persistence.mapper.handler.DOITypeHandler; +import org.gbif.registry.persistence.mapper.handler.DisciplineArrayTypeHandler; +import org.gbif.registry.persistence.mapper.handler.ExtensionArrayTypeHandler; +import org.gbif.registry.persistence.mapper.handler.InstitutionGovernanceArrayTypeHandler; +import org.gbif.registry.persistence.mapper.handler.LocaleTypeHandler; +import org.gbif.registry.persistence.mapper.handler.MetricInfoTypeHandler; +import org.gbif.registry.persistence.mapper.handler.OccurrenceDownloadStatusTypeHandler; +import org.gbif.registry.persistence.mapper.handler.PredicateTypeHandler; +import org.gbif.registry.persistence.mapper.handler.StepTypeArrayTypeHandler; +import org.gbif.registry.persistence.mapper.handler.SuggestedChangesTypeHandler; +import org.gbif.registry.persistence.mapper.handler.UserIdsTypeHandler; import java.net.URI; import java.util.UUID; +import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + @Configuration public class MyBatisConfiguration { @@ -128,10 +135,20 @@ ConfigurationCustomizer mybatisConfigCustomizer() { configuration.getTypeAliasRegistry().registerAlias("Address", Address.class); configuration.getTypeAliasRegistry().registerAlias("CollectionDto", CollectionDto.class); configuration.getTypeAliasRegistry().registerAlias("DuplicateDto", DuplicateDto.class); + configuration.getTypeAliasRegistry().registerAlias("DescriptorGroup", DescriptorGroup.class); + configuration.getTypeAliasRegistry().registerAlias("Descriptor", Descriptor.class); + configuration.getTypeAliasRegistry().registerAlias("DescriptorDto", DescriptorDto.class); + configuration.getTypeAliasRegistry().registerAlias("VerbatimDto", VerbatimDto.class); configuration .getTypeAliasRegistry() .registerAlias("DuplicateMetadataDto", DuplicateMetadataDto.class); configuration.getTypeAliasRegistry().registerAlias("SearchDto", SearchDto.class); + configuration + .getTypeAliasRegistry() + .registerAlias("InstitutionSearchDto", InstitutionSearchDto.class); + configuration + .getTypeAliasRegistry() + .registerAlias("CollectionSearchDto", CollectionSearchDto.class); configuration .getTypeAliasRegistry() .registerAlias("InstitutionMatchedDto", InstitutionMatchedDto.class); @@ -193,8 +210,15 @@ ConfigurationCustomizer mybatisConfigCustomizer() { .getTypeAliasRegistry() .registerAlias("ExtensionArrayTypeHandler", ExtensionArrayTypeHandler.class); configuration - .getTypeAliasRegistry() - .registerAlias("InstitutionGovernanceArrayTypeHandler", InstitutionGovernanceArrayTypeHandler.class); + .getTypeAliasRegistry() + .registerAlias( + "InstitutionGovernanceArrayTypeHandler", InstitutionGovernanceArrayTypeHandler.class); + configuration + .getTypeAliasRegistry() + .registerAlias("RankedNameListTypeHandler", RankedNameListTypeHandler.class); + configuration + .getTypeAliasRegistry() + .registerAlias("IntegerArrayTypeHandler", IntegerArrayTypeHandler.class); configuration.getTypeAliasRegistry().registerAlias("PipelineProcess", PipelineProcess.class); configuration.getTypeAliasRegistry().registerAlias("Step", PipelineStep.class); @@ -216,6 +240,9 @@ ConfigurationCustomizer mybatisConfigCustomizer() { configuration .getTypeAliasRegistry() .registerAlias("UserIdsTypeHandler", UserIdsTypeHandler.class); + configuration + .getTypeAliasRegistry() + .registerAlias("OrganizationGeoJsonDto", OrganizationGeoJsonDto.class); // external iDigBio configuration.getTypeAliasRegistry().registerAlias("MachineTagDto", MachineTagDto.class); diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/OrganizationMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/OrganizationMapper.java index 17ec8188c8..340ab28228 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/OrganizationMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/OrganizationMapper.java @@ -20,6 +20,7 @@ import org.gbif.api.vocabulary.InstallationType; import org.gbif.registry.domain.ws.LegacyOrganizationBriefResponse; import org.gbif.registry.persistence.ChallengeCodeSupportMapper; +import org.gbif.registry.persistence.mapper.dto.OrganizationGeoJsonDto; import org.gbif.registry.persistence.mapper.params.OrganizationListParams; import java.util.List; @@ -83,4 +84,10 @@ List hostingInstallationsOf( List suggest(@Nullable @Param("q") String q); Organization getLightweight(@Param("key") UUID key); + + /** + * @return The list of organizations as geojson + */ + List listGeoJson(@Param("params") OrganizationListParams params); + } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.java index efae128a5a..fbfa0b7bb6 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.java @@ -43,6 +43,7 @@ List list( @Param("entityType") CollectionEntityType entityType, @Param("proposerEmail") String proposerEmail, @Param("entityKey") UUID entityKey, + @Param("ihIdentifier") String ihIdentifier, @Nullable @Param("page") Pageable page); long count( diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionMapper.java index a2ce5f2e58..bcc28c9844 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionMapper.java @@ -18,7 +18,7 @@ import org.gbif.registry.persistence.mapper.collections.dto.CollectionDto; import org.gbif.registry.persistence.mapper.collections.dto.CollectionMatchedDto; import org.gbif.registry.persistence.mapper.collections.dto.MasterSourceOrganizationDto; -import org.gbif.registry.persistence.mapper.collections.params.CollectionSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.CollectionListParams; import java.util.List; import java.util.UUID; @@ -33,9 +33,9 @@ public interface CollectionMapper extends BaseMapper, LookupMapper { - List list(@Param("params") CollectionSearchParams searchParams); + List list(@Param("params") CollectionListParams searchParams); - long count(@Param("params") CollectionSearchParams searchParams); + long count(@Param("params") CollectionListParams searchParams); /** A simple suggest by title service. */ List suggest(@Nullable @Param("q") String q); diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.java index 39f8e12952..34b11a0f03 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.java @@ -13,22 +13,26 @@ */ package org.gbif.registry.persistence.mapper.collections; -import org.gbif.api.vocabulary.Country; -import org.gbif.registry.persistence.mapper.collections.dto.SearchDto; - import java.util.List; - import javax.annotation.Nullable; - import org.apache.ibatis.annotations.Param; +import org.gbif.registry.persistence.mapper.collections.dto.CollectionSearchDto; +import org.gbif.registry.persistence.mapper.collections.dto.InstitutionSearchDto; +import org.gbif.registry.persistence.mapper.collections.dto.SearchDto; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorsParams; +import org.gbif.registry.persistence.mapper.collections.params.FullTextSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.InstitutionListParams; public interface CollectionsSearchMapper { - List search( - @Nullable @Param("q") String query, - @Param("highlight") boolean highlight, - @Nullable @Param("type") String type, - @Nullable @Param("displayOnNHCPortal") Boolean displayOnNHCPortal, - @Nullable @Param("country") Country country, - @Param("limit") int limit); + List search(@Nullable @Param("params") FullTextSearchParams params); + + List searchInstitutions( + @Nullable @Param("params") InstitutionListParams listParams); + + long countInstitutions(@Nullable @Param("params") InstitutionListParams listParams); + + List searchCollections(@Nullable @Param("params") DescriptorsParams params); + + long countCollections(@Nullable @Param("params") DescriptorsParams listParams); } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/Common.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/Common.java new file mode 100644 index 0000000000..e0d814adf0 --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/Common.java @@ -0,0 +1,3 @@ +package org.gbif.registry.persistence.mapper.collections; + +public interface Common {} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/DescriptorsMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/DescriptorsMapper.java new file mode 100644 index 0000000000..69c1b63c3d --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/DescriptorsMapper.java @@ -0,0 +1,47 @@ +package org.gbif.registry.persistence.mapper.collections; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; +import org.gbif.registry.persistence.mapper.collections.dto.DescriptorDto; +import org.gbif.registry.persistence.mapper.collections.dto.VerbatimDto; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorGroupParams; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorParams; +import org.springframework.stereotype.Repository; + +@Repository +public interface DescriptorsMapper { + + DescriptorGroup getDescriptorGroup(@Param("key") long key); + + void createDescriptorGroup(DescriptorGroup entity); + + void deleteDescriptorGroup(@Param("key") long key); + + void updateDescriptorGroup(DescriptorGroup entity); + + List listDescriptorGroups(@Param("params") DescriptorGroupParams searchParams); + + long countDescriptorGroups(@Param("params") DescriptorGroupParams searchParams); + + DescriptorDto getDescriptor(@Param("key") long key); + + void createDescriptor(DescriptorDto entity); + + void deleteDescriptor(@Param("key") long key); + + List listDescriptors(@Param("params") DescriptorParams searchParams); + + long countDescriptors(@Param("params") DescriptorParams searchParams); + + void deleteDescriptors(@Param("descriptorGroupKey") long descriptorGroupKey); + + void createVerbatim( + @Param("descriptorKey") long descriptorKey, + @Param("fieldName") String fieldName, + @Param("fieldValue") String fieldValue); + + // TODO: list deleted + + List getVerbatimNames(long descriptorGroupKey); +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java index 82360c45d5..4baf47ddea 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/InstitutionMapper.java @@ -17,7 +17,7 @@ import org.gbif.api.model.registry.search.collections.KeyCodeNameResult; import org.gbif.registry.persistence.mapper.collections.dto.InstitutionGeoJsonDto; import org.gbif.registry.persistence.mapper.collections.dto.InstitutionMatchedDto; -import org.gbif.registry.persistence.mapper.collections.params.InstitutionSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.InstitutionListParams; import java.util.List; import java.util.UUID; @@ -32,9 +32,9 @@ public interface InstitutionMapper extends BaseMapper, LookupMapper { - List list(@Param("params") InstitutionSearchParams searchParams); + List list(@Param("params") InstitutionListParams searchParams); - long count(@Param("params") InstitutionSearchParams searchParams); + long count(@Param("params") InstitutionListParams searchParams); /** A simple suggest by title service. */ List suggest(@Nullable @Param("q") String q); @@ -49,7 +49,7 @@ public interface InstitutionMapper void convertToCollection( @Param("institutionKey") UUID institutionKey, @Param("collectionKey") UUID collectionKey); - List listGeoJson(@Param("params") InstitutionSearchParams searchParams); + List listGeoJson(@Param("params") InstitutionListParams searchParams); List getAllKeys(); } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/BaseSearchDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/BaseSearchDto.java new file mode 100644 index 0000000000..aa8664e8fb --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/BaseSearchDto.java @@ -0,0 +1,55 @@ +package org.gbif.registry.persistence.mapper.collections.dto; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.Data; + +import org.gbif.api.model.collections.AlternativeCode; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.License; + +@Data +public abstract class BaseSearchDto { + + private UUID key; + private String code; + private String name; + private String description; + private boolean active; + private Country country; + private Country mailingCountry; + private String city; + private String mailingCity; + private boolean displayOnNHCPortal; + private List alternativeCodes = new ArrayList<>(); + private URI featuredImageUrl; + private License featuredImageLicense; + private String featuredImageAttribution; + + // highlights + private String codeHighlight; + private String nameHighlight; + private String descriptionHighlight; + private String alternativeCodesHighlight; + private String addressHighlight; + private String cityHighlight; + private String provinceHighlight; + private String countryHighlight; + private String mailAddressHighlight; + private String mailCityHighlight; + private String mailProvinceHighlight; + private String mailCountryHighlight; + private String descriptorUsageNameHighlight; + private String descriptorCountryHighlight; + private String descriptorIdentifiedByHighlight; + private String descriptorTypeStatusHighlight; + private String descriptorRecordedByHighlight; + private String descriptorDisciplineHighlight; + private String descriptorObjectClassificationHighlight; + private String descriptorIssuesHighlight; + private String descriptorGroupTitleHighlight; + private String descriptorGroupDescriptionHighlight; + private boolean similarityMatch; +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/ChangeSuggestionDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/ChangeSuggestionDto.java index 47ec7182b2..b198778899 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/ChangeSuggestionDto.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/ChangeSuggestionDto.java @@ -50,6 +50,8 @@ public class ChangeSuggestionDto { private String nameNewInstitutionConvertedCollection; private Date modified; private String modifiedBy; + private String ihIdentifier; + private Boolean createInstitution; // this field is not persisted in the DB. It stores the country that has to be taken into account // to check the user permissions diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/CollectionSearchDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/CollectionSearchDto.java new file mode 100644 index 0000000000..285242b4ba --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/CollectionSearchDto.java @@ -0,0 +1,29 @@ +package org.gbif.registry.persistence.mapper.collections.dto; + +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.gbif.api.vocabulary.collections.MasterSourceType; + +@SuppressWarnings("MissingOverride") +@EqualsAndHashCode(callSuper = true) +@Data +public class CollectionSearchDto extends SearchDto { + + private List contentTypes = new ArrayList<>(); + private boolean personalCollection; + private List preservationTypes = new ArrayList<>(); + private String accessionStatus; + private Integer numberSpecimens; + private String taxonomicCoverage; + private String geographicCoverage; + private MasterSourceType masterSource; + private String department; + private String division; + private Integer occurrenceCount; + private Integer typeSpecimenCount; + private String temporalCoverage; + private Float queryRank; + private Float queryDescriptorRank; +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/DescriptorDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/DescriptorDto.java new file mode 100644 index 0000000000..009a11552e --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/DescriptorDto.java @@ -0,0 +1,31 @@ +package org.gbif.registry.persistence.mapper.collections.dto; + +import java.util.Date; +import java.util.List; +import java.util.Set; +import lombok.Data; +import org.gbif.api.v2.RankedName; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Rank; + +@Data +public class DescriptorDto { + + private long key; + private Long descriptorGroupKey; + private Country country; + private Integer individualCount; + private List identifiedBy; + private Date dateIdentified; + private List typeStatus; + private List recordedBy; + private String discipline; + private String objectClassificationName; + private List issues; + private List verbatim; + private Integer usageKey; + private String usageName; + private Rank usageRank; + private List taxonClassification; + private Set taxonKeys; +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/InstitutionSearchDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/InstitutionSearchDto.java new file mode 100644 index 0000000000..32f7cf2c3f --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/InstitutionSearchDto.java @@ -0,0 +1,31 @@ +package org.gbif.registry.persistence.mapper.collections.dto; + +import java.math.BigDecimal; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.gbif.api.model.collections.AlternativeCode; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.License; +import org.gbif.api.vocabulary.collections.MasterSourceType; + +@SuppressWarnings("MissingOverride") +@EqualsAndHashCode(callSuper = true) +@Data +public class InstitutionSearchDto extends BaseSearchDto { + + private List types = new ArrayList<>(); + private List institutionalGovernances = new ArrayList<>(); + private List disciplines = new ArrayList<>(); + private BigDecimal latitude; + private BigDecimal longitude; + private Integer foundingDate; + private Integer numberSpecimens; + private MasterSourceType masterSource; + private Integer occurrenceCount; + private Integer typeSpecimenCount; + +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/SearchDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/SearchDto.java index f680fd444f..4827400df0 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/SearchDto.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/SearchDto.java @@ -13,41 +13,38 @@ */ package org.gbif.registry.persistence.mapper.collections.dto; -import org.gbif.api.vocabulary.Country; - +import java.util.Date; +import java.util.List; import java.util.UUID; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Rank; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class SearchDto { +@SuppressWarnings("MissingOverride") +@EqualsAndHashCode(callSuper = true) +@Data +public class SearchDto extends BaseSearchDto { private float score; private String type; - private UUID key; - private String code; - private String name; private UUID institutionKey; private String institutionCode; private String institutionName; - private boolean displayOnNHCPortal; - private Country country; - private Country mailCountry; - - private String codeHighlight; - private String nameHighlight; - private String descriptionHighlight; - private String alternativeCodesHighlight; - private String addressHighlight; - private String cityHighlight; - private String provinceHighlight; - private String countryHighlight; - private String mailAddressHighlight; - private String mailCityHighlight; - private String mailProvinceHighlight; - private String mailCountryHighlight; - private boolean similarityMatch; + // descriptors fields + private Long descriptorKey; + private Long descriptorGroupKey; + private Long descriptorUsageKey; + private String descriptorUsageName; + private Rank descriptorUsageRank; + private Country descriptorCountry; + private Integer descriptorIndividualCount; + private List descriptorIdentifiedBy; + private Date descriptorDateIdentified; + private List descriptorTypeStatus; + private List descriptorRecordedBy; + private String descriptorDiscipline; + private String descriptorObjectClassification; + private List descriptorIssues; } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/VerbatimDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/VerbatimDto.java new file mode 100644 index 0000000000..96a8a8a37c --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/dto/VerbatimDto.java @@ -0,0 +1,11 @@ +package org.gbif.registry.persistence.mapper.collections.dto; + +import lombok.Data; + +@Data +public class VerbatimDto { + + private long key; + private String fieldName; + private String fieldValue; +} 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/CollectionListParams.java similarity index 94% rename from registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/CollectionSearchParams.java rename to registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/CollectionListParams.java index bd8b403747..8c6e10b5ee 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/CollectionListParams.java @@ -24,7 +24,7 @@ @Getter @SuperBuilder -public class CollectionSearchParams extends SearchParams { +public class CollectionListParams extends ListParams { @Nullable List contentTypes; @Nullable List preservationTypes; diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorGroupParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorGroupParams.java new file mode 100644 index 0000000000..1b0065b27c --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorGroupParams.java @@ -0,0 +1,19 @@ +package org.gbif.registry.persistence.mapper.collections.params; + +import java.util.UUID; +import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Getter; +import org.gbif.api.model.common.paging.Pageable; + +@Getter +@Builder +public class DescriptorGroupParams { + + UUID collectionKey; + @Nullable String query; + @Nullable String title; + @Nullable String description; + @Nullable Boolean deleted; + @Nullable Pageable page; +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorParams.java new file mode 100644 index 0000000000..8aa433450b --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorParams.java @@ -0,0 +1,33 @@ +package org.gbif.registry.persistence.mapper.collections.params; + +import java.util.Date; +import java.util.List; +import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Getter; +import org.gbif.api.model.common.paging.Pageable; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Rank; + +@Getter +@Builder +public class DescriptorParams { + + @Nullable String query; + @Nullable Long descriptorGroupKey; + @Nullable List usageKey; + @Nullable List usageName; + @Nullable List usageRank; + @Nullable List taxonKey; + @Nullable List country; + @Nullable RangeParam individualCount; + @Nullable List identifiedBy; + @Nullable Date dateIdentifiedFrom; + @Nullable Date dateIdentifiedBefore; + @Nullable List typeStatus; + @Nullable List recordedBy; + @Nullable List discipline; + @Nullable List objectClassification; + @Nullable List issues; + @Nullable Pageable page; +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorVerbatimParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorVerbatimParams.java new file mode 100644 index 0000000000..ba40d0c8f6 --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorVerbatimParams.java @@ -0,0 +1,17 @@ +package org.gbif.registry.persistence.mapper.collections.params; + +import org.gbif.api.model.common.paging.Pageable; + +import javax.annotation.Nullable; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class DescriptorVerbatimParams { + + @Nullable String query; + @Nullable Long recordKey; + @Nullable Pageable page; +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorsParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorsParams.java new file mode 100644 index 0000000000..3468351ece --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/DescriptorsParams.java @@ -0,0 +1,65 @@ +package org.gbif.registry.persistence.mapper.collections.params; + +import java.time.LocalDate; +import java.util.List; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Rank; + +@Getter +@SuperBuilder +public class DescriptorsParams extends CollectionListParams { + + // descriptors fields + List usageName; + List usageKey; + List usageRank; + List taxonKey; + @Nullable List descriptorCountry; + @Nullable RangeParam individualCount; + @Nullable List identifiedBy; + @Nullable LocalDate dateIdentifiedFrom; + @Nullable LocalDate dateIdentifiedBefore; + @Nullable List typeStatus; + @Nullable List recordedBy; + @Nullable List discipline; + @Nullable List objectClassification; + @Nullable List issues; + + public boolean descriptorSearch() { + return query != null + || usageName != null + || usageKey != null + || usageRank != null + || taxonKey != null + || descriptorCountry != null + || individualCount != null + || identifiedBy != null + || dateIdentifiedFrom != null + || dateIdentifiedBefore != null + || typeStatus != null + || recordedBy != null + || discipline != null + || objectClassification != null + || issues != null; + } + + public boolean descriptorSearchWithoutQuery() { + return usageName != null + || usageKey != null + || usageRank != null + || taxonKey != null + || descriptorCountry != null + || individualCount != null + || identifiedBy != null + || dateIdentifiedFrom != null + || dateIdentifiedBefore != null + || typeStatus != null + || recordedBy != null + || discipline != null + || objectClassification != null + || issues != null; + } +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/FullTextSearchParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/FullTextSearchParams.java new file mode 100644 index 0000000000..2a363d7a7c --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/FullTextSearchParams.java @@ -0,0 +1,18 @@ +package org.gbif.registry.persistence.mapper.collections.params; + +import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Data; +import org.gbif.api.vocabulary.Country; + +@Data +@Builder +public class FullTextSearchParams { + + @Nullable String query; + boolean highlight; + @Nullable String type; + @Nullable Boolean displayOnNHCPortal; + @Nullable Country country; + int limit; +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/InstitutionSearchParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/InstitutionListParams.java similarity index 94% rename from registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/InstitutionSearchParams.java rename to registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/InstitutionListParams.java index 6a9348eb9a..efac8f9c1e 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/InstitutionSearchParams.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/InstitutionListParams.java @@ -24,7 +24,7 @@ @Getter @SuperBuilder -public class InstitutionSearchParams extends SearchParams { +public class InstitutionListParams extends ListParams { @Nullable List types; @Nullable List institutionalGovernances; diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/ListParams.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/ListParams.java new file mode 100644 index 0000000000..e6f7317ba0 --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/collections/params/ListParams.java @@ -0,0 +1,58 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gbif.registry.persistence.mapper.collections.params; + +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import org.gbif.api.model.common.paging.Pageable; +import org.gbif.api.vocabulary.CollectionsSortField; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.IdentifierType; +import org.gbif.api.vocabulary.SortOrder; +import org.gbif.api.vocabulary.collections.MasterSourceType; + +@Getter +@SuperBuilder +public abstract class ListParams { + + @Nullable Boolean highlight; + @Nullable String query; + @Nullable String code; + @Nullable String name; + @Nullable String alternativeCode; + @Nullable String machineTagNamespace; + @Nullable String machineTagName; + @Nullable String machineTagValue; + @Nullable IdentifierType identifierType; + @Nullable String identifier; + @Nullable List countries; + @Nullable List regionCountries; + @Nullable String city; + @Nullable String fuzzyName; + @Nullable Boolean active; + @Nullable MasterSourceType masterSourceType; + @Nullable RangeParam numberSpecimens; + @Nullable Boolean displayOnNHCPortal; + @Nullable RangeParam occurrenceCount; + @Nullable RangeParam typeSpecimenCount; + @Nullable List institutionKeys; + @Nullable CollectionsSortField sortBy; + @Nullable SortOrder sortOrder; + @Nullable private Boolean deleted; + @Nullable private UUID replacedBy; + @Nullable private Pageable page; +} 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 58eb1986c9..28cad8be2c 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 @@ -1,60 +1,46 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.gbif.registry.persistence.mapper.collections.params; -import org.gbif.api.model.common.paging.Pageable; -import org.gbif.api.vocabulary.CollectionsSortField; -import org.gbif.api.vocabulary.Country; -import org.gbif.api.vocabulary.IdentifierType; -import org.gbif.api.vocabulary.SortOrder; -import org.gbif.api.vocabulary.collections.MasterSourceType; - +import java.util.Date; import java.util.List; -import java.util.UUID; - import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Data; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Rank; +import org.gbif.api.vocabulary.TypeStatus; -import lombok.Getter; -import lombok.experimental.SuperBuilder; - -@Getter -@SuperBuilder -public abstract class SearchParams { +@Data +@Builder +public class SearchParams { - @Nullable String query; - @Nullable String code; - @Nullable String name; - @Nullable String alternativeCode; - @Nullable String machineTagNamespace; - @Nullable String machineTagName; - @Nullable String machineTagValue; - @Nullable IdentifierType identifierType; - @Nullable String identifier; + @Nullable String q; + Boolean highlight; + String type; + Boolean displayOnNHCPortal; @Nullable List countries; @Nullable List regionCountries; @Nullable String city; - @Nullable String fuzzyName; - @Nullable Boolean active; - @Nullable MasterSourceType masterSourceType; - @Nullable RangeParam numberSpecimens; - @Nullable Boolean displayOnNHCPortal; - @Nullable RangeParam occurrenceCount; - @Nullable RangeParam typeSpecimenCount; - @Nullable List institutionKeys; - @Nullable CollectionsSortField sortBy; - @Nullable SortOrder sortOrder; - @Nullable private Boolean deleted; - @Nullable private UUID replacedBy; - @Nullable private Pageable page; + + Integer limit; + Integer offset; + + // collection fields + + // descriptors fields + List usageName; + List usageKey; + List usageRank; + List taxonKey; + @Nullable List descriptorCountry; + @Nullable RangeParam individualCount; + @Nullable List identifiedBy; + @Nullable + Date dateIdentified; + @Nullable Date dateIdentifiedFrom; + @Nullable Date dateIdentifiedBefore; + @Nullable List typeStatus; + @Nullable List recordedBy; + @Nullable List discipline; + @Nullable List objectClassification; + @Nullable List issues; } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/dto/OrganizationGeoJsonDto.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/dto/OrganizationGeoJsonDto.java new file mode 100644 index 0000000000..5d8fe5f8c8 --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/dto/OrganizationGeoJsonDto.java @@ -0,0 +1,17 @@ +package org.gbif.registry.persistence.mapper.dto; + +import java.math.BigDecimal; +import java.util.UUID; + +import lombok.Data; + +@Data +public class OrganizationGeoJsonDto { + + private UUID key; + private String title; + private Integer numPublishedDatasets; + private BigDecimal latitude; + private BigDecimal longitude; + +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/handler/IntegerArrayTypeHandler.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/handler/IntegerArrayTypeHandler.java new file mode 100644 index 0000000000..1f00ebaa26 --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/handler/IntegerArrayTypeHandler.java @@ -0,0 +1,69 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gbif.registry.persistence.mapper.handler; + +import com.google.common.base.Strings; +import java.sql.Array; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.gbif.api.vocabulary.collections.PreservationType; + +/** {@link org.apache.ibatis.type.TypeHandler} for arrays of {@link PreservationType}. */ +public class IntegerArrayTypeHandler extends BaseTypeHandler> { + + @Override + public void setNonNullParameter( + PreparedStatement ps, int i, Set parameter, JdbcType jdbcType) throws SQLException { + Array array = ps.getConnection().createArrayOf("integer", parameter.toArray()); + ps.setArray(i, array); + } + + @Override + public Set getNullableResult(ResultSet rs, String columnName) throws SQLException { + return toSet(rs.getArray(columnName)); + } + + @Override + public Set getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return toSet(rs.getArray(columnIndex)); + } + + @Override + public Set getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return toSet(cs.getArray(columnIndex)); + } + + private Set toSet(Array pgArray) throws SQLException { + if (pgArray == null) { + return new HashSet<>(); + } + + String[] strings = (String[]) pgArray.getArray(); + if (strings != null && strings.length > 0) { + return Arrays.stream(strings) + .filter(v -> !Strings.isNullOrEmpty(v)) + .map(Integer::valueOf) + .collect(Collectors.toSet()); + } + return new HashSet<>(); + } +} diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/handler/RankedNameListTypeHandler.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/handler/RankedNameListTypeHandler.java new file mode 100644 index 0000000000..27b35e1a94 --- /dev/null +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/handler/RankedNameListTypeHandler.java @@ -0,0 +1,74 @@ +package org.gbif.registry.persistence.mapper.handler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.google.common.base.Strings; +import java.io.IOException; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.gbif.api.v2.RankedName; + +public class RankedNameListTypeHandler extends BaseTypeHandler> { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectReader OBJECT_READER = + OBJECT_MAPPER.readerFor(new TypeReference>() {}); + + @Override + public void setNonNullParameter( + PreparedStatement preparedStatement, + int i, + List rankedNamesList, + JdbcType jdbcType) + throws SQLException { + preparedStatement.setString(i, toString(rankedNamesList)); + } + + @Override + public List getNullableResult(ResultSet resultSet, String columnName) + throws SQLException { + return fromString(resultSet.getString(columnName)); + } + + @Override + public List getNullableResult(ResultSet resultSet, int columnIndex) + throws SQLException { + return fromString(resultSet.getString(columnIndex)); + } + + @Override + public List getNullableResult(CallableStatement callableStatement, int columnIndex) + throws SQLException { + return fromString(callableStatement.getString(columnIndex)); + } + + private String toString(List rankedNamesList) { + try { + return OBJECT_MAPPER.writeValueAsString(rankedNamesList); + } catch (JsonProcessingException e) { + throw new IllegalStateException( + "Couldn't convert language map to JSON: " + rankedNamesList.toString(), e); + } + } + + private List fromString(String json) { + if (Strings.isNullOrEmpty(json)) { + return Collections.emptyList(); + } + + try { + return OBJECT_READER.readValue(json); + } catch (IOException e) { + throw new IllegalStateException( + "Couldn't deserialize taxon classification JSON from DB: " + json, e); + } + } +} diff --git a/registry-persistence/src/main/resources/liquibase/138-dataset-occurrence-download-created-index.xml b/registry-persistence/src/main/resources/liquibase/138-dataset-occurrence-download-created-index.xml index d566fcb896..e87bf7c273 100644 --- a/registry-persistence/src/main/resources/liquibase/138-dataset-occurrence-download-created-index.xml +++ b/registry-persistence/src/main/resources/liquibase/138-dataset-occurrence-download-created-index.xml @@ -1,7 +1,7 @@ + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"> diff --git a/registry-persistence/src/main/resources/liquibase/139-dataset-occurrence-download-created-index.xml b/registry-persistence/src/main/resources/liquibase/139-dataset-occurrence-download-created-index.xml index 4f774caafa..1f8df66a60 100644 --- a/registry-persistence/src/main/resources/liquibase/139-dataset-occurrence-download-created-index.xml +++ b/registry-persistence/src/main/resources/liquibase/139-dataset-occurrence-download-created-index.xml @@ -1,7 +1,7 @@ + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"> diff --git a/registry-persistence/src/main/resources/liquibase/140-ih-identifier-change-sugestion.xml b/registry-persistence/src/main/resources/liquibase/140-ih-identifier-change-sugestion.xml new file mode 100644 index 0000000000..9247a8b269 --- /dev/null +++ b/registry-persistence/src/main/resources/liquibase/140-ih-identifier-change-sugestion.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/registry-persistence/src/main/resources/liquibase/141-collection-descriptors-grscicoll.xml b/registry-persistence/src/main/resources/liquibase/141-collection-descriptors-grscicoll.xml new file mode 100644 index 0000000000..9ea354fa83 --- /dev/null +++ b/registry-persistence/src/main/resources/liquibase/141-collection-descriptors-grscicoll.xml @@ -0,0 +1,115 @@ + + + + + + + + diff --git a/registry-persistence/src/main/resources/liquibase/master.xml b/registry-persistence/src/main/resources/liquibase/master.xml index 3b9c9e49bc..c76008c0c0 100644 --- a/registry-persistence/src/main/resources/liquibase/master.xml +++ b/registry-persistence/src/main/resources/liquibase/master.xml @@ -145,4 +145,6 @@ + + diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/OrganizationMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/OrganizationMapper.xml index adcd758856..380f2a9a22 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/OrganizationMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/OrganizationMapper.xml @@ -29,6 +29,12 @@ + + + + + + key,endorsing_node_key,password,title,abbreviation,description,language,email, phone,homepage,logo_url,address,city,province,country,postal_code,latitude,longitude,created,created_by, @@ -485,6 +491,15 @@ + + diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.xml index fcfa3f0baa..9560e9a4ad 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/ChangeSuggestionMapper.xml @@ -12,14 +12,14 @@ entity_type, entity_key, type, status, proposed, proposed_by, proposer_email, changes, comments, suggested_entity, merge_target_key, institution_converted_collection, name_new_institution_converted_collection, - modified, modified_by + modified, modified_by, ih_identifier, create_institution cs.key, cs.entity_type, cs.entity_key, cs.type, cs.status, cs.proposed, cs.proposed_by, cs.proposer_email, cs. applied, cs.applied_by, cs.discarded_by, cs.discarded, cs.suggested_entity, cs.comments, cs.merge_target_key, cs.changes, cs.institution_converted_collection, cs.name_new_institution_converted_collection, - cs.modified, cs.modified_by + cs.modified, cs.modified_by, cs.ih_identifier, cs.create_institution @@ -37,7 +37,9 @@ #{institutionConvertedCollection,jdbcType=OTHER}, #{nameNewInstitutionConvertedCollection,jdbcType=VARCHAR}, now(), - #{modifiedBy,jdbcType=VARCHAR} + #{modifiedBy,jdbcType=VARCHAR}, + #{ihIdentifier, jdbcType=VARCHAR}, + #{createInstitution, jdbcType=BOOLEAN} @@ -94,6 +96,9 @@ AND cs.entity_key = #{entityKey,jdbcType=OTHER} + + AND cs.ih_identifier = #{ihIdentifier, jdbcType=OTHER} + ORDER BY cs.proposed DESC 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 e3ed1c3978..0950775d65 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 @@ -201,28 +201,16 @@ , inst.name institutionName, inst.code institutionCode, c.fulltext_search ,query ,similarity(c.name, #{params.fuzzyName,jdbcType=VARCHAR}) AS similarity_score - + FROM collection c + + + + ) AS c ORDER BY - - - - c.number_specimens - - - - - - - DESC - - - ASC - - - - NULLS LAST, - + + + ts_rank_cd(c.fulltext_search, query) DESC, similarity_score DESC, c.created DESC, c.key @@ -233,15 +221,15 @@ - FROM collection c LEFT JOIN institution inst ON inst.key = c.institution_key - - - INNER JOIN collection_machine_tag cmt on cmt.collection_key = c.key INNER JOIN machine_tag mt on mt.key = cmt.machine_tag_key @@ -250,10 +238,8 @@ INNER JOIN collection_identifier ci on ci.collection_key = c.key INNER JOIN identifier id on id.key = ci.identifier_key - - LEFT JOIN address addr ON addr.key = c.address_key - LEFT JOIN address mail_addr ON mail_addr.key = c.mailing_address_key - + LEFT JOIN address addr ON addr.key = c.address_key + LEFT JOIN address mail_addr ON mail_addr.key = c.mailing_address_key INNER JOIN master_sync_metadata m ON c.master_sync_metadata_key = m.key diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.xml index cf86c3b7e1..a876547da2 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/CollectionsSearchMapper.xml @@ -3,130 +3,445 @@ - + + + + i.key, i.code, i.name, i.description, i.alternative_codes, + i.display_on_NHCPortal AS displayOnNHCPortal,'institution' AS type, + i.address_key, i.mailing_address_key, null::uuid institution_key, null institution_name, null institution_code + + + + c.key, c.code, c.name, c.description, c.alternative_codes, + c.display_on_NHCPortal AS displayOnNHCPortal,'collection' AS type, + c.address_key, c.mailing_address_key, i.key institution_key, i.name institution_name, i.code institution_code + + + + to_tsquery('english',regexp_replace(quote_literal(unaccent(trim(#{params.query}))),'\s+',':*&','g')||':*') + + + + + + + + + + + + + + + JOIN to_tsquery('english',regexp_replace(quote_literal(unaccent(trim(#{params.query}))),'\s+',':*&','g')||':*') AS query ON query @@ fulltext_search + OR EXISTS( + SELECT collection_contact_key + FROM institution_collection_contact JOIN collection_contact cc ON cc.key = collection_contact_key + WHERE institution_key = i.key AND query @@ cc.fulltext_search + ) + + + + + + + + + + + + + + + + + + + + AND d.usage_key IN + + #{item,jdbcType=INTEGER} + + + + AND d.usage_name IN + + #{item,jdbcType=VARCHAR} + + + + AND d.usage_rank IN + + #{item,jdbcType=VARCHAR} + + + + AND d.taxon_keys && ARRAY + + #{item,jdbcType=INTEGER} + + + + + + AND d.individual_count = #{params.individualCount.exactValue,jdbcType=INTEGER} + + + + AND d.individual_count >= #{params.individualCount.lowerBound,jdbcType=INTEGER} + + + AND d.individual_count <= #{params.individualCount.higherBound,jdbcType=INTEGER} + + + + + + AND d.identified_by && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + AND d.date_identified >= #{params.dateIdentifiedFrom} + AND d.date_identified < #{params.dateIdentifiedBefore} + + + AND d.type_status && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + + AND d.recorded_by && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + + AND d.discipline IN + + #{item,jdbcType=VARCHAR} + + + + AND d.object_classification_name IN + + #{item,jdbcType=VARCHAR} + + + + AND d.country IN + + #{item,jdbcType=VARCHAR} + + + + AND d.issues && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + + + + JOIN to_tsquery('english',regexp_replace(quote_literal(unaccent(trim(#{params.query}))),'\s+',':*&','g')||':*') AS query ON query @@ c.fulltext_search + OR EXISTS( + SELECT collection_contact_key + FROM collection_collection_contact JOIN collection_contact cc ON cc.key = collection_contact_key + WHERE collection_key = c.key AND query @@ cc.fulltext_search + ) + OR @@ d.fulltext_search + OR @@ ds.fulltext_search + + diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/Common.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/Common.xml new file mode 100644 index 0000000000..f6160f87a7 --- /dev/null +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/Common.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + ${alias}number_specimens + + + + + + + DESC + + + ASC + + + + NULLS LAST, + + + + diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/DescriptorsMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/DescriptorsMapper.xml new file mode 100644 index 0000000000..c4946bfaf9 --- /dev/null +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/collections/DescriptorsMapper.xml @@ -0,0 +1,323 @@ + + + + + + + + + + title, description, collection_key, created, created_by, modified, modified_by + + + + ds.key, ds.title, ds.description, ds.collection_key, ds.created, ds.created_by, ds.modified, ds.modified_by, + ds.deleted + + + + #{title,jdbcType=VARCHAR}, + #{description,jdbcType=VARCHAR}, + #{collectionKey,jdbcType=OTHER}, + now(), + #{createdBy,jdbcType=VARCHAR}, + now(), + #{modifiedBy,jdbcType=VARCHAR} + + + + title = #{title,jdbcType=VARCHAR}, + description = #{description,jdbcType=VARCHAR}, + modified_by = #{modifiedBy,jdbcType=VARCHAR}, + modified = now(), + deleted = null + + + + + JOIN to_tsquery('english',regexp_replace(quote_literal(unaccent(trim(#{params.query}))),'\s+',':*&','g')||':*') + AS query ON query @@ fulltext_search + + + + INSERT INTO collection_descriptor_group() + VALUES() + + + + UPDATE collection_descriptor_group + SET + + WHERE key = #{key,jdbcType=OTHER} + + + + UPDATE collection_descriptor_group + SET deleted = now() + WHERE key = #{key,jdbcType=OTHER} AND deleted IS NULL + + + + + + + + + + FROM collection_descriptor_group ds + + + + + + + ds.deleted IS NOT NULL + + + ds.deleted IS NULL + + + + AND ds.collection_key = #{params.collectionKey,jdbcType=OTHER} + + + AND lower(ds.title) = lower(#{params.title,jdbcType=VARCHAR}) + + + AND lower(ds.description) = lower(#{params.description,jdbcType=VARCHAR}) + + + + + + + + + + + + + + + + + + + collection_descriptor_group_key, usage_key, usage_name, usage_rank, taxon_classification, taxon_keys, country, + individual_count, identified_by, date_identified, type_status, recorded_by, discipline, object_classification_name, + issues + + + + d.key, d.collection_descriptor_group_key, d.usage_key, d.usage_name, d.usage_rank, d.taxon_classification, d.country, + d.individual_count, d.identified_by, d.date_identified, d.type_status, d.recorded_by, d.discipline, + d.object_classification_name, d.issues + + + + #{descriptorGroupKey,jdbcType=INTEGER}, + #{usageKey,jdbcType=INTEGER}, + #{usageName,jdbcType=VARCHAR}, + #{usageRank,jdbcType=VARCHAR}, + #{taxonClassification,jdbcType=OTHER,typeHandler=RankedNameListTypeHandler}::jsonb, + #{taxonKeys,jdbcType=ARRAY,typeHandler=IntegerArrayTypeHandler}, + #{country,jdbcType=VARCHAR}, + #{individualCount,jdbcType=INTEGER}, + #{identifiedBy,jdbcType=ARRAY,typeHandler=StringArrayTypeHandler}, + #{dateIdentified,jdbcType=OTHER}, + #{typeStatus,jdbcType=ARRAY,typeHandler=StringArrayTypeHandler}, + #{recordedBy,jdbcType=ARRAY,typeHandler=StringArrayTypeHandler}, + #{discipline,jdbcType=VARCHAR}, + #{objectClassificationName,jdbcType=VARCHAR}, + #{issues,jdbcType=ARRAY,typeHandler=StringArrayTypeHandler} + + + + INSERT INTO collection_descriptor() + VALUES() + + + + DELETE FROM collection_descriptor + WHERE collection_descriptor_group_key = #{descriptorGroupKey,jdbcType=INTEGER} + + + + + + + + + + FROM collection_descriptor d + INNER JOIN collection_descriptor_group ds ON ds.key = d.collection_descriptor_group_key AND ds.deleted IS NULL + + + + + + AND d.collection_descriptor_group_key = #{params.descriptorGroupKey,jdbcType=OTHER} + + + AND d.usage_key IN + + #{item,jdbcType=INTEGER} + + + + AND d.usage_name IN + + #{item,jdbcType=VARCHAR} + + + + AND d.usage_rank IN + + #{item,jdbcType=VARCHAR} + + + + AND d.taxon_keys && ARRAY + + #{item,jdbcType=INTEGER} + + + + + + AND d.individual_count = #{params.individualCount.exactValue,jdbcType=INTEGER} + + + + AND d.individual_count >= #{params.individualCount.lowerBound,jdbcType=INTEGER} + + + AND d.individual_count <= #{params.individualCount.higherBound,jdbcType=INTEGER} + + + + + + AND d.identified_by && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + AND d.date_identified >= #{params.dateIdentifiedFrom} + AND d.date_identified < #{params.dateIdentifiedBefore} + + + AND d.type_status && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + + AND d.recorded_by && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + + AND d.discipline IN + + #{item,jdbcType=VARCHAR} + + + + AND d.object_classification_name IN + + #{item,jdbcType=VARCHAR} + + + + AND d.country IN + + #{item,jdbcType=VARCHAR} + + + + AND d.issues && ARRAY + + #{item,jdbcType=VARCHAR}::text + + + + + + + + + + + + collection_descriptor_key, field_name, field_value + + + + v.key, v.collection_descriptor_key, v.field_name, v.field_value + + + + #{descriptorKey,jdbcType=INTEGER}, + #{fieldName,jdbcType=VARCHAR}, + #{fieldValue,jdbcType=VARCHAR} + + + + INSERT INTO collection_descriptor_verbatim() + VALUES() + + + + + + + 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 91d9c8c91a..f6c5d3c365 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 @@ -201,28 +201,16 @@ , i.fulltext_search ,query ,similarity(i.name, #{params.fuzzyName,jdbcType=VARCHAR}) AS similarity_score - + FROM institution i + + + + ) AS i ORDER BY - - - - i.number_specimens - - - - - - - DESC - - - ASC - - - - NULLS LAST, - + + + ts_rank_cd(i.fulltext_search, query) DESC, similarity_score DESC, i.created DESC, i.key @@ -233,6 +221,10 @@ @@ -242,6 +234,10 @@ similarity_score, i.key) i.key, i.name, i.latitude, i.longitude ,similarity(i.name, #{params.fuzzyName,jdbcType=VARCHAR}) AS similarity_score + FROM institution i + + + AND i.latitude IS NOT NULL AND i.longitude IS NOT NULL ORDER BY ts_rank_cd(i.fulltext_search, query) DESC, @@ -253,10 +249,6 @@ - FROM institution i - - - INNER JOIN institution_machine_tag imt on imt.institution_key = i.key INNER JOIN machine_tag mt on mt.key = imt.machine_tag_key @@ -265,10 +257,8 @@ INNER JOIN institution_identifier ii on ii.institution_key = i.key INNER JOIN identifier id on id.key = ii.identifier_key - - LEFT JOIN address addr ON addr.key = i.address_key - LEFT JOIN address mail_addr ON mail_addr.key = i.mailing_address_key - + LEFT JOIN address addr ON addr.key = i.address_key + LEFT JOIN address mail_addr ON mail_addr.key = i.mailing_address_key INNER JOIN master_sync_metadata m ON i.master_sync_metadata_key = m.key diff --git a/registry-search/src/main/java/org/gbif/registry/search/dataset/service/collections/CollectionsSearchService.java b/registry-search/src/main/java/org/gbif/registry/search/dataset/service/collections/CollectionsSearchService.java deleted file mode 100644 index 40eb5bd0e1..0000000000 --- a/registry-search/src/main/java/org/gbif/registry/search/dataset/service/collections/CollectionsSearchService.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.gbif.registry.search.dataset.service.collections; - -import org.gbif.api.model.collections.search.CollectionsSearchResponse; -import org.gbif.api.vocabulary.Country; -import org.gbif.registry.domain.collections.TypeParam; -import org.gbif.registry.persistence.mapper.collections.CollectionsSearchMapper; -import org.gbif.registry.persistence.mapper.collections.dto.SearchDto; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.regex.Pattern; - -import org.elasticsearch.common.Strings; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** Service to lookup GRSciColl institutions and collections. */ -@Service -public class CollectionsSearchService { - - private static final Pattern HIGHLIGHT_PATTERN = Pattern.compile(".*.+.*"); - - private final CollectionsSearchMapper searchMapper; - - @Autowired - public CollectionsSearchService(CollectionsSearchMapper searchMapper) { - this.searchMapper = searchMapper; - } - - public List search( - String query, - boolean highlight, - TypeParam type, - Boolean displayOnNHCPortal, - Country country, - int limit) { - List dtos = - searchMapper.search( - query, - highlight, - type != null ? type.name() : null, - displayOnNHCPortal, - country, - limit); - - // the query can return duplicates so we need an auxiliary map to filter duplicates - Map responsesMap = new HashMap<>(); - List responses = new ArrayList<>(); - dtos.forEach( - dto -> { - if (responsesMap.containsKey(dto.getKey())) { - if (highlight) { - CollectionsSearchResponse existing = responsesMap.get(dto.getKey()); - addMatches(existing, dto); - } - return; - } - - CollectionsSearchResponse response = new CollectionsSearchResponse(); - response.setType(dto.getType()); - response.setCode(dto.getCode()); - response.setKey(dto.getKey()); - response.setName(dto.getName()); - response.setDisplayOnNHCPortal(dto.isDisplayOnNHCPortal()); - response.setCountry(dto.getCountry()); - response.setMailingCountry(dto.getMailCountry()); - - if (dto.getType().equals("collection")) { - response.setInstitutionKey(dto.getInstitutionKey()); - response.setInstitutionCode(dto.getInstitutionCode()); - response.setInstitutionName(dto.getInstitutionName()); - } - - if (highlight) { - addMatches(response, dto); - } - - responses.add(response); - responsesMap.put(dto.getKey(), response); - }); - - return responses; - } - - private void addMatches(CollectionsSearchResponse response, SearchDto dto) { - Set matches = new HashSet<>(); - createHighlightMatch(dto.getCodeHighlight(), "code").ifPresent(matches::add); - createHighlightMatch(dto.getDescriptionHighlight(), "description").ifPresent(matches::add); - createHighlightMatch(dto.getAlternativeCodesHighlight(), "alternativeCode") - .ifPresent(matches::add); - createHighlightMatch(dto.getAddressHighlight(), "address").ifPresent(matches::add); - createHighlightMatch(dto.getCityHighlight(), "city").ifPresent(matches::add); - createHighlightMatch(dto.getProvinceHighlight(), "province").ifPresent(matches::add); - createHighlightMatch(dto.getCountryHighlight(), "country").ifPresent(matches::add); - createHighlightMatch(dto.getMailAddressHighlight(), "mailingAddress").ifPresent(matches::add); - createHighlightMatch(dto.getMailCityHighlight(), "mailingCity").ifPresent(matches::add); - createHighlightMatch(dto.getMailProvinceHighlight(), "mailingProvince").ifPresent(matches::add); - createHighlightMatch(dto.getMailCountryHighlight(), "mailingCountry").ifPresent(matches::add); - - Optional nameMatch = - createHighlightMatch(dto.getNameHighlight(), "name"); - if (nameMatch.isPresent()) { - matches.add(nameMatch.get()); - } else if (dto.isSimilarityMatch()) { - CollectionsSearchResponse.Match match = new CollectionsSearchResponse.Match(); - match.setField("name"); - match.setSnippet(dto.getName()); - matches.add(match); - } - - if (!matches.isEmpty()) { - if (response.getMatches() == null) { - response.setMatches(matches); - } else { - response.getMatches().addAll(matches); - } - } - } - - private static Optional createHighlightMatch( - String highlight, String fieldName) { - if (!Strings.isNullOrEmpty(highlight) && HIGHLIGHT_PATTERN.matcher(highlight).matches()) { - CollectionsSearchResponse.Match match = new CollectionsSearchResponse.Match(); - match.setField(fieldName); - match.setSnippet(highlight); - return Optional.of(match); - } - - return Optional.empty(); - } -} diff --git a/registry-service/pom.xml b/registry-service/pom.xml index 98afa9d98a..4880a4f578 100644 --- a/registry-service/pom.xml +++ b/registry-service/pom.xml @@ -54,6 +54,10 @@ org.gbif.vocabulary vocabulary-rest-ws-client + + org.gbif + gbif-parsers + diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/BaseCollectionEntityService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/BaseCollectionEntityService.java index 6a5d8a829b..b1f1c34fd4 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/BaseCollectionEntityService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/BaseCollectionEntityService.java @@ -14,7 +14,6 @@ package org.gbif.registry.service.collections; import lombok.extern.slf4j.Slf4j; -import org.elasticsearch.common.Strings; import org.gbif.api.model.collections.Address; import org.gbif.api.model.collections.Contact; import org.gbif.api.model.collections.*; @@ -33,7 +32,6 @@ import org.gbif.registry.events.collections.*; import org.gbif.registry.persistence.mapper.*; import org.gbif.registry.persistence.mapper.collections.*; -import org.gbif.registry.persistence.mapper.collections.params.RangeParam; import org.gbif.registry.security.SecurityContextCheck; import org.gbif.registry.service.WithMyBatis; import org.gbif.registry.service.collections.utils.IdentifierValidatorUtils; @@ -57,14 +55,11 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; -import java.util.regex.Matcher; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; import static org.gbif.registry.security.UserRoles.*; import static org.gbif.registry.service.collections.utils.MasterSourceUtils.*; -import static org.gbif.registry.service.collections.utils.SearchUtils.INTEGER_RANGE; -import static org.gbif.registry.service.collections.utils.SearchUtils.WILDCARD_SEARCH; @Validated @Slf4j @@ -876,43 +871,4 @@ private void checkExistsNetworkEntity( "Cannot set a deleted " + entityClass.getSimpleName() + " as master source"); } } - - protected RangeParam parseIntegerRangeParameter(String param) { - if (Strings.isNullOrEmpty(param)) { - return null; - } - - RangeParam rangeParam = new RangeParam(); - Matcher matcher = INTEGER_RANGE.matcher(param); - if (matcher.matches()) { - String lowerString = matcher.group(1); - if (!lowerString.equals(WILDCARD_SEARCH)) { - rangeParam.setLowerBound(Integer.valueOf(lowerString)); - } - - String higherString = matcher.group(2); - if (!higherString.equals(WILDCARD_SEARCH)) { - rangeParam.setHigherBound(Integer.valueOf(higherString)); - } - } else { - try { - rangeParam.setExactValue(Integer.valueOf(param)); - } catch (Exception ex) { - log.info("Invalid range {}", param, ex); - } - } - - return rangeParam; - } - - protected List parseGbifRegion(SearchRequest searchRequest) { - List countries = new ArrayList<>(); - if (searchRequest.getGbifRegion() != null && !searchRequest.getGbifRegion().isEmpty()) { - countries.addAll( - Arrays.stream(Country.values()) - .filter(c -> searchRequest.getGbifRegion().contains(c.getGbifRegion())) - .collect(Collectors.toList())); - } - return countries; - } } diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/CollectionsSearchService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/CollectionsSearchService.java new file mode 100644 index 0000000000..8c7ff4ae19 --- /dev/null +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/CollectionsSearchService.java @@ -0,0 +1,418 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gbif.registry.service.collections; + +import static org.gbif.registry.service.collections.utils.ParamUtils.parseGbifRegion; +import static org.gbif.registry.service.collections.utils.ParamUtils.parseIntegerRangeParameter; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Strings; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.gbif.api.model.collections.request.CollectionDescriptorsSearchRequest; +import org.gbif.api.model.collections.request.InstitutionSearchRequest; +import org.gbif.api.model.collections.request.SearchRequest; +import org.gbif.api.model.collections.search.BaseSearchResponse; +import org.gbif.api.model.collections.search.CollectionSearchResponse; +import org.gbif.api.model.collections.search.CollectionsFullSearchResponse; +import org.gbif.api.model.collections.search.DescriptorMatch; +import org.gbif.api.model.collections.search.Highlight; +import org.gbif.api.model.collections.search.InstitutionSearchResponse; +import org.gbif.api.model.common.paging.Pageable; +import org.gbif.api.model.common.paging.PagingRequest; +import org.gbif.api.model.common.paging.PagingResponse; +import org.gbif.api.vocabulary.Country; +import org.gbif.registry.domain.collections.TypeParam; +import org.gbif.registry.persistence.mapper.collections.CollectionsSearchMapper; +import org.gbif.registry.persistence.mapper.collections.dto.BaseSearchDto; +import org.gbif.registry.persistence.mapper.collections.dto.CollectionSearchDto; +import org.gbif.registry.persistence.mapper.collections.dto.InstitutionSearchDto; +import org.gbif.registry.persistence.mapper.collections.dto.SearchDto; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorsParams; +import org.gbif.registry.persistence.mapper.collections.params.FullTextSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.InstitutionListParams; +import org.gbif.registry.persistence.mapper.collections.params.ListParams; +import org.gbif.registry.service.collections.utils.Vocabularies; +import org.gbif.vocabulary.client.ConceptClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** Service to lookup GRSciColl institutions and collections. */ +@Service +public class CollectionsSearchService { + + private static final Pattern HIGHLIGHT_PATTERN = Pattern.compile(".*.+.*"); + + private final CollectionsSearchMapper searchMapper; + private final ConceptClient conceptClient; + + @Autowired + public CollectionsSearchService( + CollectionsSearchMapper searchMapper, ConceptClient conceptClient) { + this.searchMapper = searchMapper; + this.conceptClient = conceptClient; + } + + public List search( + String query, + boolean highlight, + TypeParam type, + Boolean displayOnNHCPortal, + Country country, + int limit) { + List dtos = + searchMapper.search( + FullTextSearchParams.builder() + .query(query) + .highlight(highlight) + .type(type != null ? type.name() : null) + .displayOnNHCPortal(displayOnNHCPortal) + .country(country) + .limit(limit) + .build()); + + // the query can return duplicates so we need an auxiliary map to filter duplicates + Map responsesMap = new HashMap<>(); + List responses = new ArrayList<>(); + dtos.forEach( + dto -> { + if (responsesMap.containsKey(dto.getKey())) { + CollectionsFullSearchResponse existing = responsesMap.get(dto.getKey()); + if (highlight) { + addHighlights(existing, dto); + } + if (dto.getDescriptorKey() != null) { + existing.getDescriptorMatches().add(addDescriptorMatch(dto)); + } + return; + } + + CollectionsFullSearchResponse response = new CollectionsFullSearchResponse(); + response.setType(dto.getType()); + response.setCode(dto.getCode()); + response.setKey(dto.getKey()); + response.setName(dto.getName()); + response.setDisplayOnNHCPortal(dto.isDisplayOnNHCPortal()); + response.setCountry(dto.getCountry()); + response.setMailingCountry(dto.getMailingCountry()); + + if (dto.getType().equals("collection")) { + response.setInstitutionKey(dto.getInstitutionKey()); + response.setInstitutionCode(dto.getInstitutionCode()); + response.setInstitutionName(dto.getInstitutionName()); + } + + if (dto.getDescriptorKey() != null) { + response.getDescriptorMatches().add(addDescriptorMatch(dto)); + } + + if (highlight) { + addHighlights(response, dto); + } + + responses.add(response); + responsesMap.put(dto.getKey(), response); + }); + + return responses; + } + + public PagingResponse searchInstitutions( + InstitutionSearchRequest searchRequest) { + + Pageable page = searchRequest.getPage() == null ? new PagingRequest() : searchRequest.getPage(); + + Vocabularies.addChildrenConcepts(searchRequest, conceptClient); + + InstitutionListParams.InstitutionListParamsBuilder listParamsBuilder = + InstitutionListParams.builder() + .types(searchRequest.getType()) + .institutionalGovernances(searchRequest.getInstitutionalGovernance()) + .disciplines(searchRequest.getDisciplines()) + .institutionKeys(searchRequest.getInstitutionKeys()); + buildCommonParams(listParamsBuilder, searchRequest); + InstitutionListParams listParams = listParamsBuilder.build(); + + List dtos = searchMapper.searchInstitutions(listParams); + List results = + dtos.stream() + .map( + dto -> { + InstitutionSearchResponse response = new InstitutionSearchResponse(); + createCommonResponse(dto, response); + response.setTypes(dto.getTypes()); + response.setInstitutionalGovernances(dto.getInstitutionalGovernances()); + response.setDisciplines(dto.getDisciplines()); + response.setLatitude(dto.getLatitude()); + response.setLongitude(dto.getLongitude()); + response.setFoundingDate(dto.getFoundingDate()); + response.setNumberSpecimens(dto.getNumberSpecimens()); + response.setOccurrenceCount(dto.getOccurrenceCount()); + response.setTypeSpecimenCount(dto.getTypeSpecimenCount()); + + if (Boolean.TRUE.equals(searchRequest.getHl())) { + addHighlights(response, dto); + } + + return response; + }) + .collect(Collectors.toList()); + + return new PagingResponse<>(page, searchMapper.countInstitutions(listParams), results); + } + + public PagingResponse searchCollections( + CollectionDescriptorsSearchRequest searchRequest) { + + Pageable page = searchRequest.getPage() == null ? new PagingRequest() : searchRequest.getPage(); + + Set institutionKeys = new HashSet<>(); + if (searchRequest.getInstitution() != null) { + institutionKeys.add(searchRequest.getInstitution()); + } + if (searchRequest.getInstitutionKeys() != null) { + institutionKeys.addAll(searchRequest.getInstitutionKeys()); + } + + Vocabularies.addChildrenConcepts(searchRequest, conceptClient); + + DescriptorsParams.DescriptorsParamsBuilder listParamsBuilder = + DescriptorsParams.builder() + .contentTypes(searchRequest.getContentTypes()) + .preservationTypes(searchRequest.getPreservationTypes()) + .accessionStatus(searchRequest.getAccessionStatus()) + .personalCollection(searchRequest.getPersonalCollection()) + .institutionKeys(new ArrayList<>(institutionKeys)) + .usageName(searchRequest.getUsageName()) + .usageKey(searchRequest.getUsageKey()) + .usageRank(searchRequest.getUsageRank()) + .taxonKey(searchRequest.getTaxonKey()) + .descriptorCountry(searchRequest.getDescriptorCountry()) + .individualCount(parseIntegerRangeParameter(searchRequest.getIndividualCount())) + .identifiedBy(searchRequest.getIdentifiedBy()) + .dateIdentifiedBefore( + searchRequest.getDateIdentified() != null + ? searchRequest.getDateIdentified().lowerEndpoint() + : null) + .dateIdentifiedFrom( + searchRequest.getDateIdentified() != null + ? searchRequest.getDateIdentified().upperEndpoint() + : null) + .typeStatus(searchRequest.getTypeStatus()) + .recordedBy(searchRequest.getRecordedBy()) + .discipline(searchRequest.getDiscipline()) + .objectClassification(searchRequest.getObjectClassification()) + .issues(searchRequest.getIssue()); + buildCommonParams(listParamsBuilder, searchRequest); + DescriptorsParams listParams = listParamsBuilder.build(); + + List dtos = searchMapper.searchCollections(listParams); + Map responsesMap = new HashMap<>(); + List results = new ArrayList<>(); + dtos.stream() + .forEach( + dto -> { + if (responsesMap.containsKey(dto.getKey())) { + CollectionSearchResponse existing = responsesMap.get(dto.getKey()); + if (Boolean.TRUE.equals(listParams.getHighlight())) { + addHighlights(existing, dto); + } + if (isCollectionDescriptorResult(dto, listParams)) { + existing.getDescriptorMatches().add(addDescriptorMatch(dto)); + } + return; + } + + CollectionSearchResponse response = new CollectionSearchResponse(); + responsesMap.put(dto.getKey(), response); + results.add(response); + + createCommonResponse(dto, response); + response.setContentTypes(dto.getContentTypes()); + response.setPersonalCollection(dto.isPersonalCollection()); + response.setPreservationTypes(dto.getPreservationTypes()); + response.setAccessionStatus(dto.getAccessionStatus()); + response.setInstitutionKey(dto.getInstitutionKey()); + response.setInstitutionName(dto.getInstitutionName()); + response.setInstitutionCode(dto.getInstitutionCode()); + response.setNumberSpecimens(dto.getNumberSpecimens()); + response.setTaxonomicCoverage(dto.getTaxonomicCoverage()); + response.setGeographicCoverage(dto.getGeographicCoverage()); + response.setDepartment(dto.getDepartment()); + response.setDivision(dto.getDivision()); + response.setDisplayOnNHCPortal(dto.isDisplayOnNHCPortal()); + response.setOccurrenceCount(dto.getOccurrenceCount()); + response.setTypeSpecimenCount(dto.getTypeSpecimenCount()); + + if (isCollectionDescriptorResult(dto, listParams)) { + response.getDescriptorMatches().add(addDescriptorMatch(dto)); + } + + if (Boolean.TRUE.equals(searchRequest.getHl())) { + addHighlights(response, dto); + } + }); + + return new PagingResponse<>(page, searchMapper.countCollections(listParams), results); + } + + private static boolean isCollectionDescriptorResult( + CollectionSearchDto dto, DescriptorsParams params) { + return dto.getDescriptorKey() != null + && (dto.getQueryDescriptorRank() != null && dto.getQueryDescriptorRank() > 0 + || params.descriptorSearchWithoutQuery()); + } + + private static DescriptorMatch addDescriptorMatch(SearchDto dto) { + DescriptorMatch descriptorMatch = new DescriptorMatch(); + descriptorMatch.setKey(dto.getDescriptorKey()); + descriptorMatch.setDescriptorGroupKey(dto.getDescriptorGroupKey()); + descriptorMatch.setUsageName(dto.getDescriptorUsageName()); + descriptorMatch.setUsageKey(dto.getDescriptorUsageKey()); + descriptorMatch.setUsageRank(dto.getDescriptorUsageRank()); + descriptorMatch.setCountry(dto.getDescriptorCountry()); + descriptorMatch.setIndividualCount(dto.getDescriptorIndividualCount()); + descriptorMatch.setIdentifiedBy(dto.getDescriptorIdentifiedBy()); + descriptorMatch.setDateIdentified(dto.getDescriptorDateIdentified()); + descriptorMatch.setTypeStatus(dto.getDescriptorTypeStatus()); + descriptorMatch.setRecordedBy(dto.getDescriptorRecordedBy()); + descriptorMatch.setDiscipline(dto.getDescriptorDiscipline()); + descriptorMatch.setObjectClassification(dto.getDescriptorObjectClassification()); + descriptorMatch.setIssues(dto.getDescriptorIssues()); + return descriptorMatch; + } + + private void buildCommonParams( + ListParams.ListParamsBuilder listParams, SearchRequest searchRequest) { + String query = + searchRequest.getQ() != null + ? Strings.emptyToNull(CharMatcher.WHITESPACE.trimFrom(searchRequest.getQ())) + : searchRequest.getQ(); + + listParams + .query(query) + .code(searchRequest.getCode()) + .name(searchRequest.getName()) + .alternativeCode(searchRequest.getAlternativeCode()) + .countries(searchRequest.getCountry()) + .regionCountries(parseGbifRegion(searchRequest)) + .city(searchRequest.getCity()) + .fuzzyName(searchRequest.getFuzzyName()) + .active(searchRequest.getActive()) + .masterSourceType(searchRequest.getMasterSourceType()) + .numberSpecimens(parseIntegerRangeParameter(searchRequest.getNumberSpecimens())) + .displayOnNHCPortal(searchRequest.getDisplayOnNHCPortal()) + .occurrenceCount(parseIntegerRangeParameter(searchRequest.getOccurrenceCount())) + .typeSpecimenCount(parseIntegerRangeParameter(searchRequest.getTypeSpecimenCount())) + .sortBy(searchRequest.getSortBy()) + .sortOrder(searchRequest.getSortOrder()) + .highlight(searchRequest.getHl()) + .page(searchRequest.getPage()); + } + + private void createCommonResponse(BaseSearchDto dto, BaseSearchResponse response) { + response.setKey(dto.getKey()); + response.setCode(dto.getCode()); + response.setName(dto.getName()); + response.setDescription(dto.getDescription()); + response.setActive(dto.isActive()); + response.setCountry(dto.getCountry()); + response.setMailingCountry(dto.getMailingCountry()); + response.setCity(dto.getCity()); + response.setMailingCity(dto.getMailingCity()); + response.setAlternativeCodes(dto.getAlternativeCodes()); + response.setDisplayOnNHCPortal(dto.isDisplayOnNHCPortal()); + response.setFeaturedImageUrl(dto.getFeaturedImageUrl()); + response.setFeaturedImageLicense(dto.getFeaturedImageLicense()); + response.setFeaturedImageAttribution(dto.getFeaturedImageAttribution()); + } + + private void addHighlights(BaseSearchResponse response, BaseSearchDto dto) { + Set highlights = new HashSet<>(); + createHighlightMatch(dto.getCodeHighlight(), "code").ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptionHighlight(), "description").ifPresent(highlights::add); + createHighlightMatch(dto.getAlternativeCodesHighlight(), "alternativeCode") + .ifPresent(highlights::add); + createHighlightMatch(dto.getAddressHighlight(), "address").ifPresent(highlights::add); + createHighlightMatch(dto.getCityHighlight(), "city").ifPresent(highlights::add); + createHighlightMatch(dto.getProvinceHighlight(), "province").ifPresent(highlights::add); + createHighlightMatch(dto.getCountryHighlight(), "country").ifPresent(highlights::add); + createHighlightMatch(dto.getMailAddressHighlight(), "mailingAddress") + .ifPresent(highlights::add); + createHighlightMatch(dto.getMailCityHighlight(), "mailingCity").ifPresent(highlights::add); + createHighlightMatch(dto.getMailProvinceHighlight(), "mailingProvince") + .ifPresent(highlights::add); + createHighlightMatch(dto.getMailCountryHighlight(), "mailingCountry") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorUsageNameHighlight(), "descriptor.usageName") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorCountryHighlight(), "descriptor.country") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorIdentifiedByHighlight(), "descriptor.identifiedBy") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorTypeStatusHighlight(), "descriptor.typeStatus") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorRecordedByHighlight(), "descriptor.recordedBy") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorDisciplineHighlight(), "descriptor.discipline") + .ifPresent(highlights::add); + createHighlightMatch( + dto.getDescriptorObjectClassificationHighlight(), "descriptor.objectClassification") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorIssuesHighlight(), "descriptor.issues") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorGroupTitleHighlight(), "descriptorGroup.title") + .ifPresent(highlights::add); + createHighlightMatch(dto.getDescriptorGroupDescriptionHighlight(), "descriptorGroup.description") + .ifPresent(highlights::add); + + Optional nameMatch = createHighlightMatch(dto.getNameHighlight(), "name"); + if (nameMatch.isPresent()) { + highlights.add(nameMatch.get()); + } else if (dto.isSimilarityMatch()) { + Highlight highlight = new Highlight(); + highlight.setField("name"); + highlight.setSnippet(dto.getName()); + highlights.add(highlight); + } + + if (!highlights.isEmpty()) { + if (response.getHighlights() == null) { + response.setHighlights(highlights); + } else { + response.getHighlights().addAll(highlights); + } + } + } + + private static Optional createHighlightMatch(String highlightText, String fieldName) { + if (!Strings.isNullOrEmpty(highlightText) + && HIGHLIGHT_PATTERN.matcher(highlightText).matches()) { + Highlight highlight = new Highlight(); + highlight.setField(fieldName); + highlight.setSnippet(highlightText); + return Optional.of(highlight); + } + + return Optional.empty(); + } +} 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 038e80c62e..92f18c9ec6 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 @@ -13,8 +13,21 @@ */ package org.gbif.registry.service.collections; +import static com.google.common.base.Preconditions.checkArgument; +import static org.gbif.registry.security.UserRoles.*; +import static org.gbif.registry.service.collections.utils.ParamUtils.parseGbifRegion; +import static org.gbif.registry.service.collections.utils.ParamUtils.parseIntegerRangeParameter; + import com.google.common.base.CharMatcher; import com.google.common.base.Strings; +import java.util.*; +import java.util.stream.Collectors; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Valid; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; +import javax.validation.groups.Default; import org.gbif.api.model.collections.Collection; import org.gbif.api.model.collections.Contact; import org.gbif.api.model.collections.MasterSourceMetadata; @@ -35,7 +48,7 @@ import org.gbif.registry.persistence.mapper.*; import org.gbif.registry.persistence.mapper.collections.*; import org.gbif.registry.persistence.mapper.collections.dto.CollectionDto; -import org.gbif.registry.persistence.mapper.collections.params.CollectionSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.CollectionListParams; import org.gbif.registry.service.WithMyBatis; import org.gbif.registry.service.collections.converters.CollectionConverter; import org.gbif.registry.service.collections.utils.LatimerCoreConverter; @@ -48,18 +61,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestParam; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Valid; -import javax.validation.Validator; -import javax.validation.constraints.NotNull; -import javax.validation.groups.Default; -import java.util.*; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkArgument; -import static org.gbif.registry.security.UserRoles.*; - @Validated @Service public class DefaultCollectionService extends BaseCollectionEntityService @@ -142,7 +143,7 @@ public PagingResponse listAsLatimerCore(CollectionSearchRequest sea private PagingResponse listInternal( CollectionSearchRequest searchRequest, boolean deleted) { if (searchRequest == null) { - searchRequest = new CollectionSearchRequest(); + searchRequest = CollectionSearchRequest.builder().build(); } Pageable page = searchRequest.getPage() == null ? new PagingRequest() : searchRequest.getPage(); @@ -162,8 +163,8 @@ private PagingResponse listInternal( Vocabularies.addChildrenConcepts(searchRequest, conceptClient); - CollectionSearchParams params = - CollectionSearchParams.builder() + CollectionListParams params = + CollectionListParams.builder() .query(query) .code(searchRequest.getCode()) .name(searchRequest.getName()) 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 c1835cf9c5..a0acb2e296 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 @@ -13,8 +13,23 @@ */ package org.gbif.registry.service.collections; +import static com.google.common.base.Preconditions.checkArgument; +import static org.gbif.registry.security.UserRoles.*; +import static org.gbif.registry.service.collections.utils.ParamUtils.parseGbifRegion; +import static org.gbif.registry.service.collections.utils.ParamUtils.parseIntegerRangeParameter; + import com.google.common.base.CharMatcher; import com.google.common.base.Strings; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Valid; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; +import javax.validation.groups.Default; import org.gbif.api.model.collections.Contact; import org.gbif.api.model.collections.Institution; import org.gbif.api.model.collections.MasterSourceMetadata; @@ -35,7 +50,7 @@ import org.gbif.registry.persistence.mapper.*; import org.gbif.registry.persistence.mapper.collections.*; import org.gbif.registry.persistence.mapper.collections.dto.InstitutionGeoJsonDto; -import org.gbif.registry.persistence.mapper.collections.params.InstitutionSearchParams; +import org.gbif.registry.persistence.mapper.collections.params.InstitutionListParams; import org.gbif.registry.service.WithMyBatis; import org.gbif.registry.service.collections.converters.InstitutionConverter; import org.gbif.registry.service.collections.utils.LatimerCoreConverter; @@ -51,20 +66,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestParam; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Valid; -import javax.validation.Validator; -import javax.validation.constraints.NotNull; -import javax.validation.groups.Default; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkArgument; -import static org.gbif.registry.security.UserRoles.*; - @Validated @Service public class DefaultInstitutionService extends BaseCollectionEntityService @@ -168,27 +169,27 @@ public void updateFromLatimerCore(@NotNull @Valid OrganisationalUnit organisatio private PagingResponse listInternal( InstitutionSearchRequest searchRequest, boolean deleted) { if (searchRequest == null) { - searchRequest = new InstitutionSearchRequest(); + searchRequest = InstitutionSearchRequest.builder().build(); } Pageable page = searchRequest.getPage() == null ? new PagingRequest() : searchRequest.getPage(); Vocabularies.addChildrenConcepts(searchRequest, conceptClient); - InstitutionSearchParams params = buildSearchParams(searchRequest, deleted, page); + InstitutionListParams params = buildSearchParams(searchRequest, deleted, page); long total = institutionMapper.count(params); return new PagingResponse<>(page, total, institutionMapper.list(params)); } - private InstitutionSearchParams buildSearchParams( + private InstitutionListParams buildSearchParams( InstitutionSearchRequest searchRequest, boolean deleted, Pageable page) { String query = searchRequest.getQ() != null ? Strings.emptyToNull(CharMatcher.WHITESPACE.trimFrom(searchRequest.getQ())) : searchRequest.getQ(); - return InstitutionSearchParams.builder() + return InstitutionListParams.builder() .query(query) .code(searchRequest.getCode()) .name(searchRequest.getName()) diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/DefaultDescriptorService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/DefaultDescriptorService.java new file mode 100644 index 0000000000..943de9e070 --- /dev/null +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/DefaultDescriptorService.java @@ -0,0 +1,431 @@ +package org.gbif.registry.service.collections.descriptors; + +import static org.gbif.api.util.GrSciCollUtils.*; +import static org.gbif.registry.service.collections.utils.ParamUtils.parseIntegerRangeParameter; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.opencsv.CSVParser; +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import lombok.SneakyThrows; +import org.gbif.api.model.collections.Collection; +import org.gbif.api.model.collections.descriptors.Descriptor; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; +import org.gbif.api.model.collections.request.DescriptorGroupSearchRequest; +import org.gbif.api.model.collections.request.DescriptorSearchRequest; +import org.gbif.api.model.common.export.ExportFormat; +import org.gbif.api.model.common.paging.Pageable; +import org.gbif.api.model.common.paging.PagingRequest; +import org.gbif.api.model.common.paging.PagingResponse; +import org.gbif.api.service.collections.CollectionService; +import org.gbif.api.service.collections.DescriptorsService; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.collections.MasterSourceType; +import org.gbif.checklistbank.ws.client.NubResourceClient; +import org.gbif.dwc.terms.DwcTerm; +import org.gbif.registry.events.EventManager; +import org.gbif.registry.events.collections.EventType; +import org.gbif.registry.events.collections.SubEntityCollectionEvent; +import org.gbif.registry.persistence.mapper.collections.DescriptorsMapper; +import org.gbif.registry.persistence.mapper.collections.dto.DescriptorDto; +import org.gbif.registry.persistence.mapper.collections.dto.VerbatimDto; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorGroupParams; +import org.gbif.registry.persistence.mapper.collections.params.DescriptorParams; +import org.gbif.registry.service.collections.batch.FileParsingUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +@Validated +@Service +public class DefaultDescriptorService implements DescriptorsService { + + private final NubResourceClient nubResourceClient; + private final DescriptorsMapper descriptorsMapper; + private final EventManager eventManager; + private final CollectionService collectionService; + + @Autowired + public DefaultDescriptorService( + NubResourceClient nubResourceClient, + DescriptorsMapper descriptorsMapper, + EventManager eventManager, + CollectionService collectionService) { + this.nubResourceClient = nubResourceClient; + this.descriptorsMapper = descriptorsMapper; + this.eventManager = eventManager; + this.collectionService = collectionService; + } + + @SneakyThrows + @Transactional + @Override + public long createDescriptorGroup( + @NotNull @Valid byte[] descriptorGroupFile, + @NotNull ExportFormat format, + @NotNull String title, + String description, + @NotNull UUID collectionKey) { + Objects.requireNonNull(descriptorGroupFile); + Preconditions.checkArgument(descriptorGroupFile.length > 0); + Objects.requireNonNull(collectionKey); + Preconditions.checkArgument(!Strings.isNullOrEmpty(title)); + + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + final String username = authentication.getName(); + + DescriptorGroup descriptorGroup = new DescriptorGroup(); + descriptorGroup.setTitle(title); + descriptorGroup.setDescription(description); + descriptorGroup.setCreatedBy(username); + descriptorGroup.setModifiedBy(username); + descriptorGroup.setCollectionKey(collectionKey); + descriptorsMapper.createDescriptorGroup(descriptorGroup); + + importDescriptorsFile(descriptorGroupFile, format, descriptorGroup.getKey()); + + eventManager.post( + SubEntityCollectionEvent.newInstance( + collectionKey, + Collection.class, + DescriptorGroup.class, + descriptorGroup.getKey(), + EventType.CREATE)); + + return descriptorGroup.getKey(); + } + + private void importDescriptorsFile( + @NotNull @Valid byte[] descriptorFile, ExportFormat format, long descriptorGroupKey) + throws IOException { + // csv options + CSVParser csvParser = new CSVParserBuilder().withSeparator(format.getDelimiter()).build(); + + Map headersByIndex = new HashMap<>(); + Map headersByName = new HashMap<>(); + try (CSVReader csvReader = + new CSVReaderBuilder( + new BufferedReader(new InputStreamReader(new ByteArrayInputStream(descriptorFile)))) + .withCSVParser(csvParser) + .build()) { + // extract headers + String[] headers = csvReader.readNextSilently(); + for (int i = 0; i < headers.length; i++) { + headersByIndex.put(i, headers[i]); + headersByName.put(headers[i].toLowerCase(), i); + } + + String[] values; + while ((values = csvReader.readNextSilently()) != null) { + if (values.length == 0) { + continue; + } + + values = FileParsingUtils.normalizeValues(headersByIndex.entrySet().size(), values); + + DescriptorDto descriptorDto = new DescriptorDto(); + descriptorDto.setDescriptorGroupKey(descriptorGroupKey); + + // taxonomy + InterpretedResult taxonomyResult = + Interpreter.interpretTaxonomy(values, headersByName, nubResourceClient); + if (taxonomyResult.getResult() != null) { + descriptorDto.setUsageKey(taxonomyResult.getResult().getUsageKey()); + descriptorDto.setUsageRank(taxonomyResult.getResult().getUsageRank()); + descriptorDto.setUsageName(taxonomyResult.getResult().getUsageName()); + descriptorDto.setTaxonKeys(taxonomyResult.getResult().getTaxonKeys()); + descriptorDto.setTaxonClassification(taxonomyResult.getResult().getTaxonClassification()); + } + addIssues(descriptorDto, taxonomyResult); + + // country + InterpretedResult countryResult = + Interpreter.interpretCountry(values, headersByName); + setResult(descriptorDto, countryResult, DescriptorDto::setCountry); + + // individual count + InterpretedResult individualCountResult = + Interpreter.interpretIndividualCount(values, headersByName); + setResult(descriptorDto, individualCountResult, DescriptorDto::setIndividualCount); + + // identifiedBy + InterpretedResult> identifiedByResult = + Interpreter.interpretStringList(values, headersByName, DwcTerm.identifiedBy); + setResult(descriptorDto, identifiedByResult, DescriptorDto::setIdentifiedBy); + + // dateIdentified + InterpretedResult dateIdentifiedResult = + Interpreter.interpretDateIdentified(values, headersByName); + setResult(descriptorDto, dateIdentifiedResult, DescriptorDto::setDateIdentified); + + // TypeStatus + InterpretedResult> typeStatusResult = + Interpreter.interpretTypeStatus(values, headersByName); + setResult(descriptorDto, typeStatusResult, DescriptorDto::setTypeStatus); + + // recordedBy + InterpretedResult> recordedByResult = + Interpreter.interpretStringList(values, headersByName, DwcTerm.recordedBy); + setResult(descriptorDto, recordedByResult, DescriptorDto::setRecordedBy); + + // TODO: create ltc terms?? + // discipline + InterpretedResult disciplineResult = + Interpreter.interpretString(values, headersByName, "ltc:discipline"); + setResult(descriptorDto, disciplineResult, DescriptorDto::setDiscipline); + + // objectClassification + InterpretedResult objectClassificationResult = + Interpreter.interpretString(values, headersByName, "ltc:objectClassificationName"); + setResult( + descriptorDto, objectClassificationResult, DescriptorDto::setObjectClassificationName); + + descriptorsMapper.createDescriptor(descriptorDto); + + // verbatim fields + for (int i = 0; i < values.length; i++) { + descriptorsMapper.createVerbatim( + descriptorDto.getKey(), headersByIndex.get(i), values[i]); + } + } + } + } + + private void setResult( + DescriptorDto descriptorDto, + InterpretedResult result, + BiConsumer setter) { + setter.accept(descriptorDto, result.getResult()); + addIssues(descriptorDto, result); + } + + private static void addIssues(DescriptorDto descriptorDto, InterpretedResult result) { + if (descriptorDto.getIssues() == null) { + descriptorDto.setIssues(new ArrayList<>()); + } + if (result.getIssues() != null) { + descriptorDto.getIssues().addAll(result.getIssues()); + } + } + + @Override + public void deleteDescriptorGroup(@NotNull long key) { + DescriptorGroup descriptorGroup = descriptorsMapper.getDescriptorGroup(key); + Preconditions.checkArgument( + descriptorGroup != null, "Descriptor group not found for key " + key); + + if (isIHDescriptorGroup(key, descriptorGroup.getCollectionKey())) { + // can't delete a descriptor group that comes from IH + return; + } + + descriptorsMapper.deleteDescriptorGroup(key); + + eventManager.post( + SubEntityCollectionEvent.newInstance( + descriptorGroup.getCollectionKey(), + Collection.class, + DescriptorGroup.class, + key, + EventType.DELETE)); + } + + @Override + public DescriptorGroup getDescriptorGroup(@NotNull long key) { + return descriptorsMapper.getDescriptorGroup(key); + } + + @SneakyThrows + @Transactional + @Override + public void updateDescriptorGroup( + @NotNull long descriptorGroupKey, + @NotNull byte[] descriptorGroupFile, + @NotNull ExportFormat format, + @NotNull String title, + String description) { + Objects.requireNonNull(descriptorGroupFile); + Preconditions.checkArgument(descriptorGroupFile.length > 0); + Preconditions.checkArgument(!Strings.isNullOrEmpty(title)); + + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + final String username = authentication.getName(); + + DescriptorGroup descriptorGroup = descriptorsMapper.getDescriptorGroup(descriptorGroupKey); + + if (isIHDescriptorGroup(descriptorGroupKey, descriptorGroup.getCollectionKey())) { + // can't update a descriptor group that comes from IH + return; + } + + descriptorGroup.setTitle(title); + descriptorGroup.setDescription(description); + descriptorGroup.setModifiedBy(username); + descriptorsMapper.updateDescriptorGroup(descriptorGroup); + + // remove descriptors + descriptorsMapper.deleteDescriptors(descriptorGroup.getKey()); + + // reimport the file + importDescriptorsFile(descriptorGroupFile, format, descriptorGroup.getKey()); + + eventManager.post( + SubEntityCollectionEvent.newInstance( + descriptorGroup.getCollectionKey(), + Collection.class, + DescriptorGroup.class, + descriptorGroupKey, + EventType.UPDATE)); + } + + private boolean isIHDescriptorGroup(long descriptorGroupKey, UUID collectionKey) { + Collection collection = collectionService.get(collectionKey); + List ihDescriptorGroups = + collection.getMachineTags().stream() + .filter( + mt -> + mt.getNamespace().equals(IH_NS) + && (mt.getName().equals(COLL_SUMMARY_MT) + || mt.getName().equals(COLLECTORS_MT)) + && mt.getValue() != null) + .map(mt -> Long.parseLong(mt.getValue())) + .collect(Collectors.toList()); + + return collection.getMasterSource().equals(MasterSourceType.IH) + && ihDescriptorGroups.contains(descriptorGroupKey); + } + + @Override + public PagingResponse listDescriptorGroups( + @NotNull UUID collectionKey, DescriptorGroupSearchRequest searchRequest) { + Objects.requireNonNull(collectionKey); + if (searchRequest == null) { + searchRequest = DescriptorGroupSearchRequest.builder().build(); + } + + Pageable page = searchRequest.getPage() == null ? new PagingRequest() : searchRequest.getPage(); + String query = + searchRequest.getQ() != null + ? Strings.emptyToNull(CharMatcher.WHITESPACE.trimFrom(searchRequest.getQ())) + : searchRequest.getQ(); + + DescriptorGroupParams params = + DescriptorGroupParams.builder() + .query(query) + .collectionKey(collectionKey) + .title(searchRequest.getTitle()) + .description(searchRequest.getDescription()) + .deleted(searchRequest.getDeleted()) + .page(page) + .build(); + + return new PagingResponse<>( + page, + descriptorsMapper.countDescriptorGroups(params), + descriptorsMapper.listDescriptorGroups(params)); + } + + @Override + public Descriptor getDescriptor(@NotNull long key) { + return convertRecordDto(descriptorsMapper.getDescriptor(key)); + } + + @Override + public PagingResponse listDescriptors(DescriptorSearchRequest searchRequest) { + if (searchRequest == null) { + searchRequest = DescriptorSearchRequest.builder().build(); + } + + DescriptorParams params = createDescriptorParams(searchRequest); + List dtos = descriptorsMapper.listDescriptors(params); + List results = + dtos.stream().map(DefaultDescriptorService::convertRecordDto).collect(Collectors.toList()); + + return new PagingResponse<>( + params.getPage(), descriptorsMapper.countDescriptors(params), results); + } + + private DescriptorParams createDescriptorParams(DescriptorSearchRequest searchRequest) { + Pageable page = searchRequest.getPage() == null ? new PagingRequest() : searchRequest.getPage(); + String query = + searchRequest.getQ() != null + ? Strings.emptyToNull(CharMatcher.WHITESPACE.trimFrom(searchRequest.getQ())) + : searchRequest.getQ(); + + return DescriptorParams.builder() + .query(query) + .descriptorGroupKey(searchRequest.getDescriptorGroupKey()) + .country(searchRequest.getCountry()) + .dateIdentifiedBefore(searchRequest.getDateIdentifiedBefore()) + .dateIdentifiedFrom(searchRequest.getDateIdentifiedFrom()) + .discipline(searchRequest.getDiscipline()) + .individualCount(parseIntegerRangeParameter(searchRequest.getIndividualCount())) + .usageKey(searchRequest.getUsageKey()) + .usageName(searchRequest.getUsageName()) + .usageRank(searchRequest.getUsageRank()) + .taxonKey(searchRequest.getTaxonKey()) + .objectClassification(searchRequest.getObjectClassification()) + .recordedBy(searchRequest.getRecordedBy()) + .identifiedBy(searchRequest.getIdentifiedBy()) + .issues(searchRequest.getIssues()) + .typeStatus(searchRequest.getTypeStatus()) + .page(page) + .build(); + } + + @Override + public long countDescriptors(DescriptorSearchRequest searchRequest) { + DescriptorParams params = createDescriptorParams(searchRequest); + return descriptorsMapper.countDescriptors(params); + } + + @Override + public Set getVerbatimNames(long descriptorGroupKey) { + List dtos = descriptorsMapper.getVerbatimNames(descriptorGroupKey); + return dtos.stream() + .sorted(Comparator.comparing(VerbatimDto::getKey)) + .map(VerbatimDto::getFieldName) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + private static Descriptor convertRecordDto(DescriptorDto dto) { + Descriptor descriptorRecord = new Descriptor(); + descriptorRecord.setKey(dto.getKey()); + descriptorRecord.setRecordedBy(dto.getRecordedBy()); + descriptorRecord.setDescriptorGroupKey(dto.getDescriptorGroupKey()); + descriptorRecord.setCountry(dto.getCountry()); + descriptorRecord.setDiscipline(dto.getDiscipline()); + descriptorRecord.setIssues(dto.getIssues()); + descriptorRecord.setDateIdentified(dto.getDateIdentified()); + descriptorRecord.setIdentifiedBy(dto.getIdentifiedBy()); + descriptorRecord.setIndividualCount(dto.getIndividualCount()); + descriptorRecord.setObjectClassification(dto.getObjectClassificationName()); + descriptorRecord.setTypeStatus(dto.getTypeStatus()); + descriptorRecord.setUsageKey(dto.getUsageKey()); + descriptorRecord.setUsageName(dto.getUsageName()); + descriptorRecord.setUsageRank(dto.getUsageRank()); + descriptorRecord.setTaxonClassification(dto.getTaxonClassification()); + + Map verbatim = new LinkedHashMap<>(); + dto.getVerbatim().stream() + .sorted(Comparator.comparing(VerbatimDto::getKey)) + .forEach(v -> verbatim.put(v.getFieldName(), v.getFieldValue())); + descriptorRecord.setVerbatim(verbatim); + return descriptorRecord; + } +} diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/InterpretedResult.java b/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/InterpretedResult.java new file mode 100644 index 0000000000..f211e607dd --- /dev/null +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/InterpretedResult.java @@ -0,0 +1,18 @@ +package org.gbif.registry.service.collections.descriptors; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class InterpretedResult { + + T result; + List issues; + + public static InterpretedResult empty() { + return new InterpretedResult<>(null, null); + } +} diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/Interpreter.java b/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/Interpreter.java new file mode 100644 index 0000000000..e04ec91b2b --- /dev/null +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/descriptors/Interpreter.java @@ -0,0 +1,388 @@ +package org.gbif.registry.service.collections.descriptors; + +import static org.gbif.api.vocabulary.Kingdom.INCERTAE_SEDIS; +import static org.gbif.api.vocabulary.OccurrenceIssue.*; + +import com.google.common.base.Strings; +import com.google.common.collect.Range; +import java.time.Instant; +import java.time.LocalDate; +import java.time.temporal.TemporalAccessor; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.gbif.api.model.checklistbank.NameUsage; +import org.gbif.api.model.checklistbank.NameUsageMatch; +import org.gbif.api.v2.NameUsageMatch2; +import org.gbif.api.v2.RankedName; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.OccurrenceIssue; +import org.gbif.api.vocabulary.Rank; +import org.gbif.api.vocabulary.TypeStatus; +import org.gbif.checklistbank.ws.client.NubResourceClient; +import org.gbif.common.parsers.CountryParser; +import org.gbif.common.parsers.NumberParser; +import org.gbif.common.parsers.TypeStatusParser; +import org.gbif.common.parsers.core.OccurrenceParseResult; +import org.gbif.common.parsers.core.ParseResult; +import org.gbif.common.parsers.date.MultiinputTemporalParser; +import org.gbif.dwc.terms.DwcTerm; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Interpreter { + + private static final String DEFAULT_SEPARATOR = "\\|"; + private static final LocalDate EARLIEST_DATE_IDENTIFIED = LocalDate.of(1753, 1, 1); + private static final Pattern INT_POSITIVE_PATTERN = Pattern.compile("(^\\d{1,10}$)"); + private static final MultiinputTemporalParser temporalParser = MultiinputTemporalParser.create(); + private static final CountryParser countryParser = CountryParser.getInstance(); + private static final TypeStatusParser typeStatusParser = TypeStatusParser.getInstance(); + + public static InterpretedResult> interpretStringList( + String[] values, Map headersByName, DwcTerm term) { + if (values.length == 0) { + return InterpretedResult.empty(); + } + + List verbatimValue = extractListValue(values, headersByName, term); + if (verbatimValue == null || verbatimValue.isEmpty()) { + return InterpretedResult.empty(); + } + + return InterpretedResult.>builder().result(verbatimValue).build(); + } + + public static InterpretedResult interpretString( + String[] values, Map headersByName, DwcTerm term) { + return interpretString(values, headersByName, term.prefixedName()); + } + + public static InterpretedResult interpretString( + String[] values, Map headersByName, String fieldName) { + if (values.length == 0) { + return InterpretedResult.empty(); + } + + String verbatimValue = extractValue(values, headersByName, fieldName); + if (Strings.isNullOrEmpty(verbatimValue)) { + return InterpretedResult.empty(); + } + + return InterpretedResult.builder().result(verbatimValue).build(); + } + + public static InterpretedResult> interpretTypeStatus( + String[] values, Map headersByName) { + if (values.length == 0) { + return InterpretedResult.empty(); + } + + List verbatimValue = extractListValue(values, headersByName, DwcTerm.typeStatus); + if (verbatimValue == null || verbatimValue.isEmpty()) { + return InterpretedResult.empty(); + } + + List results = new ArrayList<>(); + Set issues = new HashSet<>(); + verbatimValue.forEach( + v -> { + ParseResult parseResult = typeStatusParser.parse(v); + if (parseResult.isSuccessful()) { + results.add(parseResult.getPayload().name()); + } else { + issues.add(TYPE_STATUS_INVALID.getId()); + } + }); + + return InterpretedResult.>builder() + .result(results) + .issues(new ArrayList<>(issues)) + .build(); + } + + public static InterpretedResult interpretDateIdentified( + String[] values, Map headersByName) { + if (values.length == 0) { + return InterpretedResult.empty(); + } + + String verbatimDateIdentified = extractValue(values, headersByName, DwcTerm.dateIdentified); + if (Strings.isNullOrEmpty(verbatimDateIdentified)) { + return InterpretedResult.empty(); + } + + LocalDate upperBound = LocalDate.now().plusDays(1); + Range validRecordedDateRange = Range.closed(EARLIEST_DATE_IDENTIFIED, upperBound); + OccurrenceParseResult parsed = + temporalParser.parseLocalDate( + verbatimDateIdentified, + validRecordedDateRange, + OccurrenceIssue.IDENTIFIED_DATE_UNLIKELY, + OccurrenceIssue.IDENTIFIED_DATE_INVALID); + + InterpretedResult.InterpretedResultBuilder resultBuilder = + InterpretedResult.builder() + .issues( + parsed.getIssues().stream() + .map(OccurrenceIssue::getId) + .collect(Collectors.toList())); + + if (parsed.isSuccessful()) { + resultBuilder.result(new Date(Instant.from(parsed.getPayload()).toEpochMilli())); + } + + return resultBuilder.build(); + } + + public static InterpretedResult interpretIndividualCount( + String[] values, Map headersByName) { + if (values.length == 0) { + return InterpretedResult.empty(); + } + + String verbatimIndividualCount = extractValue(values, headersByName, DwcTerm.individualCount); + if (Strings.isNullOrEmpty(verbatimIndividualCount)) { + return InterpretedResult.empty(); + } + + boolean matches = INT_POSITIVE_PATTERN.matcher(verbatimIndividualCount).matches(); + if (!matches) { + return InterpretedResult.builder() + .issues(Collections.singletonList(INDIVIDUAL_COUNT_INVALID.getId())) + .build(); + } + + return InterpretedResult.builder() + .result(NumberParser.parseInteger(verbatimIndividualCount)) + .build(); + } + + public static InterpretedResult interpretCountry( + String[] values, Map headersByName) { + if (values.length == 0) { + return InterpretedResult.empty(); + } + + String verbatimCountry = extractValue(values, headersByName, DwcTerm.country); + String verbatimCountryCode = extractValue(values, headersByName, DwcTerm.countryCode); + + if (Strings.isNullOrEmpty(verbatimCountry) && Strings.isNullOrEmpty(verbatimCountryCode)) { + return InterpretedResult.empty(); + } + + Set issues = new HashSet<>(); + Country interpretedCountry = null; + if (!Strings.isNullOrEmpty(verbatimCountry)) { + ParseResult parseResult = countryParser.parse(verbatimCountry); + if (!parseResult.isSuccessful()) { + issues.add(COUNTRY_INVALID.getId()); + } else { + interpretedCountry = parseResult.getPayload(); + } + } + + Country interpretedCountryCode = null; + if (!Strings.isNullOrEmpty(verbatimCountryCode)) { + ParseResult parseResult = countryParser.parse(verbatimCountryCode); + if (!parseResult.isSuccessful()) { + issues.add(COUNTRY_INVALID.getId()); + } else { + interpretedCountryCode = parseResult.getPayload(); + } + } + + if (interpretedCountry != null + && interpretedCountryCode != null + && interpretedCountry != interpretedCountryCode) { + issues.add(COUNTRY_MISMATCH.getId()); + } + + InterpretedResult.InterpretedResultBuilder resultBuilder = + InterpretedResult.builder().issues(new ArrayList<>(issues)); + if (interpretedCountry != null) { + resultBuilder.result(interpretedCountry); + } else if (interpretedCountryCode != null) { + resultBuilder.result(interpretedCountryCode); + } + + return resultBuilder.build(); + } + + public static InterpretedResult interpretTaxonomy( + String[] values, Map headersByName, NubResourceClient nubResourceClient) { + if (values.length == 0) { + return InterpretedResult.empty(); + } + + String kingdom = extractValue(values, headersByName, DwcTerm.kingdom); + String phylum = extractValue(values, headersByName, DwcTerm.phylum); + String clazz = extractValue(values, headersByName, DwcTerm.class_); + String order = extractValue(values, headersByName, DwcTerm.order); + String family = extractValue(values, headersByName, DwcTerm.family); + String genus = extractValue(values, headersByName, DwcTerm.genus); + String scientificName = extractValue(values, headersByName, DwcTerm.scientificName); + String genericName = extractValue(values, headersByName, DwcTerm.genericName); + String specificEpithet = extractValue(values, headersByName, DwcTerm.specificEpithet); + String infraspecificEpithet = extractValue(values, headersByName, DwcTerm.infraspecificEpithet); + String scientificNameAuthorship = + extractValue(values, headersByName, DwcTerm.scientificNameAuthorship); + String taxonRank = extractValue(values, headersByName, DwcTerm.taxonRank); + String taxonID = extractValue(values, headersByName, DwcTerm.taxonID); + + if (Stream.of( + kingdom, + phylum, + clazz, + order, + family, + genus, + scientificName, + genericName, + specificEpithet, + infraspecificEpithet, + scientificNameAuthorship, + taxonRank, + taxonID) + .allMatch(Strings::isNullOrEmpty)) { + return InterpretedResult.empty(); + } + + NameUsage nameUsageParam = new NameUsage(); + nameUsageParam.setKingdom(kingdom); + nameUsageParam.setPhylum(phylum); + nameUsageParam.setClazz(clazz); + nameUsageParam.setOrder(order); + nameUsageParam.setFamily(family); + nameUsageParam.setGenus(genus); + nameUsageParam.setTaxonID(taxonID); + + // TODO: also do backbone match by ID?? + NameUsageMatch2 nameUsageMatch = + nubResourceClient.match2( + scientificName, + scientificNameAuthorship, + taxonRank, + genericName, + specificEpithet, + infraspecificEpithet, + nameUsageParam, + false, + false); + + if (nameUsageMatch == null + || isEmptyResponse(nameUsageMatch) + || checkFuzzy(nameUsageMatch, nameUsageParam)) { + + return InterpretedResult.builder() + .result( + TaxonData.builder() + .usageName(INCERTAE_SEDIS.scientificName()) + .usageKey(INCERTAE_SEDIS.nubUsageKey()) + .build()) + .issues(Collections.singletonList(TAXON_MATCH_NONE.getId())) + .build(); + } else { + NameUsageMatch.MatchType matchType = nameUsageMatch.getDiagnostics().getMatchType(); + + List issues = new ArrayList<>(); + if (NameUsageMatch.MatchType.NONE == matchType) { + issues.add(TAXON_MATCH_NONE.getId()); + } else if (NameUsageMatch.MatchType.FUZZY == matchType) { + issues.add(TAXON_MATCH_FUZZY.getId()); + } else if (NameUsageMatch.MatchType.HIGHERRANK == matchType) { + issues.add(TAXON_MATCH_HIGHERRANK.getId()); + } + + TaxonData.TaxonDataBuilder taxonDataBuilder = TaxonData.builder(); + Set taxonKeys = new HashSet<>(); + taxonDataBuilder.taxonKeys(taxonKeys); + if (nameUsageMatch.getUsage() != null) { + taxonDataBuilder + .usageName(nameUsageMatch.getUsage().getName()) + .usageKey(nameUsageMatch.getUsage().getKey()) + .usageRank(nameUsageMatch.getUsage().getRank()); + taxonKeys.add(nameUsageMatch.getUsage().getKey()); + } + + if (nameUsageMatch.getClassification() != null) { + List rankedNames = new ArrayList<>(); + taxonDataBuilder.taxonClassification(rankedNames); + nameUsageMatch + .getClassification() + .forEach( + c -> { + rankedNames.add(c); + taxonKeys.add(c.getKey()); + }); + } + + return InterpretedResult.builder() + .result(taxonDataBuilder.build()) + .issues(issues) + .build(); + } + } + + private static String extractValue( + String[] values, Map headersByName, DwcTerm term) { + return extractValue(values, headersByName, term.prefixedName()); + } + + private static String extractValue( + String[] values, Map headersByName, String fieldName) { + return Optional.ofNullable(headersByName.get(fieldName.toLowerCase())) + .map(i -> values[i]) + .filter(v -> !v.isEmpty()) + .orElse(null); + } + + private static Optional extractOptValue( + String[] values, Map headersByName, DwcTerm term) { + return Optional.ofNullable(headersByName.get(term.prefixedName().toLowerCase())) + .map(i -> values[i]) + .filter(v -> !v.isEmpty()); + } + + private static List extractListValue( + String[] values, Map headersByName, DwcTerm term) { + return extractOptValue(values, headersByName, term) + .map( + x -> + Arrays.stream(x.split(DEFAULT_SEPARATOR)) + .map(String::trim) + .filter(v -> !v.isEmpty()) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } + + private static boolean isEmptyResponse(NameUsageMatch2 response) { + return response == null || response.getUsage() == null || response.getDiagnostics() == null; + } + + private static boolean checkFuzzy(NameUsageMatch2 usageMatch, NameUsage nameUsageParam) { + boolean isFuzzy = NameUsageMatch.MatchType.FUZZY == usageMatch.getDiagnostics().getMatchType(); + boolean isEmptyTaxa = + Strings.isNullOrEmpty(nameUsageParam.getKingdom()) + && Strings.isNullOrEmpty(nameUsageParam.getPhylum()) + && Strings.isNullOrEmpty(nameUsageParam.getClazz()) + && Strings.isNullOrEmpty(nameUsageParam.getOrder()) + && Strings.isNullOrEmpty(nameUsageParam.getFamily()); + return isFuzzy && isEmptyTaxa; + } + + @Data + @Builder + static class TaxonData { + private Integer usageKey; + private String usageName; + private Rank usageRank; + private List taxonClassification; + + private Set taxonKeys; + } +} diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/BaseChangeSuggestionService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/BaseChangeSuggestionService.java index b8331411b0..6f58c23c0c 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/BaseChangeSuggestionService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/BaseChangeSuggestionService.java @@ -20,6 +20,7 @@ import org.gbif.api.model.collections.*; import org.gbif.api.model.collections.suggestions.Change; import org.gbif.api.model.collections.suggestions.ChangeSuggestion; +import org.gbif.api.model.collections.suggestions.CollectionChangeSuggestion; import org.gbif.api.model.collections.suggestions.Status; import org.gbif.api.model.collections.suggestions.Type; import org.gbif.api.model.common.GbifUser; @@ -261,6 +262,10 @@ protected ChangeSuggestionDto createNewEntitySuggestionDto(R changeSuggestion) { dto.setChanges( extractChanges(changeSuggestion.getSuggestedEntity(), createEmptyEntityInstance())); dto.setCountryScope(getCountry(changeSuggestion.getSuggestedEntity())); + if (changeSuggestion instanceof CollectionChangeSuggestion) { + dto.setCreateInstitution(((CollectionChangeSuggestion) changeSuggestion).getCreateInstitution()); + dto.setIhIdentifier(((CollectionChangeSuggestion) changeSuggestion).getIhIdentifier()); + } return dto; } @@ -483,12 +488,13 @@ public PagingResponse list( @Nullable Type type, @Nullable String proposerEmail, @Nullable UUID entityKey, + @Nullable String ihIdentifier, @Nullable Pageable pageable) { Pageable page = pageable == null ? new PagingRequest() : pageable; List dtos = changeSuggestionMapper.list( - status, type, collectionEntityType, proposerEmail, entityKey, page); + status, type, collectionEntityType, proposerEmail, entityKey, ihIdentifier, page); long count = changeSuggestionMapper.count(status, type, collectionEntityType, proposerEmail, entityKey); @@ -801,7 +807,7 @@ protected String toJson(T entity) { } } - private S readJson(String content, Class clazz) { + protected S readJson(String content, Class clazz) { try { return objectMapper.readValue(content, clazz); } catch (JsonProcessingException e) { @@ -838,4 +844,8 @@ private boolean isEmptyAddress(Address address) { protected abstract ChangeSuggestionDto createConvertToCollectionSuggestionDto(R changeSuggestion); protected abstract UUID applyConversionToCollection(ChangeSuggestionDto dto); + + protected static String decodeIRN(String irn) { + return irn.replace("gbif:ih:irn:", ""); + } } diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/CollectionChangeSuggestionService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/CollectionChangeSuggestionService.java index e6cacb5baf..b0280d0050 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/CollectionChangeSuggestionService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/CollectionChangeSuggestionService.java @@ -15,9 +15,15 @@ import org.gbif.api.model.collections.Collection; import org.gbif.api.model.collections.CollectionEntityType; +import org.gbif.api.model.collections.Institution; +import org.gbif.api.model.collections.MasterSourceMetadata; import org.gbif.api.model.collections.suggestions.CollectionChangeSuggestion; import org.gbif.api.model.collections.suggestions.Type; +import org.gbif.api.model.registry.Identifier; import org.gbif.api.service.collections.CollectionService; +import org.gbif.api.service.collections.InstitutionService; +import org.gbif.api.vocabulary.IdentifierType; +import org.gbif.api.vocabulary.collections.Source; import org.gbif.registry.events.EventManager; import org.gbif.registry.mail.EmailSender; import org.gbif.registry.mail.collections.CollectionsEmailManager; @@ -45,6 +51,8 @@ public class CollectionChangeSuggestionService extends BaseChangeSuggestionService { private final ChangeSuggestionMapper changeSuggestionMapper; + private final InstitutionService institutionService; + private final CollectionService collectionService; @Autowired public CollectionChangeSuggestionService( @@ -57,7 +65,8 @@ public CollectionChangeSuggestionService( CollectionsEmailManager emailManager, EventManager eventManager, GrSciCollAuthorizationService grSciCollAuthorizationService, - CollectionsMailConfigurationProperties collectionsMailConfigurationProperties) { + CollectionsMailConfigurationProperties collectionsMailConfigurationProperties, + InstitutionService institutionService) { super( changeSuggestionMapper, collectionMergeService, @@ -72,6 +81,8 @@ public CollectionChangeSuggestionService( grSciCollAuthorizationService, collectionsMailConfigurationProperties); this.changeSuggestionMapper = changeSuggestionMapper; + this.institutionService = institutionService; + this.collectionService = collectionService; } @Override @@ -82,6 +93,32 @@ public int createChangeSuggestion(CollectionChangeSuggestion changeSuggestion) { return super.createChangeSuggestion(changeSuggestion); } + @Override + public UUID applyChangeSuggestion(int suggestionKey){ + ChangeSuggestionDto dto = changeSuggestionMapper.get(suggestionKey); + if (dto.getType() == Type.CREATE) { + if (Boolean.TRUE.equals(dto.getCreateInstitution())) { + UUID createdInstitution = createInstitutionForCollectionSuggestion(dto); + Collection suggestedCollection = readJson(dto.getSuggestedEntity(), Collection.class); + suggestedCollection.setInstitutionKey(createdInstitution); + dto.setSuggestedEntity(toJson(suggestedCollection)); + + changeSuggestionMapper.update(dto); + } + } + + UUID createdEntity = super.applyChangeSuggestion(suggestionKey); + + if (dto.getIhIdentifier() != null){ + collectionService.addIdentifier(createdEntity,new Identifier(IdentifierType.IH_IRN, + dto.getIhIdentifier())); + collectionService.addMasterSourceMetadata(createdEntity, new MasterSourceMetadata( + Source.IH_IRN, decodeIRN(dto.getIhIdentifier()))); + } + + return createdEntity; + } + @Override public CollectionChangeSuggestion getChangeSuggestion(int key) { ChangeSuggestionDto dto = changeSuggestionMapper.get(key); @@ -89,8 +126,10 @@ public CollectionChangeSuggestion getChangeSuggestion(int key) { if (dto == null || dto.getEntityType() != CollectionEntityType.COLLECTION) { return null; } - - return dtoToChangeSuggestion(dto); + CollectionChangeSuggestion changeSuggestion = dtoToChangeSuggestion(dto); + changeSuggestion.setCreateInstitution(dto.getCreateInstitution()); + changeSuggestion.setIhIdentifier(dto.getIhIdentifier()); + return changeSuggestion; } @Override @@ -108,4 +147,60 @@ protected ChangeSuggestionDto createConvertToCollectionSuggestionDto( protected UUID applyConversionToCollection(ChangeSuggestionDto dto) { throw new UnsupportedOperationException(); } + + public UUID createInstitutionForCollectionSuggestion(ChangeSuggestionDto dto){ + CollectionChangeSuggestion changeSuggestion = dtoToChangeSuggestion(dto); + UUID createdEntity = null; + if (dto.getType() == Type.CREATE) { + if (Boolean.TRUE.equals(dto.getCreateInstitution())) { + Institution institution = collectionChangeSuggestionToInstitution(dto); + + createdEntity = institutionService.create(institution); + institutionService.addIdentifier(createdEntity, new Identifier(IdentifierType.IH_IRN, + dto.getIhIdentifier())); + institutionService.addMasterSourceMetadata(createdEntity, new MasterSourceMetadata( + Source.IH_IRN, decodeIRN(dto.getIhIdentifier()))); + createContacts(changeSuggestion,createdEntity); + } + } + return createdEntity; + } + + private Institution collectionChangeSuggestionToInstitution(ChangeSuggestionDto dto) { + Institution institution = new Institution(); + + if (dto.getSuggestedEntity() != null) { + Collection collection = readJson(dto.getSuggestedEntity(), Collection.class); + + String name = collection.getName(); + if(name.startsWith("Herbarium - ")) { + name = name.substring("Herbarium - ".length()); + } + institution.setName(name); + institution.setCode(collection.getCode()); + institution.setActive(collection.isActive()); + institution.setAddress(collection.getAddress()); + institution.setEmail(collection.getEmail()); + institution.setPhone(collection.getPhone()); + institution.setComments(collection.getComments()); + institution.setMasterSourceMetadata(collection.getMasterSourceMetadata()); + institution.setMasterSource(collection.getMasterSource()); + institution.setContactPersons(collection.getContactPersons()); + + institution.setDescription(collection.getDescription()); + + } + + return institution; + } + + private void createContacts(CollectionChangeSuggestion changeSuggestion, UUID createdEntity) { + if (changeSuggestion.getSuggestedEntity().getContactPersons() != null + && !changeSuggestion.getSuggestedEntity().getContactPersons().isEmpty()) { + changeSuggestion + .getSuggestedEntity() + .getContactPersons() + .forEach(c -> institutionService.addContactPerson(createdEntity, c)); + } + } } diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/InstitutionChangeSuggestionService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/InstitutionChangeSuggestionService.java index cccfab92c5..3539a1ead4 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/InstitutionChangeSuggestionService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/suggestions/InstitutionChangeSuggestionService.java @@ -158,4 +158,5 @@ public void fix() { throw new RuntimeException(e); } } + } diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/utils/ParamUtils.java b/registry-service/src/main/java/org/gbif/registry/service/collections/utils/ParamUtils.java new file mode 100644 index 0000000000..918e2caf3d --- /dev/null +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/utils/ParamUtils.java @@ -0,0 +1,61 @@ +package org.gbif.registry.service.collections.utils; + +import static org.gbif.registry.service.collections.utils.SearchUtils.INTEGER_RANGE; +import static org.gbif.registry.service.collections.utils.SearchUtils.WILDCARD_SEARCH; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.common.Strings; +import org.gbif.api.model.collections.request.SearchRequest; +import org.gbif.api.vocabulary.Country; +import org.gbif.registry.persistence.mapper.collections.params.RangeParam; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ParamUtils { + + public static RangeParam parseIntegerRangeParameter(String param) { + if (Strings.isNullOrEmpty(param)) { + return null; + } + + RangeParam rangeParam = new RangeParam(); + Matcher matcher = INTEGER_RANGE.matcher(param); + if (matcher.matches()) { + String lowerString = matcher.group(1); + if (!lowerString.equals(WILDCARD_SEARCH)) { + rangeParam.setLowerBound(Integer.valueOf(lowerString)); + } + + String higherString = matcher.group(2); + if (!higherString.equals(WILDCARD_SEARCH)) { + rangeParam.setHigherBound(Integer.valueOf(higherString)); + } + } else { + try { + rangeParam.setExactValue(Integer.valueOf(param)); + } catch (Exception ex) { + log.info("Invalid range {}", param, ex); + } + } + + return rangeParam; + } + + public static List parseGbifRegion(SearchRequest searchRequest) { + List countries = new ArrayList<>(); + if (searchRequest.getGbifRegion() != null && !searchRequest.getGbifRegion().isEmpty()) { + countries.addAll( + Arrays.stream(Country.values()) + .filter(c -> searchRequest.getGbifRegion().contains(c.getGbifRegion())) + .collect(Collectors.toList())); + } + return countries; + } +} diff --git a/registry-service/src/test/java/org/gbif/registry/service/collections/lookup/matchers/MatchersTest.java b/registry-service/src/test/java/org/gbif/registry/service/collections/lookup/matchers/MatchersTest.java index 309b1a2be7..14871ba95f 100644 --- a/registry-service/src/test/java/org/gbif/registry/service/collections/lookup/matchers/MatchersTest.java +++ b/registry-service/src/test/java/org/gbif/registry/service/collections/lookup/matchers/MatchersTest.java @@ -15,6 +15,8 @@ import java.util.UUID; +import org.gbif.dwc.terms.DwcTerm; + import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,6 +26,8 @@ public class MatchersTest { @Test public void parseUUIDTest() { + System.out.println(DwcTerm.kingdom.qualifiedName()); + assertEquals( UUID.fromString("685298f2-0bc1-4e44-b2d5-d9760574644e"), BaseMatcher.parseUUID( @@ -36,7 +40,7 @@ public void parseUUIDTest() { BaseMatcher.parseUUID( "https://www.gbif.org/grscicoll/collection/685298f2-bc1-4e44-b2d5-d9760574644e")); assertNull( - BaseMatcher.parseUUID( - "https://scientific-collections.gbif.org/institution/685298f2-bc1-4e44-b2d5-d9760574644e")); + BaseMatcher.parseUUID( + "https://scientific-collections.gbif.org/institution/685298f2-bc1-4e44-b2d5-d9760574644e")); } } diff --git a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/OrganizationClient.java b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/OrganizationClient.java index 745bbb17b1..dbe44a2ce2 100644 --- a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/OrganizationClient.java +++ b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/OrganizationClient.java @@ -26,9 +26,11 @@ import java.util.List; import java.util.UUID; +import org.geojson.FeatureCollection; import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -122,4 +124,9 @@ boolean confirmEndorsement( @ResponseBody @Override PagingResponse list(@SpringQueryMap OrganizationRequestSearchParams searchParams); + + @GetMapping(value = "geojson", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @Override + FeatureCollection listGeoJson(@SpringQueryMap OrganizationRequestSearchParams searchParams); } diff --git a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionClient.java b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionClient.java index b2745c79e9..cc5d96b6f7 100644 --- a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionClient.java +++ b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionClient.java @@ -13,20 +13,26 @@ */ package org.gbif.registry.ws.client.collections; +import java.util.List; +import java.util.UUID; +import org.gbif.api.annotation.Trim; import org.gbif.api.model.collections.Collection; import org.gbif.api.model.collections.CollectionImportParams; +import org.gbif.api.model.collections.descriptors.Descriptor; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; import org.gbif.api.model.collections.latimercore.ObjectGroup; import org.gbif.api.model.collections.request.CollectionSearchRequest; +import org.gbif.api.model.collections.request.DescriptorGroupSearchRequest; +import org.gbif.api.model.collections.request.DescriptorSearchRequest; import org.gbif.api.model.collections.suggestions.CollectionChangeSuggestion; import org.gbif.api.model.collections.view.CollectionView; +import org.gbif.api.model.common.export.ExportFormat; import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.registry.search.collections.KeyCodeNameResult; import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.UUID; +import org.springframework.web.multipart.MultipartFile; @RequestMapping("grscicoll/collection") public interface CollectionClient @@ -96,4 +102,60 @@ default Collection get(UUID key) { produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) UUID createFromDataset(@RequestBody CollectionImportParams importParams); + + @PostMapping( + value = "{collectionKey}/descriptorGroup", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + long createDescriptorGroup( + @PathVariable("collectionKey") UUID collectionKey, + @RequestParam(value = "format", defaultValue = "CSV") ExportFormat format, + @RequestPart("descriptorsFile") MultipartFile descriptorsFile, + @RequestParam("title") @Trim String title, + @RequestParam(value = "description", required = false) @Trim String description); + + @PutMapping( + value = "{collectionKey}/descriptorGroup/{key}", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + void updateDescriptorGroup( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey, + @RequestParam(value = "format", defaultValue = "CSV") ExportFormat format, + @RequestPart("descriptorsFile") MultipartFile descriptorsFile, + @RequestParam("title") @Trim String title, + @RequestParam(value = "description", required = false) @Trim String description); + + @GetMapping( + value = "{collectionKey}/descriptorGroup/{key}", + produces = MediaType.APPLICATION_JSON_VALUE) + DescriptorGroup getCollectionDescriptorGroup( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey); + + @DeleteMapping(value = "{collectionKey}/descriptorGroup/{key}") + void deleteCollectionDescriptorGroup( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey); + + @GetMapping( + value = "{collectionKey}/descriptorGroup", + produces = MediaType.APPLICATION_JSON_VALUE) + PagingResponse listCollectionDescriptorGroups( + @PathVariable("collectionKey") UUID collectionKey, + @SpringQueryMap DescriptorGroupSearchRequest searchRequest); + + @GetMapping( + value = "{collectionKey}/descriptorGroup/{key}/descriptor", + produces = MediaType.APPLICATION_JSON_VALUE) + PagingResponse listCollectionDescriptors( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey, + @SpringQueryMap DescriptorSearchRequest searchRequest); + + @GetMapping( + value = "{collectionKey}/descriptorGroup/{descriptorGroupKey}/descriptor/{key}", + produces = MediaType.APPLICATION_JSON_VALUE) + Descriptor getCollectionDescriptor( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("descriptorGroupKey") long descriptorGroupKey, + @PathVariable("key") long descriptorKey); } diff --git a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionsSearchClient.java b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionsSearchClient.java index 22222be6a9..3aaffc4376 100644 --- a/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionsSearchClient.java +++ b/registry-ws-client/src/main/java/org/gbif/registry/ws/client/collections/CollectionsSearchClient.java @@ -13,34 +13,54 @@ */ package org.gbif.registry.ws.client.collections; -import org.gbif.api.model.collections.search.CollectionsSearchResponse; +import java.util.List; +import lombok.AllArgsConstructor; +import org.gbif.api.model.collections.request.CollectionDescriptorsSearchRequest; +import org.gbif.api.model.collections.request.InstitutionSearchRequest; +import org.gbif.api.model.collections.search.CollectionSearchResponse; +import org.gbif.api.model.collections.search.CollectionsFullSearchResponse; +import org.gbif.api.model.collections.search.InstitutionSearchResponse; +import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.vocabulary.Country; import org.gbif.registry.domain.collections.TypeParam; - -import java.util.List; - import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -import lombok.AllArgsConstructor; - -@RequestMapping("grscicoll/search") +@RequestMapping("grscicoll") public interface CollectionsSearchClient { - @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - List searchCollections(@SpringQueryMap SearchRequest searchRequest); + @RequestMapping( + value = "search", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE) + List searchCrossEntities( + @SpringQueryMap SearchRequest searchRequest); + + @RequestMapping( + value = "institution/search", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE) + PagingResponse searchInstitutions( + @SpringQueryMap InstitutionSearchRequest searchRequest); + + @RequestMapping( + value = "collection/search", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE) + PagingResponse searchCollections( + @SpringQueryMap CollectionDescriptorsSearchRequest searchRequest); - default List searchCollections( + default List searchCrossEntities( @RequestParam(value = "q", required = false) String query, @RequestParam(value = "hl", defaultValue = "false") boolean highlight, @RequestParam(value = "entityType", required = false) TypeParam type, @RequestParam(value = "displayOnNHCPortal", required = false) Boolean displayOnNHCPortal, @SpringQueryMap Country country, @RequestParam(value = "limit", defaultValue = "20") int limit) { - return searchCollections( + return searchCrossEntities( SearchRequest.of(query, highlight, type, displayOnNHCPortal, country, limit)); } diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/config/WebMvcConfig.java b/registry-ws/src/main/java/org/gbif/registry/ws/config/WebMvcConfig.java index 10f01e58a2..fb65b86915 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/config/WebMvcConfig.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/config/WebMvcConfig.java @@ -19,9 +19,12 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; +import java.util.*; +import org.gbif.checklistbank.ws.client.NubResourceClient; import org.gbif.registry.domain.ws.*; import org.gbif.registry.security.precheck.AuthPreCheckInterceptor; import org.gbif.registry.ws.converter.UuidTextMessageConverter; +import org.gbif.registry.ws.provider.CollectionDescriptorsSearchRequestHandlerMethodArgumentResolver; import org.gbif.registry.ws.provider.CollectionSearchRequestHandlerMethodArgumentResolver; import org.gbif.registry.ws.provider.InstitutionSearchRequestHandlerMethodArgumentResolver; import org.gbif.registry.ws.provider.PartialDateHandlerMethodArgumentResolver; @@ -49,8 +52,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; -import java.util.*; - @Configuration public class WebMvcConfig implements WebMvcConfigurer { @@ -73,6 +74,7 @@ public void addArgumentResolvers(List argumentRes argumentResolvers.add(new NodeRequestSearchParamsHandlerMethodArgumentResolver()); argumentResolvers.add(new InstitutionSearchRequestHandlerMethodArgumentResolver()); argumentResolvers.add(new CollectionSearchRequestHandlerMethodArgumentResolver()); + argumentResolvers.add(new CollectionDescriptorsSearchRequestHandlerMethodArgumentResolver()); } @Override @@ -176,4 +178,14 @@ public ConceptClient conceptClient(@Value("${api.root.url}") String apiRootUrl) .withUrl(apiRootUrl) .build(ConceptClient.class); } + + @Bean + public NubResourceClient nubResourceClient(@Value("${api.root.url}") String apiRootUrl) { + return new ClientBuilder() + .withObjectMapper( + JacksonJsonObjectMapperProvider.getObjectMapperWithBuilderSupport() + .registerModule(new JavaTimeModule())) + .withUrl(apiRootUrl) + .build(NubResourceClient.class); + } } diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/export/CsvWriter.java b/registry-ws/src/main/java/org/gbif/registry/ws/export/CsvWriter.java index d317c18a59..4cc71388ba 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/export/CsvWriter.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/export/CsvWriter.java @@ -13,43 +13,46 @@ */ package org.gbif.registry.ws.export; +import com.fasterxml.jackson.databind.util.StdDateFormat; +import java.io.Writer; +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import lombok.Builder; +import lombok.Data; +import lombok.SneakyThrows; import org.gbif.api.model.collections.*; import org.gbif.api.model.collections.Address; import org.gbif.api.model.collections.Contact; +import org.gbif.api.model.collections.descriptors.Descriptor; import org.gbif.api.model.collections.view.CollectionView; import org.gbif.api.model.common.export.ExportFormat; import org.gbif.api.model.occurrence.DownloadStatistics; import org.gbif.api.model.registry.*; import org.gbif.api.model.registry.search.DatasetSearchResult; +import org.gbif.api.v2.RankedName; import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.DatasetSubtype; import org.gbif.api.vocabulary.DatasetType; import org.gbif.api.vocabulary.License; +import org.gbif.api.vocabulary.Rank; import org.gbif.api.vocabulary.collections.MasterSourceType; - -import java.io.Writer; -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - import org.supercsv.cellprocessor.*; import org.supercsv.cellprocessor.ift.CellProcessor; import org.supercsv.io.CsvBeanWriter; +import org.supercsv.io.CsvMapWriter; import org.supercsv.io.ICsvBeanWriter; +import org.supercsv.io.ICsvMapWriter; import org.supercsv.io.dozer.CsvDozerBeanWriter; import org.supercsv.prefs.CsvPreference; import org.supercsv.util.CsvContext; -import com.fasterxml.jackson.databind.util.StdDateFormat; - -import lombok.Builder; -import lombok.Data; -import lombok.SneakyThrows; - @Data @Builder public class CsvWriter { @@ -65,6 +68,8 @@ public class CsvWriter { private final Iterable pager; + private final Iterable> mapPager; + private final ExportFormat preference; // Use dozer if set to true. @@ -340,7 +345,7 @@ public static CsvWriter collections( new Optional(new ParseEnum(License.class)), // featuredImageLicense: License new CleanStringProcessor(), // temporalCoverage: String new Optional(new ParseEnum(MasterSourceType.class)), // masterSourceType: MasterSource - new Optional(new FmtBool("true", "false")) //displayOnNHCPortal: boolean + new Optional(new FmtBool("true", "false")) // displayOnNHCPortal: boolean }) .forClass(CollectionView.class) .preference(preference) @@ -348,8 +353,7 @@ public static CsvWriter collections( .build(); } - /** Creates an CsvWriter/exporter of Collection. */ - // TODO: processor for new multivalue fields + /** Creates an CsvWriter/exporter of Institution. */ public static CsvWriter institutions( Iterable pager, ExportFormat preference) { return CsvWriter.builder() @@ -487,13 +491,89 @@ public static CsvWriter institutions( new UriProcessor(), // featuredImageUrl : URI new Optional(new ParseEnum(License.class)), // featuredImageLicense: License new Optional(new ParseEnum(MasterSourceType.class)), // masterSourceType: MasterSource - new Optional(new FmtBool("true", "false")) //displayOnNHCPortal: boolean + new Optional(new FmtBool("true", "false")) // displayOnNHCPortal: boolean + }) + .preference(preference) + .pager(pager) + .build(); + } + + /** Creates an CsvWriter/exporter of Descriptor. */ + public static CsvWriter descriptors( + Iterable pager, ExportFormat preference) { + return CsvWriter.builder() + .fields( + new String[] { + "key", + "descriptorGroupKey", + "usageKey", + "usageName", + "usageRank", + "country", + "individualCount", + "identifiedBy", + "dateIdentified", + "typeStatus", + "recordedBy", + "discipline", + "objectClassification", + "taxonClassification", + "issues" + }) + .header( + new String[] { + "key", + "descriptor_group_key", + "usage_key", + "usage_name", + "usage_rank", + "country", + "individual_count", + "identified_by", + "date_identified", + "type_status", + "recorded_by", + "discipline", + "object_classification", + "taxon_classification", + "issues" + }) + .processors( + new CellProcessor[] { + new ParseLong(), // key: long + new ParseLong(), // descriptorGroupKey: long + new Optional(new ParseInt()), // usageKey: int + new CleanStringProcessor(), // usageName: String + new Optional(new ParseEnum(Rank.class)), // usageRank: Rank + new CountryProcessor(), // country + new Optional(new ParseInt()), // individualCount: int + new Optional(new ListStringProcessor()), // identifiedBy + new Optional(new FmtDate(StdDateFormat.DATE_FORMAT_STR_ISO8601)), // dateIdentified + new Optional(new ListStringProcessor()), // typeStatus: List + new Optional(new ListStringProcessor()), // recordedBy: List + new Optional(new CleanStringProcessor()), // discipline + new Optional(new CleanStringProcessor()), // objectClassification + new Optional(new ListRankedNameProcessor()), // taxonClassification + new Optional(new ListStringProcessor()) // issues }) .preference(preference) .pager(pager) .build(); } + /** Creates an CsvWriter/exporter of Descriptor verbatim fields. */ + public static CsvWriter descriptorVerbatims( + Iterable> pager, ExportFormat preference, Set headers) { + return CsvWriter.builder() + .fields(headers.toArray(new String[0])) + .header(headers.toArray(new String[0])) + .processors( + headers.stream().map(h -> new CleanStringProcessor()).toArray(CellProcessor[]::new)) + .preference(preference) + .mapPager(pager) + .build(); + } + /** Joins elements using as a delimiter. */ public static String notNullJoiner(String delimiter, String... elements) { return Arrays.stream(elements) @@ -510,6 +590,16 @@ private CsvPreference csvPreference() { throw new IllegalArgumentException("Export format not supported " + preference); } + @SneakyThrows + public void exportMap(Writer writer) { + try (ICsvMapWriter mapWriter = new CsvMapWriter(writer, csvPreference())) { + mapWriter.writeHeader(header); + for (Map o : mapPager) { + mapWriter.write(o, fields, processors); + } + } + } + @SneakyThrows public void export(Writer writer) { if (forClass != null) { @@ -796,4 +886,28 @@ public String execute(Object value, CsvContext csvContext) { return value != null ? toString((List) value) : ""; } } + + /** Null aware List processor. */ + public static class ListRankedNameProcessor implements CellProcessor { + + public static String toString(List value) { + return value.stream() + .filter(Objects::nonNull) + .map(ListRankedNameProcessor::toString) + .collect(Collectors.joining(ARRAY_DELIMITER)); + } + + public static String toString(RankedName rankedName) { + return notNullJoiner( + ":", + String.valueOf(rankedName.getKey()), + rankedName.getName(), + rankedName.getRank() != null ? rankedName.getRank().name() : null); + } + + @Override + public String execute(Object value, CsvContext csvContext) { + return value != null ? toString((List) value) : ""; + } + } } 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 861441961d..f26ea10351 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 @@ -14,6 +14,8 @@ package org.gbif.registry.ws.provider; import com.google.common.base.Strings; +import java.util.ArrayList; +import java.util.UUID; import org.gbif.api.model.collections.request.SearchRequest; import org.gbif.api.model.common.paging.Pageable; import org.gbif.api.util.VocabularyUtils; @@ -25,9 +27,6 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import java.util.ArrayList; -import java.util.UUID; - public abstract class BaseGrSciCollSearchRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @@ -40,6 +39,15 @@ protected void fillSearchRequestParams( request.setLimit(page.getLimit()); request.setOffset(page.getOffset()); + String hl = webRequest.getParameter("hl"); + if (!Strings.isNullOrEmpty(hl)) { + try { + request.setHl(Boolean.parseBoolean(hl)); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid boolean for hl: " + hl); + } + } + request.setAlternativeCode(webRequest.getParameter("alternativeCode")); request.setCode(webRequest.getParameter("code")); request.setName(webRequest.getParameter("name")); @@ -181,7 +189,7 @@ protected void fillSearchRequestParams( } } - private static void validateIntegerRange(String param, String paramName) { + protected static void validateIntegerRange(String param, String paramName) { boolean rangeMatch = SearchUtils.INTEGER_RANGE.matcher(param).find(); boolean numberMatch = true; try { diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/provider/CollectionDescriptorsSearchRequestHandlerMethodArgumentResolver.java b/registry-ws/src/main/java/org/gbif/registry/ws/provider/CollectionDescriptorsSearchRequestHandlerMethodArgumentResolver.java new file mode 100644 index 0000000000..8112161e3d --- /dev/null +++ b/registry-ws/src/main/java/org/gbif/registry/ws/provider/CollectionDescriptorsSearchRequestHandlerMethodArgumentResolver.java @@ -0,0 +1,158 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gbif.registry.ws.provider; + +import com.google.common.base.Strings; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import org.gbif.api.model.collections.request.CollectionDescriptorsSearchRequest; +import org.gbif.api.util.SearchTypeValidator; +import org.gbif.api.util.VocabularyUtils; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Rank; +import org.gbif.api.vocabulary.TypeStatus; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +@SuppressWarnings("NullableProblems") +public class CollectionDescriptorsSearchRequestHandlerMethodArgumentResolver + extends CollectionSearchRequestHandlerMethodArgumentResolver { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return CollectionDescriptorsSearchRequest.class.equals(parameter.getParameterType()); + } + + @Override + public Object resolveArgument( + MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) { + + CollectionDescriptorsSearchRequest searchRequest = + CollectionDescriptorsSearchRequest.builder().build(); + fillCollectionSearchRequest(searchRequest, webRequest); + + String[] usageKeys = webRequest.getParameterValues("usageKey"); + if (usageKeys != null && usageKeys.length > 0) { + searchRequest.setUsageKey(new ArrayList<>()); + for (String keyParam : usageKeys) { + try { + searchRequest.getUsageKey().add(Integer.parseInt(keyParam)); + } catch (Exception ex) { + throw new IllegalArgumentException( + "Invalid integer for usage key parameter: " + keyParam); + } + } + } + + String[] usageNames = webRequest.getParameterValues("usageName"); + if (usageNames != null && usageNames.length > 0) { + searchRequest.setUsageName(Arrays.asList(usageNames)); + } + + String[] usageRanks = webRequest.getParameterValues("usageRank"); + if (usageRanks != null && usageRanks.length > 0) { + searchRequest.setUsageRank(new ArrayList<>()); + for (String param : usageRanks) { + try { + searchRequest.getUsageRank().add(Rank.valueOf(param)); + } catch (Exception ex) { + throw new IllegalArgumentException("Invalid rank for usage rank parameter: " + param); + } + } + } + + String[] taxonKeys = webRequest.getParameterValues("taxonKey"); + if (taxonKeys != null && taxonKeys.length > 0) { + searchRequest.setTaxonKey(new ArrayList<>()); + for (String keyParam : taxonKeys) { + try { + searchRequest.getTaxonKey().add(Integer.parseInt(keyParam)); + } catch (Exception ex) { + throw new IllegalArgumentException( + "Invalid integer for taxon key parameter: " + keyParam); + } + } + } + + String[] descriptorCountries = webRequest.getParameterValues("descriptorCountry"); + if (descriptorCountries != null && descriptorCountries.length > 0) { + searchRequest.setDescriptorCountry(new ArrayList<>()); + for (String countryParam : descriptorCountries) { + Country country = Country.fromIsoCode(countryParam); + if (country == null) { + // if nothing found also try by enum name + country = VocabularyUtils.lookupEnum(countryParam, Country.class); + } + + if (country != null) { + searchRequest.getDescriptorCountry().add(country); + } + } + } + + String individualCountParam = webRequest.getParameter("individualCount"); + if (!Strings.isNullOrEmpty(individualCountParam)) { + validateIntegerRange(individualCountParam, "individualCount"); + searchRequest.setIndividualCount(individualCountParam); + } + + String[] identifiedByParam = webRequest.getParameterValues("identifiedBy"); + if (identifiedByParam != null && identifiedByParam.length > 0) { + searchRequest.setIdentifiedBy(Arrays.asList(identifiedByParam)); + } + + Optional.ofNullable(webRequest.getParameter("dateIdentified")) + .ifPresent(v -> searchRequest.setDateIdentified(SearchTypeValidator.parseDateRange(v))); + + String[] typeStatusParam = webRequest.getParameterValues("typeStatus"); + if (typeStatusParam != null && typeStatusParam.length > 0) { + searchRequest.setTypeStatus(new ArrayList<>()); + for (String param : typeStatusParam) { + try { + searchRequest.getTypeStatus().add(TypeStatus.valueOf(param).name()); + } catch (Exception ex) { + throw new IllegalArgumentException("Invalid type status parameter: " + param); + } + } + } + + String[] recordedByParam = webRequest.getParameterValues("recordedBy"); + if (recordedByParam != null && recordedByParam.length > 0) { + searchRequest.setRecordedBy(Arrays.asList(recordedByParam)); + } + + String[] disciplineParam = webRequest.getParameterValues("discipline"); + if (disciplineParam != null && disciplineParam.length > 0) { + searchRequest.setDiscipline(Arrays.asList(disciplineParam)); + } + + String[] objectClassificationParam = webRequest.getParameterValues("objectClassification"); + if (objectClassificationParam != null && objectClassificationParam.length > 0) { + searchRequest.setObjectClassification(Arrays.asList(objectClassificationParam)); + } + + String[] issueParam = webRequest.getParameterValues("issue"); + if (issueParam != null && issueParam.length > 0) { + searchRequest.setIssue(Arrays.asList(issueParam)); + } + + return searchRequest; + } +} diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/provider/CollectionSearchRequestHandlerMethodArgumentResolver.java b/registry-ws/src/main/java/org/gbif/registry/ws/provider/CollectionSearchRequestHandlerMethodArgumentResolver.java index 7753f7d543..2af4800a65 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/provider/CollectionSearchRequestHandlerMethodArgumentResolver.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/provider/CollectionSearchRequestHandlerMethodArgumentResolver.java @@ -14,15 +14,14 @@ package org.gbif.registry.ws.provider; import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.UUID; import org.gbif.api.model.collections.request.CollectionSearchRequest; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.ModelAndViewContainer; -import java.util.Arrays; -import java.util.UUID; - @SuppressWarnings("NullableProblems") public class CollectionSearchRequestHandlerMethodArgumentResolver extends BaseGrSciCollSearchRequestHandlerMethodArgumentResolver { @@ -39,7 +38,14 @@ public Object resolveArgument( NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { - CollectionSearchRequest searchRequest = new CollectionSearchRequest(); + CollectionSearchRequest searchRequest = CollectionSearchRequest.builder().build(); + fillCollectionSearchRequest(searchRequest, webRequest); + + return searchRequest; + } + + protected void fillCollectionSearchRequest( + CollectionSearchRequest searchRequest, NativeWebRequest webRequest) { fillSearchRequestParams(searchRequest, webRequest); String institution = webRequest.getParameter("institution"); @@ -75,7 +81,5 @@ public Object resolveArgument( "Invalid boolean for personalCollection: " + personalCollection); } } - - return searchRequest; } } diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/provider/InstitutionSearchRequestHandlerMethodArgumentResolver.java b/registry-ws/src/main/java/org/gbif/registry/ws/provider/InstitutionSearchRequestHandlerMethodArgumentResolver.java index 581fd70138..1b976ff031 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/provider/InstitutionSearchRequestHandlerMethodArgumentResolver.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/provider/InstitutionSearchRequestHandlerMethodArgumentResolver.java @@ -37,7 +37,7 @@ public Object resolveArgument( NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { - InstitutionSearchRequest searchRequest = new InstitutionSearchRequest(); + InstitutionSearchRequest searchRequest = InstitutionSearchRequest.builder().build(); fillSearchRequestParams(searchRequest, webRequest); String[] types = webRequest.getParameterValues("type"); diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/resources/OrganizationResource.java b/registry-ws/src/main/java/org/gbif/registry/ws/resources/OrganizationResource.java index 88cb0f7ecf..a226522c3e 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/resources/OrganizationResource.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/resources/OrganizationResource.java @@ -13,6 +13,8 @@ */ package org.gbif.registry.ws.resources; +import org.apache.ibatis.annotations.Param; + import org.gbif.api.annotation.NullToNotFound; import org.gbif.api.annotation.Trim; import org.gbif.api.documentation.CommonParameters; @@ -33,6 +35,7 @@ import org.gbif.registry.persistence.mapper.DatasetMapper; import org.gbif.registry.persistence.mapper.InstallationMapper; import org.gbif.registry.persistence.mapper.OrganizationMapper; +import org.gbif.registry.persistence.mapper.dto.OrganizationGeoJsonDto; import org.gbif.registry.persistence.mapper.params.BaseListParams; import org.gbif.registry.persistence.mapper.params.DatasetListParams; import org.gbif.registry.persistence.mapper.params.InstallationListParams; @@ -52,6 +55,9 @@ import javax.validation.constraints.NotNull; import javax.validation.groups.Default; +import org.geojson.Feature; +import org.geojson.FeatureCollection; +import org.geojson.Point; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -536,6 +542,70 @@ public List suggest(@RequestParam(value = "q", required = false) return organizationMapper.suggest(label); } + @Operation( + operationId = "listOrganizationAsGeoJson", + summary = "List all organizations as GeoJson.", + description = + "Lists all current organizations in GeoJson format (deleted organizations are not listed).", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0550"))) + @SimpleSearchParameters + @Parameters( + value = { + @Parameter( + name = "isEndorsed", + description = "Whether the organization is endorsed by a node.", + schema = @Schema(implementation = Boolean.class), + in = ParameterIn.QUERY), + @Parameter( + name = "networkKey", + description = "Filter for organizations publishing datasets belonging to a network.", + schema = @Schema(implementation = UUID.class), + in = ParameterIn.QUERY) + }) + @ApiResponse(responseCode = "200", description = "Organization search successful") + @ApiResponse(responseCode = "400", description = "Invalid search query provided") + @Override + @GetMapping("geojson") + public FeatureCollection listGeoJson(@Param("params") OrganizationRequestSearchParams request) { + if (request == null) { + request = new OrganizationRequestSearchParams(); + } + + OrganizationListParams listParams = + OrganizationListParams.builder() + .query(parseQuery(request.getQ())) + .country(request.getCountry()) + .isEndorsed(request.getIsEndorsed()) + .deleted(false) + .networkKey(request.getNetworkKey()) + .from(parseFrom(request.getModified())) + .to(parseTo(request.getModified())) + .identifier(request.getIdentifier()) + .identifierType(request.getIdentifierType()) + .mtNamespace(request.getMachineTagNamespace()) + .mtName(request.getMachineTagName()) + .mtValue(request.getMachineTagValue()) + .page(null) + .build(); + + List publishers = organizationMapper.listGeoJson(listParams); + + FeatureCollection featureCollection = new FeatureCollection(); + for (OrganizationGeoJsonDto p: publishers) { + Feature feature = new Feature(); + feature.setProperty("key", p.getKey()); + feature.setProperty("title", p.getTitle()); + feature.setProperty("numPublishedDatasets", p.getNumPublishedDatasets()); + feature.setGeometry(new Point(p.getLongitude().doubleValue(), p.getLatitude().doubleValue())); + featureCollection.add(feature); + } + + return featureCollection; + } + /** * This is an HTTP only method to retrieve the shared token (password) for an organization. * 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 d751f7cab4..33d665da22 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 @@ -506,8 +506,9 @@ public PagingResponse listChangeSuggestion( @RequestParam(value = "type", required = false) Type type, @RequestParam(value = "proposerEmail", required = false) String proposerEmail, @RequestParam(value = "entityKey", required = false) UUID entityKey, + @RequestParam(value = "ihIdentifier", required = false) String ihIdentifier, Pageable page) { - return changeSuggestionService.list(status, type, proposerEmail, entityKey, page); + return changeSuggestionService.list(status, type, proposerEmail, entityKey, ihIdentifier, page); } @Operation( 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 245e189faf..ecdc9357f2 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 @@ -13,33 +13,68 @@ */ package org.gbif.registry.ws.resources.collections; +import com.google.common.base.Preconditions; import io.swagger.v3.oas.annotations.Hidden; 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 java.io.BufferedWriter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import javax.servlet.http.HttpServletResponse; +import lombok.SneakyThrows; import org.gbif.api.annotation.NullToNotFound; import org.gbif.api.annotation.Trim; import org.gbif.api.documentation.CommonParameters; import org.gbif.api.model.collections.Collection; import org.gbif.api.model.collections.CollectionImportParams; import org.gbif.api.model.collections.SourceableField; +import org.gbif.api.model.collections.descriptors.Descriptor; +import org.gbif.api.model.collections.descriptors.DescriptorGroup; import org.gbif.api.model.collections.latimercore.ObjectGroup; import org.gbif.api.model.collections.request.CollectionSearchRequest; +import org.gbif.api.model.collections.request.DescriptorGroupSearchRequest; +import org.gbif.api.model.collections.request.DescriptorSearchRequest; import org.gbif.api.model.collections.suggestions.CollectionChangeSuggestion; import org.gbif.api.model.collections.view.CollectionView; 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.search.collections.KeyCodeNameResult; import org.gbif.api.service.collections.CollectionService; +import org.gbif.api.service.collections.DescriptorsService; import org.gbif.api.util.iterables.Iterables; import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.GbifRegion; +import org.gbif.api.vocabulary.Rank; import org.gbif.api.vocabulary.collections.AccessionStatus; import org.gbif.api.vocabulary.collections.CollectionContentType; import org.gbif.api.vocabulary.collections.PreservationType; @@ -51,24 +86,17 @@ import org.gbif.registry.service.collections.utils.MasterSourceUtils; import org.gbif.registry.ws.export.CsvWriter; import org.gbif.registry.ws.resources.Docs; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletResponse; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; +import org.springframework.web.multipart.MultipartFile; /** * Class that acts both as the WS endpoint for {@link Collection} entities and also provides an @@ -95,7 +123,8 @@ public class CollectionResource extends BaseCollectionEntityResource { - public final CollectionService collectionService; + private final CollectionService collectionService; + private final DescriptorsService descriptorsService; // Prefix for the export file format private static final String EXPORT_FILE_NAME = "%scollections.%s"; @@ -109,6 +138,7 @@ public CollectionResource( CollectionService collectionService, CollectionChangeSuggestionService collectionChangeSuggestionService, CollectionBatchService batchService, + DescriptorsService descriptorsService, @Value("${api.root.url}") String apiBaseUrl) { super( collectionMergeService, @@ -119,6 +149,7 @@ public CollectionResource( apiBaseUrl, Collection.class); this.collectionService = collectionService; + this.descriptorsService = descriptorsService; } @Target({ElementType.METHOD, ElementType.TYPE}) @@ -454,4 +485,397 @@ public UUID createFromDataset(@RequestBody @Trim CollectionImportParams importPa public List getSourceableFields() { return MasterSourceUtils.COLLECTION_SOURCEABLE_FIELDS; } + + @Operation( + operationId = "getCollectionDescriptorGroups", + summary = "Lists the descriptor groups of the collection.", + description = "Lists the descriptor groups of the collection.", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0500"))) + @Parameters( + value = { + @Parameter( + name = "title", + description = "Descriptor group title", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY), + @Parameter( + name = "description", + description = "Descriptor group description", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY), + @Parameter( + name = "deleted", + description = "Boolean flag to indicate if we want deleted descriptor groups", + schema = @Schema(implementation = Boolean.class), + in = ParameterIn.QUERY) + }) + @CommonParameters.QParameter + @Pageable.OffsetLimitParameters + @Docs.DefaultEntityKeyParameter + @ApiResponse( + responseCode = "200", + description = "Collection descriptor groups found and returned") + @Docs.DefaultUnsuccessfulReadResponses + @GetMapping("{collectionKey}/descriptorGroup") + @NullToNotFound("/grscicoll/collection/{collectionKey}/descriptorGroup") + public PagingResponse listCollectionDescriptorGroups( + @PathVariable("collectionKey") UUID collectionKey, + DescriptorGroupSearchRequest searchRequest) { + return descriptorsService.listDescriptorGroups(collectionKey, searchRequest); + } + + @Operation( + operationId = "createCollectionDescriptorGroup", + summary = "Create a new collection descriptor group", + description = "Creates a new collection descriptor group.", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0501"))) + @ApiResponse( + responseCode = "201", + description = "Collection descriptor group created, new descriptor group's key returned") + @Docs.DefaultUnsuccessfulWriteResponses + @SneakyThrows + @PostMapping( + value = "{collectionKey}/descriptorGroup", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public long createDescriptorGroup( + @PathVariable UUID collectionKey, + @RequestParam(value = "format", defaultValue = "CSV") ExportFormat format, + @RequestPart(value = "descriptorsFile", required = false) MultipartFile descriptorsFile, + @RequestParam("title") @Trim String title, + @RequestParam(value = "description", required = false) @Trim String description) { + return descriptorsService.createDescriptorGroup( + StreamUtils.copyToByteArray(descriptorsFile.getResource().getInputStream()), + format, + title, + description, + collectionKey); + } + + @Operation( + operationId = "updateCollectionDescriptorGroup", + summary = "Update an existing collection descriptor group", + description = + "Updates the existing collection descriptor group. It updates the metadata and reimport the file by deleting the " + + "existing descriptors and creating new ones.", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0502"))) + @Docs.DefaultEntityKeyParameter + @ApiResponse(responseCode = "204", description = "Collection descriptor group updated") + @Docs.DefaultUnsuccessfulReadResponses + @Docs.DefaultUnsuccessfulWriteResponses + @SneakyThrows + @PutMapping( + value = "{collectionKey}/descriptorGroup/{key}", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public void updateDescriptorGroup( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey, + @RequestParam(value = "format", defaultValue = "CSV") ExportFormat format, + @RequestPart("descriptorsFile") MultipartFile descriptorsFile, + @RequestParam("title") @Trim String title, + @RequestParam(value = "description", required = false) @Trim String description) { + getDescriptorGroupWithCheck(collectionKey, descriptorGroupKey); + descriptorsService.updateDescriptorGroup( + descriptorGroupKey, + StreamUtils.copyToByteArray(descriptorsFile.getResource().getInputStream()), + format, + title, + description); + } + + @Operation( + operationId = "getCollectionDescriptorGroup", + summary = "Get details of a single collection descriptor", + description = "Details of a single collection descriptor", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0500"))) + @Docs.DefaultEntityKeyParameter + @ApiResponse(responseCode = "200", description = "Collection found and returned") + @Docs.DefaultUnsuccessfulReadResponses + @GetMapping("{collectionKey}/descriptorGroup/{key}") + @NullToNotFound("/grscicoll/collection/{collectionKey}/descriptorGroup/{key}") + public DescriptorGroup getCollectionDescriptorGroup( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey) { + return getDescriptorGroupWithCheck(collectionKey, descriptorGroupKey); + } + + @Operation( + operationId = "deleteCollectionDescriptorGroup", + summary = "Deletes a collection descriptor group", + description = "Deletes a collection descriptor group", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0510"))) + @Docs.DefaultEntityKeyParameter + @ApiResponse(responseCode = "204", description = "Descriptor group marked as deleted") + @Docs.DefaultUnsuccessfulReadResponses + @Docs.DefaultUnsuccessfulWriteResponses + @DeleteMapping("{collectionKey}/descriptorGroup/{key}") + public void deleteCollectionDescriptorGroup( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey) { + getDescriptorGroupWithCheck(collectionKey, descriptorGroupKey); + descriptorsService.deleteDescriptorGroup(descriptorGroupKey); + } + + @Operation( + operationId = "CollectionDescriptorGroupExport", + summary = "Exports a collection descriptor group.", + description = "Download a collection descriptor group as CSV or TSV.", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0520"))) + @CollectionSearchParameters + @ApiResponse(responseCode = "200", description = "Collection search successful") + @ApiResponse(responseCode = "400", description = "Invalid search query provided") + @GetMapping(value = "{collectionKey}/descriptorGroup/{key}/export", produces = "application/zip") + public ResponseEntity exportDescriptorGroup( + HttpServletResponse response, + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey, + @RequestParam(value = "format", defaultValue = "TSV") ExportFormat format) + throws IOException { + DescriptorGroup existingDescriptorGroup = + getDescriptorGroupWithCheck(collectionKey, descriptorGroupKey); + + DescriptorSearchRequest searchRequest = + DescriptorSearchRequest.builder().descriptorGroupKey(descriptorGroupKey).build(); + + Preconditions.checkArgument( + descriptorsService.countDescriptors(searchRequest) > 0, + "The descriptor group doesn't have descriptors"); + + long ts = System.currentTimeMillis(); + + Path interpretedCsv = + Files.createFile( + Paths.get("interpreted" + ts + "." + format.name().toLowerCase()), + PosixFilePermissions.asFileAttribute(filePermissions())); + try (Writer writer = + new OutputStreamWriter(new FileOutputStream(interpretedCsv.toFile().getName()))) { + CsvWriter.descriptors( + Iterables.descriptors(descriptorsService, searchRequest, EXPORT_LIMIT), format) + .export(writer); + } + + Path verbatimCsv = + Files.createFile( + Paths.get("verbatim" + ts + "." + format.name().toLowerCase()), + PosixFilePermissions.asFileAttribute(filePermissions())); + Set verbatimFields = descriptorsService.getVerbatimNames(descriptorGroupKey); + try (Writer writer = + new OutputStreamWriter(Files.newOutputStream(Paths.get(verbatimCsv.toFile().getName())))) { + CsvWriter.descriptorVerbatims( + Iterables.descriptorVerbatims(descriptorsService, searchRequest, EXPORT_LIMIT), + format, + verbatimFields) + .exportMap(writer); + } + + Path zipFile = zipFiles(existingDescriptorGroup, interpretedCsv, verbatimCsv); + ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(zipFile)); + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=" + zipFile.toFile().getName()) + .body(resource); + } + + private static Set filePermissions() { + return new HashSet<>( + Arrays.asList( + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.OTHERS_WRITE, + PosixFilePermission.OTHERS_READ, + PosixFilePermission.OTHERS_EXECUTE)); + } + + private static Path zipFiles( + DescriptorGroup existingDescriptorGroup, Path interpretedCsv, Path verbatimCsv) + throws IOException { + Path zipFile = + Files.createTempFile( + "descriptorGroup" + existingDescriptorGroup.getKey() + "_export", ".zip"); + + try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile.toFile()))) { + for (Path f : Arrays.asList(interpretedCsv, verbatimCsv)) { + try (FileInputStream fis = new FileInputStream(f.toFile())) { + ZipEntry zipEntry = new ZipEntry(f.toFile().getName().replaceAll("[0-9]", "")); + zipOut.putNextEntry(zipEntry); + byte[] bytes = new byte[1024]; + int length; + while ((length = fis.read(bytes)) >= 0) { + zipOut.write(bytes, 0, length); + } + zipOut.closeEntry(); + } + Files.delete(f); + } + } + + return zipFile; + } + + @NotNull + private DescriptorGroup getDescriptorGroupWithCheck(UUID collectionKey, long descriptorGroupKey) { + DescriptorGroup existingDescriptorGroup = + descriptorsService.getDescriptorGroup(descriptorGroupKey); + Preconditions.checkArgument(existingDescriptorGroup.getCollectionKey().equals(collectionKey)); + return existingDescriptorGroup; + } + + @Operation( + operationId = "getCollectionDescriptors", + summary = "Lists the descriptors.", + description = "Lists the descriptors.", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0700"))) + @Parameters( + value = { + @Parameter( + name = "descriptorGroupKey", + description = "Key of the descriptor group", + schema = @Schema(implementation = Long.class), + in = ParameterIn.QUERY), + @Parameter( + name = "usageKey", + description = "Taxon usage key of the descriptor", + schema = @Schema(implementation = Integer.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "usageName", + description = "Taxon usage name of the descriptor", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "usageRank", + description = "Taxon usage rank of the descriptor", + schema = @Schema(implementation = Rank.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "taxonKey", + description = "Taxon key of the descriptor", + schema = @Schema(implementation = Integer.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "country", + description = "Country of the descriptor", + schema = @Schema(implementation = Country.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "individualCount", + description = + "Individual count of the descriptor. It supports ranges and a '*' can be used as a wildcard", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY), + @Parameter( + name = "identifiedBy", + description = "Identified by field of the descriptor", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "dateIdentified", + description = "Date identified field of the descriptor", + schema = @Schema(implementation = Date.class), + in = ParameterIn.QUERY), + @Parameter( + name = "dateIdentifiedFrom", + description = "Date identified of the descriptor is equal or higher than the specified", + schema = @Schema(implementation = Date.class), + in = ParameterIn.QUERY), + @Parameter( + name = "dateIdentifiedBefore", + description = "Date identified of the descriptor is lower than the specified", + schema = @Schema(implementation = Date.class), + in = ParameterIn.QUERY), + @Parameter( + name = "typeStatus", + description = "Type status of the descriptor", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "recordedBy", + description = "RecordedBy of the descriptor", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "discipline", + description = "Discipline of the descriptor", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "objectClassification", + description = "Object classification of the descriptor", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE), + @Parameter( + name = "issues", + description = "Issues of the descriptor", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY, + explode = Explode.TRUE) + }) + @CommonParameters.QParameter + @Pageable.OffsetLimitParameters + @Docs.DefaultEntityKeyParameter + @ApiResponse(responseCode = "200", description = "Descriptors found and returned") + @Docs.DefaultUnsuccessfulReadResponses + @GetMapping("{collectionKey}/descriptorGroup/{key}/descriptor") + @NullToNotFound("/grscicoll/collection/{collectionKey}/descriptorGroup/{key}/descriptor") + public PagingResponse listCollectionDescriptors( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("key") long descriptorGroupKey, + DescriptorSearchRequest searchRequest) { + searchRequest.setDescriptorGroupKey(descriptorGroupKey); + return descriptorsService.listDescriptors(searchRequest); + } + + @Operation( + operationId = "getCollectionDescriptor", + summary = "Lists the descriptor records.", + description = "Lists the descriptor records.", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0710"))) + @Docs.DefaultEntityKeyParameter + @ApiResponse(responseCode = "200", description = "Descriptor found and returned") + @Docs.DefaultUnsuccessfulReadResponses + @GetMapping("{collectionKey}/descriptorGroup/{descriptorGroupKey}/descriptor/{key}") + @NullToNotFound( + "/grscicoll/collection/{collectionKey}/descriptorGroup/{descriptorGroupKey}/descriptor/{key}") + public Descriptor getCollectionDescriptor( + @PathVariable("collectionKey") UUID collectionKey, + @PathVariable("descriptorGroupKey") long descriptorGroupKey, + @PathVariable("key") long descriptorKey) { + Descriptor descriptor = descriptorsService.getDescriptor(descriptorKey); + Preconditions.checkArgument(descriptor.getDescriptorGroupKey().equals(descriptorGroupKey)); + getDescriptorGroupWithCheck(collectionKey, descriptorGroupKey); + return descriptor; + } } diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionsSearchResource.java b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionsSearchResource.java index eaeabb7e3d..7966c2c802 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionsSearchResource.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/resources/collections/CollectionsSearchResource.java @@ -13,21 +13,6 @@ */ package org.gbif.registry.ws.resources.collections; -import org.gbif.api.documentation.CommonParameters; -import org.gbif.api.model.collections.search.CollectionsSearchResponse; -import org.gbif.api.model.common.paging.Pageable; -import org.gbif.api.vocabulary.Country; -import org.gbif.registry.domain.collections.TypeParam; -import org.gbif.registry.search.dataset.service.collections.CollectionsSearchService; - -import java.util.List; - -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; @@ -37,15 +22,35 @@ import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import java.util.List; +import org.gbif.api.documentation.CommonParameters; +import org.gbif.api.model.collections.request.CollectionDescriptorsSearchRequest; +import org.gbif.api.model.collections.request.InstitutionSearchRequest; +import org.gbif.api.model.collections.search.CollectionSearchResponse; +import org.gbif.api.model.collections.search.CollectionsFullSearchResponse; +import org.gbif.api.model.collections.search.InstitutionSearchResponse; +import org.gbif.api.model.common.paging.Pageable; +import org.gbif.api.model.common.paging.PagingResponse; +import org.gbif.api.vocabulary.Country; +import org.gbif.registry.domain.collections.TypeParam; +import org.gbif.registry.service.collections.CollectionsSearchService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @io.swagger.v3.oas.annotations.tags.Tag( - name = "Search institutions and collections", - description = "This API provides a service to search institutions and collections. It searches in both institutions " + - "and collections and it highlights the matching fields (optional).", - extensions = @io.swagger.v3.oas.annotations.extensions.Extension( - name = "Order", properties = @ExtensionProperty(name = "Order", value = "1400"))) + name = "Search institutions and collections", + description = + "This API provides a service to search institutions and collections. It searches in both institutions " + + "and collections and it highlights the matching fields (optional).", + extensions = + @io.swagger.v3.oas.annotations.extensions.Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "1400"))) @RestController -@RequestMapping(value = "grscicoll/search", produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(value = "grscicoll", produces = MediaType.APPLICATION_JSON_VALUE) public class CollectionsSearchResource { private final CollectionsSearchService collectionsSearchService; @@ -55,36 +60,34 @@ public CollectionsSearchResource(CollectionsSearchService collectionsSearchServi } @Operation( - operationId = "searchCollectionsInstitutions", - summary = "Search collections and institutions", - extensions = @Extension(name = "Order", properties = @ExtensionProperty(name = "Order", value = "0101"))) + operationId = "searchCollectionsInstitutions", + summary = "Search collections and institutions", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0101"))) @CommonParameters.QParameter @CommonParameters.HighlightParameter @Pageable.OffsetLimitParameters @Parameters( - value = { - @Parameter( - name = "entityType", - description = "Code of a GrSciColl institution or collection", - schema = @Schema(implementation = String.class), - in = ParameterIn.QUERY), - @Parameter( - name = "displayOnNHCPortal", - hidden = true), - @Parameter( - name = "country", - description = "The 2-letter country code (as per ISO-3166-1) of the country.", - schema = @Schema(implementation = Country.class), - in = ParameterIn.QUERY, - explode = Explode.FALSE)}) - @ApiResponse( - responseCode = "200", - description = "Search successful") - @ApiResponse( - responseCode = "400", - description = "Invalid search query provided") - @GetMapping - public List searchCollections( + value = { + @Parameter( + name = "entityType", + description = "Code of a GrSciColl institution or collection", + schema = @Schema(implementation = String.class), + in = ParameterIn.QUERY), + @Parameter(name = "displayOnNHCPortal", hidden = true), + @Parameter( + name = "country", + description = "The 2-letter country code (as per ISO-3166-1) of the country.", + schema = @Schema(implementation = Country.class), + in = ParameterIn.QUERY, + explode = Explode.FALSE) + }) + @ApiResponse(responseCode = "200", description = "Search successful") + @ApiResponse(responseCode = "400", description = "Invalid search query provided") + @GetMapping("search") + public List searchCrossEntities( @RequestParam(value = "q", required = false) String query, @RequestParam(value = "hl", defaultValue = "false") boolean highlight, @RequestParam(value = "entityType", required = false) TypeParam type, @@ -94,4 +97,40 @@ public List searchCollections( return collectionsSearchService.search( query, highlight, type, displayOnNHCPortal, country, limit); } + + @Operation( + operationId = "searchInstitutions", + summary = "Search across institutions", + description = "Searches for institutions.", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0110"))) + @CommonParameters.HighlightParameter + @InstitutionResource.InstitutionSearchParameters + @ApiResponse(responseCode = "200", description = "Search successful") + @ApiResponse(responseCode = "400", description = "Invalid search query provided") + @GetMapping("institution/search") + public PagingResponse searchInstitutions( + InstitutionSearchRequest searchRequest) { + return collectionsSearchService.searchInstitutions(searchRequest); + } + + @Operation( + operationId = "searchCollections", + summary = "Search across collections", + description = "Searches for collections", + extensions = + @Extension( + name = "Order", + properties = @ExtensionProperty(name = "Order", value = "0120"))) + @CollectionResource.CollectionSearchParameters + @CommonParameters.HighlightParameter + @ApiResponse(responseCode = "200", description = "Search successful") + @ApiResponse(responseCode = "400", description = "Invalid search query provided") + @GetMapping("collection/search") + public PagingResponse searchCollections( + CollectionDescriptorsSearchRequest searchRequest) { + return collectionsSearchService.searchCollections(searchRequest); + } }