package org.gbif.ipt.service.admin.impl; import org.gbif.dwc.terms.DcTerm; import org.gbif.dwc.terms.DwcTerm; import org.gbif.dwc.terms.Term; import org.gbif.dwc.terms.TermFactory; import org.gbif.ipt.config.AppConfig; import org.gbif.ipt.config.ConfigWarnings; import org.gbif.ipt.config.Constants; import org.gbif.ipt.config.DataDir; import org.gbif.ipt.config.IPTModule; import org.gbif.ipt.model.Extension; import org.gbif.ipt.model.ExtensionMapping; import org.gbif.ipt.model.PropertyMapping; import org.gbif.ipt.model.Resource; import org.gbif.ipt.model.factory.ExtensionFactory; import org.gbif.ipt.model.factory.ThesaurusHandlingRule; import org.gbif.ipt.service.InvalidConfigException; import org.gbif.ipt.service.admin.ExtensionManager; import org.gbif.ipt.service.admin.RegistrationManager; import org.gbif.ipt.service.manage.ResourceManager; import org.gbif.ipt.service.registry.RegistryManager; import org.gbif.ipt.service.registry.impl.RegistryManagerImpl; import org.gbif.ipt.struts2.SimpleTextProvider; import org.gbif.utils.HttpUtil; import org.gbif.utils.file.FileUtils; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.List; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.io.Files; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.servlet.ServletModule; import com.google.inject.struts2.Struts2GuicePluginModule; import org.apache.commons.io.IOUtils; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.impl.client.DefaultHttpClient; import org.junit.Before; import org.junit.Test; import org.xml.sax.SAXException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ExtensionManagerImplTest { private static final TermFactory TERM_FACTORY = TermFactory.instance(); private ExtensionManager extensionManager; private ExtensionFactory extensionFactory; private ResourceManager resourceManager; private AppConfig appConfig; @Before public void setup() throws IOException, URISyntaxException, SAXException, ParserConfigurationException { resourceManager = mock(ResourceManager.class); DataDir mockDataDir = mock(DataDir.class); appConfig = new AppConfig(mockDataDir); ConfigWarnings warnings = new ConfigWarnings(); SimpleTextProvider mockSimpleTextProvider = mock(SimpleTextProvider.class); RegistrationManager mockRegistrationManager = mock(RegistrationManager.class); Injector injector = Guice.createInjector(new ServletModule(), new Struts2GuicePluginModule(), new IPTModule()); // construct ExtensionFactory using injected parameters DefaultHttpClient httpClient = injector.getInstance(DefaultHttpClient.class); ThesaurusHandlingRule thesaurusRule = new ThesaurusHandlingRule(mock(VocabulariesManagerImpl.class)); SAXParserFactory saxf = injector.getInstance(SAXParserFactory.class); extensionFactory = new ExtensionFactory(thesaurusRule, saxf, httpClient); // construct mock RegistryManager: // mock getExtensions() response from Registry with local test resource (list of extensions from extensions.json) HttpUtil mockHttpUtil = mock(HttpUtil.class); HttpUtil.Response mockResponse = mock(HttpUtil.Response.class); mockResponse.content = IOUtils .toString(ExtensionManagerImplTest.class.getResourceAsStream("/responses/extensions_sandbox.json"), "UTF-8"); when(mockHttpUtil.get(anyString())).thenReturn(mockResponse); // create instance of RegistryManager RegistryManager mockRegistryManager = new RegistryManagerImpl(appConfig, mockDataDir, mockHttpUtil, saxf, warnings, mockSimpleTextProvider, mockRegistrationManager, resourceManager); File myTmpDir = Files.createTempDir(); assertTrue(myTmpDir.isDirectory()); // copy occurrence core extension file to temporary directory File occCore = FileUtils.getClasspathFile("extensions/dwc_occurrence.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(occCore, myTmpDir); File tmpOccCore = new File(myTmpDir, "dwc_occurrence.xml"); assertTrue(tmpOccCore.exists()); // copy newer version of occurrence core extension to temporary directory File newerOccCore = FileUtils.getClasspathFile("extensions/dwc_occurrence_2015-04-24.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(newerOccCore, myTmpDir); File tmpNewerOccCore = new File(myTmpDir, "dwc_occurrence_2015-04-24.xml"); assertTrue(tmpNewerOccCore.exists()); // copy latest version of taxon core extension to temporary directory File taxonCore = FileUtils.getClasspathFile("extensions/dwc_taxon_2015-04-24.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(taxonCore, myTmpDir); File tmpTaxonCore = new File(myTmpDir, "dwc_taxon_2015-04-24.xml"); assertTrue(tmpTaxonCore.exists()); // copy latest version of event core extension to temporary directory File eventCore = FileUtils.getClasspathFile("extensions/dwc_event_2015-04-24.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(eventCore, myTmpDir); File tmpEventCore = new File(myTmpDir, "dwc_event_2015-04-24.xml"); assertTrue(tmpEventCore.exists()); // mock returning temporary files when looked up by their 'safe' filenames when(mockDataDir.tmpFile("http_rs_gbif_org_core_dwc_occurrence_xml.xml")).thenReturn(tmpOccCore); when(mockDataDir.tmpFile("http_rs_gbif_org_sandbox_core_dwc_occurrence_2015-04-24_xml.xml")) .thenReturn(tmpNewerOccCore); when(mockDataDir.tmpFile("http_rs_gbif_org_sandbox_core_dwc_taxon_2015-04-24_xml.xml")) .thenReturn(tmpTaxonCore); when(mockDataDir.tmpFile("http_rs_gbif_org_sandbox_core_dwc_event_2015-04-24_xml.xml")) .thenReturn(tmpEventCore); // Mock downloading extension into tmpFile - we're cheating by handling the actual file already as if it // were downloaded already. Furthermore, mock download() response with StatusLine with 200 OK response code StatusLine sl = mock(StatusLine.class); when(sl.getStatusCode()).thenReturn(HttpStatus.SC_OK); when(mockHttpUtil.download(any(URL.class), any(File.class))).thenReturn(sl); // mock returning newly created occurrence core extension file File occCoreExtension = new File(myTmpDir, "http_rs_tdwg_org_dwc_terms_Occurrence.xml"); when(mockDataDir.configFile(ExtensionManagerImpl.CONFIG_FOLDER + "/http_rs_tdwg_org_dwc_terms_Occurrence.xml")) .thenReturn(occCoreExtension); // mock returning newly created taxon core extension file File taxonCoreExtension = new File(myTmpDir, "http_rs_tdwg_org_dwc_terms_Taxon.xml"); when(mockDataDir.configFile(ExtensionManagerImpl.CONFIG_FOLDER + "/http_rs_tdwg_org_dwc_terms_Taxon.xml")) .thenReturn(taxonCoreExtension); // mock returning newly created event core extension file File eventCoreExtension = new File(myTmpDir, "http_rs_tdwg_org_dwc_terms_Event.xml"); when(mockDataDir.configFile(ExtensionManagerImpl.CONFIG_FOLDER + "/http_rs_tdwg_org_dwc_terms_Event.xml")) .thenReturn(eventCoreExtension); // create instance extensionManager = new ExtensionManagerImpl(appConfig, mockDataDir, extensionFactory, resourceManager, mockHttpUtil, warnings, mockSimpleTextProvider, mockRegistrationManager, mockRegistryManager); } @Test public void testInstallCoreTypes() { extensionManager.installCoreTypes(); assertEquals(3, extensionManager.list().size()); // get ext. and assert a couple properties Extension ext = extensionManager.get("http://rs.tdwg.org/dwc/terms/Occurrence"); assertEquals(169, ext.getProperties().size()); // confirm the extension attributes are read correctly from the XML (not the JSON) assertEquals("Darwin Core Occurrence", ext.getTitle()); assertEquals("Occurrence", ext.getName()); assertTrue(ext.getDescription().startsWith("The category")); assertEquals("http://rs.tdwg.org/dwc/terms/index.htm#Occurrence", ext.getLink().toString()); assertNotNull(ext.getIssued()); assertEquals("dwc:Taxon dwc:Event", ext.getSubject()); assertNull(ext.getUrl()); assertFalse(ext.isLatest()); // this isn't persisted, only populated when deserialising JSON list from registry } @Test public void testListCore() { extensionManager.installCoreTypes(); assertEquals(3, extensionManager.list().size()); // of the three cores, only the occurrence core is suitable for use on the taxon core List<Extension> results = extensionManager.listCore(Constants.DWC_ROWTYPE_TAXON); assertEquals(1, results.size()); // of the three cores, only the occurrence core is suitable for use on the event core results = extensionManager.listCore(Constants.DWC_ROWTYPE_EVENT); assertEquals(1, results.size()); } @Test public void testList() { extensionManager.installCoreTypes(); assertEquals(3, extensionManager.list().size()); // search excludes core types, otherwise it would return the occurrence core which is suitable for use on taxon core List<Extension> results = extensionManager.list(Constants.DWC_ROWTYPE_TAXON); assertEquals(0, results.size()); } /** * Test when IPT is configured with extra core type not matching a registered extension. */ @Test(expected = InvalidConfigException.class) public void testInstallCoreTypesBadCoreConfiguration() throws IOException, ParserConfigurationException, SAXException { File tmpDir = Files.createTempDir(); File dataDirLocation = new File(tmpDir, "datadir.location"); File testDataDir = FileUtils.getClasspathFile("dataDir"); org.apache.commons.io.FileUtils.copyDirectoryToDirectory(testDataDir, tmpDir); // copy testDataDir to tmp location File dataDir = new File(tmpDir, "dataDir"); assertTrue(dataDir.isDirectory() && dataDir.exists()); // Configure IPT, with extra core type DataDir builtDataDir = DataDir.buildFromLocationFile(dataDirLocation); builtDataDir.setDataDir(dataDir); appConfig = new AppConfig(builtDataDir); // trigger exception extensionManager.installCoreTypes(); } /** * Update an extension that has no associated mappings to it. */ @Test public void testUpdate() throws IOException { // first install old version of occurrence extension extensionManager.install(new URL("http://rs.gbif.org/core/dwc_occurrence.xml")); Extension ext = extensionManager.get("http://rs.tdwg.org/dwc/terms/Occurrence"); assertEquals(161, ext.getProperties().size()); assertNull(ext.getIssued()); // now update to latest version of occurrence extension issued 2015-04-24 extensionManager.update(Constants.DWC_ROWTYPE_OCCURRENCE); ext = extensionManager.get("http://rs.tdwg.org/dwc/terms/Occurrence"); assertEquals(169, ext.getProperties().size()); assertNotNull(ext.getIssued()); } /** * Update an extension that has an associated mappings to it. */ @Test public void testUpdateWithAssociatedMappings() throws IOException { // first install old version of occurrence extension extensionManager.install(new URL("http://rs.gbif.org/core/dwc_occurrence.xml")); Extension ext = extensionManager.get("http://rs.tdwg.org/dwc/terms/Occurrence"); assertEquals(161, ext.getProperties().size()); assertNull(ext.getIssued()); Resource r = getTestResource(ext); // populate list of resources, and mock resourceManager.list() List<Resource> resources = Lists.newArrayList(); resources.add(r); when(resourceManager.list()).thenReturn(resources); // now update to latest version of occurrence extension issued 2015-04-24 extensionManager.update(Constants.DWC_ROWTYPE_OCCURRENCE); ext = extensionManager.get("http://rs.tdwg.org/dwc/terms/Occurrence"); assertEquals(169, ext.getProperties().size()); assertNotNull(ext.getIssued()); // verify migration was successful ExtensionMapping migrated = r.getMapping(Constants.DWC_ROWTYPE_OCCURRENCE, 0); assertNotNull(migrated.getExtension().getIssued()); // test for example index 3 (rights term should have been replaced by dc:license) PropertyMapping licenseMapping = migrated.getField(DcTerm.license.qualifiedName()); assertTrue(licenseMapping.getIndex().compareTo(3) == 0); } @Test public void testMigrateResourceToNewExtensionVersion() throws IOException { ExtensionManagerImpl manager = new ExtensionManagerImpl(mock(AppConfig.class), mock(DataDir.class), extensionFactory, mock(ResourceManager.class), mock(HttpUtil.class), mock(ConfigWarnings.class), mock(SimpleTextProvider.class), mock(RegistrationManager.class), mock(RegistryManager.class)); File myTmpDir = Files.createTempDir(); // load current (installed) version of Occurrence extension File occCore = FileUtils.getClasspathFile("extensions/dwc_occurrence.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(occCore, myTmpDir); File tmpOccCore = new File(myTmpDir, "dwc_occurrence.xml"); Extension current = manager.loadFromFile(tmpOccCore); assertNotNull(current); // load newer (latest) version of Occurrence extension File newerOccCore = FileUtils.getClasspathFile("extensions/dwc_occurrence_2015-04-24.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(newerOccCore, myTmpDir); File tmpNewerOccCore = new File(myTmpDir, "dwc_occurrence_2015-04-24.xml"); Extension newer = manager.loadFromFile(tmpNewerOccCore); assertNotNull(newer); Resource r = getTestResource(current); // perform migration manager.migrateResourceToNewExtensionVersion(r, current, newer); // verify migration was successful ExtensionMapping migrated = r.getMapping(Constants.DWC_ROWTYPE_OCCURRENCE, 0); // index 0 (id term should have stayed the same) PropertyMapping verifiedIdMapping = migrated.getField(DwcTerm.occurrenceID.qualifiedName()); assertTrue(verifiedIdMapping.getIndex().compareTo(0) == 0); // index 1 (individualID term should have been replaced by dwc:organismID) PropertyMapping organismIdMapping = migrated.getField(DwcTerm.organismID.qualifiedName()); assertTrue(organismIdMapping.getIndex().compareTo(1) == 0); // index 3 (rights term should have been replaced by dc:license) PropertyMapping licenseMapping = migrated.getField(DcTerm.license.qualifiedName()); assertTrue(licenseMapping.getIndex().compareTo(3) == 0); // index 2 (formerly dc:source) and index 4 (formerly dwc:occurrenceDetails) could both be migrated to dc:references // only one property mapping to dc:references can exist though PropertyMapping referencesMapping = migrated.getField(DcTerm.references.qualifiedName()); assertTrue(referencesMapping.getIndex().compareTo(2) == 0 || referencesMapping.getIndex().compareTo(4) == 0); } /** * Create and return a Resource having an ExtensionMapping to Occurrence extension with some property mappings. */ private Resource getTestResource(Extension extension) { Resource r = new Resource(); r.setShortname("ants"); ExtensionMapping em = new ExtensionMapping(); em.setExtension(extension); Set<PropertyMapping> propertyMappings = Sets.newHashSet(); // index 0 (id term) PropertyMapping idMapping = new PropertyMapping(); Term occurrenceIdTerm = TERM_FACTORY.findTerm("http://rs.tdwg.org/dwc/terms/occurrenceID"); idMapping.setTerm(occurrenceIdTerm); idMapping.setIndex(0); em.setIdColumn(0); propertyMappings.add(idMapping); // index 1 (deprecated term that will get replaced by dwc:organismID) PropertyMapping individualIdMapping = new PropertyMapping(); Term individualIdTerm = TERM_FACTORY.findTerm("http://rs.tdwg.org/dwc/terms/individualID"); individualIdMapping.setTerm(individualIdTerm); individualIdMapping.setIndex(1); propertyMappings.add(individualIdMapping); // index 2 (deprecated term that will get replaced by dc:references PropertyMapping sourceMapping = new PropertyMapping(); Term sourceTerm = TERM_FACTORY.findTerm("http://purl.org/dc/terms/source"); sourceMapping.setTerm(sourceTerm); sourceMapping.setIndex(2); propertyMappings.add(sourceMapping); // index 3 (deprecated term that will get replaced by dc:references) PropertyMapping rightsMapping = new PropertyMapping(); Term rightsTerm = TERM_FACTORY.findTerm("http://purl.org/dc/terms/rights"); rightsMapping.setTerm(rightsTerm); rightsMapping.setIndex(3); propertyMappings.add(rightsMapping); // index 4 (deprecated term that will get replaced by dc:references) PropertyMapping occDetailsMapping = new PropertyMapping(); Term occDetailsTerm = TERM_FACTORY.findTerm("http://rs.tdwg.org/dwc/terms/occurrenceDetails"); occDetailsMapping.setTerm(occDetailsTerm); occDetailsMapping.setIndex(4); propertyMappings.add(occDetailsMapping); // confirm we have the right number property mappings in the extension mapping assertEquals(5, propertyMappings.size()); em.setFields(propertyMappings); r.addMapping(em); // confirm resource occurrence extension mapping can be retrieved assertEquals(1, r.getMappings(Constants.DWC_ROWTYPE_OCCURRENCE).size()); assertNull(em.getExtension().getIssued()); return r; } @Test public void testGetRedundantGroups() throws IOException { ExtensionManagerImpl manager = new ExtensionManagerImpl(mock(AppConfig.class), mock(DataDir.class), extensionFactory, mock(ResourceManager.class), mock(HttpUtil.class), mock(ConfigWarnings.class), mock(SimpleTextProvider.class), mock(RegistrationManager.class), mock(RegistryManager.class)); File myTmpDir = Files.createTempDir(); // load Occurrence extension File occ = FileUtils.getClasspathFile("extensions/dwc_occurrence.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(occ, myTmpDir); File tmpOccFile = new File(myTmpDir, "dwc_occurrence.xml"); Extension occExt = manager.loadFromFile(tmpOccFile); // load Event (core) extension File evt = FileUtils.getClasspathFile("extensions/dwc_event_2015-04-24.xml"); org.apache.commons.io.FileUtils.copyFileToDirectory(evt, myTmpDir); File tmpEvtFile = new File(myTmpDir, "dwc_event_2015-04-24.xml"); Extension evtExt = manager.loadFromFile(tmpEvtFile); List<String> redundant = manager.getRedundantGroups(occExt, evtExt); // confirm Occurrence extension has 4 redundant groups that already appear in the core Event extension assertEquals(4, redundant.size()); assertTrue(redundant.contains("Event")); assertTrue(redundant.contains("Record Level")); assertTrue(redundant.contains("Location")); assertTrue(redundant.contains("GeologicalContext")); } }