package org.gbif.ipt.action.manage; import org.gbif.api.model.common.DOI; import org.gbif.doi.service.DoiService; import org.gbif.doi.service.ServiceConfig; import org.gbif.doi.service.datacite.DataCiteService; import org.gbif.doi.service.ezid.EzidService; import org.gbif.ipt.config.AppConfig; import org.gbif.ipt.config.Constants; import org.gbif.ipt.config.DataDir; import org.gbif.ipt.model.Organisation; import org.gbif.ipt.model.Resource; import org.gbif.ipt.model.User; import org.gbif.ipt.model.VersionHistory; import org.gbif.ipt.model.voc.DOIRegistrationAgency; import org.gbif.ipt.model.voc.IdentifierStatus; import org.gbif.ipt.model.voc.PublicationStatus; import org.gbif.ipt.service.DeletionNotAllowedException; import org.gbif.ipt.service.admin.ExtensionManager; import org.gbif.ipt.service.admin.RegistrationManager; import org.gbif.ipt.service.admin.UserAccountManager; import org.gbif.ipt.service.admin.VocabulariesManager; import org.gbif.ipt.service.manage.ResourceManager; import org.gbif.ipt.struts2.SimpleTextProvider; import org.gbif.ipt.task.GenerateDwcaFactory; import org.gbif.ipt.utils.DOIUtils; import org.gbif.metadata.eml.Agent; import org.gbif.metadata.eml.Citation; import org.gbif.metadata.eml.Eml; import org.gbif.metadata.eml.EmlWriter; import org.gbif.utils.HttpUtil; import org.gbif.utils.file.FileUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.collect.Lists; import freemarker.template.TemplateException; import org.apache.log4j.Logger; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; 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; @RunWith(Parameterized.class) public class OverviewActionIT { private static final Logger LOG = Logger.getLogger(OverviewActionIT.class); private static final UUID ORGANISATION_KEY = UUID.fromString("dce7a3c9-ea78-4be7-9abc-e3838de70dc5"); private Resource r; private OverviewAction action; private DOIRegistrationAgency type; public OverviewActionIT(OverviewAction action, DOIRegistrationAgency type) { this.action = action; this.type = type; } @Parameterized.Parameters public static Iterable data() throws IOException, DeletionNotAllowedException, TemplateException, URISyntaxException { // common mock AppConfig AppConfig mockAppConfig = mock(AppConfig.class); DataDir mockDataDir = mock(DataDir.class); // mock returning versioned eml file (e.g. eml-#.0.xml) - constructed from Eml object populated with mandatory stuff Eml eml = new Eml(); eml.setTitle("ants"); eml.setPubDate(new Date()); Agent creator = new Agent(); creator.setFirstName("John"); creator.setLastName("Smith"); eml.addCreator(creator); File tmpVersionedEmlFile = File.createTempFile("eml-#.0", ".xml"); EmlWriter.writeEmlFile(tmpVersionedEmlFile, eml); when(mockDataDir.resourceEmlFile(anyString(), any(BigDecimal.class))).thenReturn(tmpVersionedEmlFile); when(mockAppConfig.getDataDir()).thenReturn(mockDataDir); // mock returning target URLs when(mockAppConfig.getResourceUri(anyString())).thenReturn(new URI("http://www.gbif-uat.org/ipt/resource?r=ants")); when(mockAppConfig.getResourceVersionUri(anyString(), any(BigDecimal.class))) .thenReturn(new URI("http://www.gbif-uat.org/ipt/resource?r=ants&v=#.0")); // DataCite parameters.. RegistrationManager mockRegistrationManagerDataCite = mock(RegistrationManager.class); ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); InputStream dc = FileUtils.classpathStream("datacite.yaml"); ServiceConfig dcCfg = mapper.readValue(dc, ServiceConfig.class); //LOG.info("DataCite password (read from Maven property datacite.password)= " + dcCfg.getPassword()); Organisation oDataCite = new Organisation(); oDataCite.setKey(ORGANISATION_KEY.toString()); oDataCite.setAgencyAccountPrimary(true); oDataCite.setName("GBIF"); oDataCite.setDoiPrefix(Constants.TEST_DOI_PREFIX); oDataCite.setCanHost(true); oDataCite.setAgencyAccountUsername(dcCfg.getUsername()); oDataCite.setAgencyAccountPassword(dcCfg.getPassword()); oDataCite.setDoiRegistrationAgency(DOIRegistrationAgency.DATACITE); // mock returning primary DOI agency account when(mockRegistrationManagerDataCite.findPrimaryDoiAgencyAccount()).thenReturn(oDataCite); // mock RegistrationManager returning organisation by key when(mockRegistrationManagerDataCite.get(any(UUID.class))).thenReturn(oDataCite); // mock returning DataCite service DoiService dataCiteService = new DataCiteService(HttpUtil.newMultithreadedClient(10000, 3, 2), dcCfg); when(mockRegistrationManagerDataCite.getDoiService()).thenReturn(dataCiteService); // mock action for DataCite OverviewAction actionDataCite = new OverviewAction(mock(SimpleTextProvider.class), mockAppConfig, mockRegistrationManagerDataCite, mock(ResourceManager.class), mock(UserAccountManager.class), mock(ExtensionManager.class), mock(VocabulariesManager.class), mock(GenerateDwcaFactory.class)); // EZID parameters.. RegistrationManager mockRegistrationManagerEZID = mock(RegistrationManager.class); Organisation oEZID = new Organisation(); oEZID.setKey(ORGANISATION_KEY.toString()); oEZID.setAgencyAccountPrimary(true); oEZID.setName("GBIF"); oEZID.setDoiPrefix(Constants.EZID_TEST_DOI_SHOULDER); oEZID.setCanHost(true); oEZID.setAgencyAccountUsername("apitest"); oEZID.setAgencyAccountPassword("apitest"); oEZID.setDoiRegistrationAgency(DOIRegistrationAgency.EZID); // mock returning primary DOI agency account when(mockRegistrationManagerEZID.findPrimaryDoiAgencyAccount()).thenReturn(oEZID); // mock RegistrationManager returning organisation by key when(mockRegistrationManagerEZID.get(any(UUID.class))).thenReturn(oEZID); // mock returning EZID service ServiceConfig cfgEZID = new ServiceConfig("apitest", "apitest"); EzidService ezidService = new EzidService(HttpUtil.newMultithreadedClient(10000, 2, 2), cfgEZID); when(mockRegistrationManagerEZID.getDoiService()).thenReturn(ezidService); // mock action for EZID OverviewAction actionEZID = new OverviewAction(mock(SimpleTextProvider.class), mockAppConfig, mockRegistrationManagerEZID, mock(ResourceManager.class), mock(UserAccountManager.class), mock(ExtensionManager.class), mock(VocabulariesManager.class), mock(GenerateDwcaFactory.class)); return Arrays.asList( new Object[][] {{actionDataCite, DOIRegistrationAgency.DATACITE} , {actionEZID, DOIRegistrationAgency.EZID} }); } /** * Generate a new test resource for each test. */ @Before public void before() { r = new Resource(); Eml eml = new Eml(); r.setEml(eml); // mandatory elements r.setTitle("Ants"); r.setShortname("ants"); eml.setTitle("Ants"); Citation citation = new Citation(); citation.setCitation("Smith J (2013). Ants. GBIF. Dataset"); r.getEml().setCitation(citation); // publication date Calendar cal = Calendar.getInstance(); cal.set(2013, Calendar.JANUARY, 9); Date date = cal.getTime(); eml.setDateStamp(date); // creator Agent creator = new Agent(); creator.setFirstName("John"); creator.setLastName("Smith"); eml.addCreator(creator); // publisher Organisation o = new Organisation(); o.setName("GBIF"); o.setKey(UUID.randomUUID().toString()); r.setOrganisation(o); // resource must be publicly available r.setStatus(PublicationStatus.PUBLIC); action.setResource(r); assertNull(r.getDoi()); assertEquals(IdentifierStatus.UNRESERVED, r.getIdentifierStatus()); assertNotNull(r.getEml().getCitation()); assertNull(r.getEml().getCitation().getIdentifier()); } /** * Reserve DOI for resource that has never been assigned a DOI. */ @Test public void testReserveDoi() throws Exception { LOG.info("Testing " + type + "..."); action.setReserveDoi("true"); action.reserveDoi(); assertNotNull(r.getDoi()); assertEquals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION, r.getIdentifierStatus()); assertNotNull(r.getDoiOrganisationKey()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(1, r.getEml().getAlternateIdentifiers().size()); // alternate ids updated assertEquals(r.getDoi().getUrl().toString(), r.getEml().getCitation().getIdentifier()); // doi set as citation id LOG.info("DOI was reserved successfully, DOI=" + r.getDoi()); } /** * Test reserving existing DOI, making sure the DOI is preserved (user wants to reuse existing DOI). */ @Test public void testReuseAndReserveExistingDoi() throws Exception { LOG.info("Testing " + type + "..."); action.setReserveDoi("true"); action.reserveDoi(); assertNotNull(r.getDoi()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION, r.getIdentifierStatus()); assertEquals(1, r.getEml().getAlternateIdentifiers().size()); // alternate ids updated LOG.info("DOI was reserved successfully, DOI=" + r.getDoi()); DOI existingDOI = new DOI(r.getDoi().toString()); // reset DOI r.setDoi(null); r.setIdentifierStatus(IdentifierStatus.UNRESERVED); r.setDoiOrganisationKey(null); r.getEml().getAlternateIdentifiers().clear(); r.getEml().setCitation(null); // set citation identifier equal to DOI - this should get reused next time we reserve a DOI r.setCitationAutoGenerated(true); r.getEml().setCitation(new Citation("Replaced by auto-generated citation", existingDOI.toString())); action.reserveDoi(); // make sure the existing DOI was reused assertEquals(existingDOI.getDoiName(), r.getDoi().getDoiName()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION, r.getIdentifierStatus()); assertEquals(1, r.getEml().getAlternateIdentifiers().size()); // alternate ids updated assertEquals(r.getDoi().getUrl().toString(), r.getEml().getCitation().getIdentifier()); // doi set as citation id LOG.info("Existing DOI was reused successfully, DOI=" + existingDOI.getDoiName()); } /** * Test deleting reserved DOI, when the resource was never assigned a DOI before. */ @Test public void testDeleteReservedDoi() throws Exception { LOG.info("Testing " + type + "..."); action.setDeleteDoi("true"); action.reserveDoi(); assertNotNull(r.getDoi()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION, r.getIdentifierStatus()); assertEquals(1, r.getEml().getAlternateIdentifiers().size()); // alternate ids updated assertEquals(r.getDoi().getUrl().toString(), r.getEml().getCitation().getIdentifier()); // doi set as citation id assertFalse(r.isAlreadyAssignedDoi()); LOG.info("DOI was reserved successfully, DOI=" + r.getDoi()); action.deleteDoi(); // make sure the reserved DOI was deleted assertNull(r.getDoi()); assertNull(r.getDoiOrganisationKey()); assertEquals(IdentifierStatus.UNRESERVED, r.getIdentifierStatus()); assertEquals(0, r.getEml().getAlternateIdentifiers().size()); // alternate ids updated assertNull(r.getEml().getCitation().getIdentifier()); // doi set as citation id assertFalse(r.isAlreadyAssignedDoi()); LOG.info("Existing DOI was deleted successfully"); } /** * Test deleting reserved DOI, when the resource was previously assigned a DOI. */ @Test public void testDeleteReservedDoiWhenPreviousDoiExists() throws Exception { LOG.info("Testing " + type + "..."); action.setDeleteDoi("true"); // mock resource being assigned DOI DOI assignedDoi = new DOI("10.5072/bclona1"); r.setDoi(assignedDoi); r.setDoiOrganisationKey(ORGANISATION_KEY); r.setIdentifierStatus(IdentifierStatus.PUBLIC); User user = new User(); user.setEmail("jsmith@gbif.org"); VersionHistory history = new VersionHistory(new BigDecimal("1.0"), new Date(), PublicationStatus.PUBLIC); history.setModifiedBy(user); history.setDoi(assignedDoi); history.setStatus(IdentifierStatus.PUBLIC); r.addVersionHistory(history); assertNotNull(r.getDoi()); r.getEml().getCitation().setIdentifier(r.getDoi().getUrl().toString()); assertTrue(r.isAlreadyAssignedDoi()); // reserve new DOI for resource action.reserveDoi(); DOI reserved = r.getDoi(); assertNotNull(reserved); assertEquals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION, r.getIdentifierStatus()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(1, r.getEml().getAlternateIdentifiers().size()); // alternate ids updated assertEquals(reserved.toString(), r.getEml().getAlternateIdentifiers().get(0)); assertEquals(reserved.getUrl().toString(), r.getEml().getCitation().getIdentifier()); // new DOI set as citation id LOG.info("DOI was reserved successfully, DOI=" + reserved.toString()); action.deleteDoi(); // make sure the reserved DOI was deleted, and previous DOI reassigned assertEquals(assignedDoi.getDoiName(), r.getDoi().getDoiName()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(IdentifierStatus.PUBLIC, r.getIdentifierStatus()); assertEquals(1, r.getEml().getAlternateIdentifiers().size()); // alternate ids updated assertEquals("doi:10.5072/bclona1", r.getEml().getAlternateIdentifiers().get(0)); assertEquals("http://doi.org/10.5072/bclona1", r.getEml().getCitation().getIdentifier()); // citation id reset to previous DOI assertTrue(r.isAlreadyAssignedDoi()); LOG.info("Existing DOI was deleted successfully"); // for fun, try to publish resource having this deleted DOI - should not be possible!! r.setDoi(reserved); assertTrue(r.getDoi() != null && r.isPubliclyAvailable()); // reset action errors, .clear() doesn't work List<String> collection = Lists.newArrayList(); action.setActionErrors(collection); action.setPublish("true"); assertEquals("input", action.publish()); assertEquals(1, action.getActionErrors().size()); LOG.info("Publishing resource with deleted DOI failed as expected"); } /** * Ensure publishing fails (is prevented from starting) if DOI reserved cannot be resolved. */ @Test public void testPublishFailsBecauseDOICannotBeResolved() throws Exception { LOG.info("Testing " + type + "..."); // mock resource having DOI reserved that doesn't exist! DOI assignedDoi = DOIUtils.mintDOI(type, (type.equals(DOIRegistrationAgency.EZID) ? Constants.EZID_TEST_DOI_SHOULDER : Constants.TEST_DOI_PREFIX)); r.setDoi(assignedDoi); r.setDoiOrganisationKey(ORGANISATION_KEY); r.setIdentifierStatus(IdentifierStatus.PUBLIC_PENDING_PUBLICATION); assertTrue(r.getDoi() != null && r.isPubliclyAvailable()); // reset action errors, .clear() doesn't work List<String> collection = Lists.newArrayList(); action.setActionErrors(collection); action.setPublish("true"); assertEquals("input", action.publish()); assertEquals(1, action.getActionErrors().size()); LOG.info("Publishing resource with DOI that cannot be resolved failed as expected"); } /** * Test deleting resource that has been assigned multiple DOIs, and ensure that deletion deletes all reserved DOIs, * and deactivates all registered DOIs. * </br> * Then test undeleting the same resource, and ensure that all registered DOIs are reactivated. */ @Test public void testDeleteAndUndeleteResourceAssignedMultipleDOIs() throws Exception { LOG.info("Testing " + type + "..."); action.setReserveDoi("true"); action.reserveDoi(); assertNotNull(r.getDoi()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(1, r.getEml().getAlternateIdentifiers().size()); assertNotNull(r.getEml().getCitation()); assertEquals(r.getDoi().getUrl().toString(), r.getEml().getCitation().getIdentifier()); assertEquals(r.getDoi().getUrl().toString(), r.getEml().getCitation().getIdentifier()); DOI reserved1 = new DOI(r.getDoi().toString()); // reset r.setDoi(null); r.setDoiOrganisationKey(null); r.setIdentifierStatus(IdentifierStatus.UNRESERVED); r.getEml().getAlternateIdentifiers().clear(); r.getEml().setCitation(null); action.reserveDoi(); DOI reserved2 = new DOI(r.getDoi().toString()); // reset r.setDoi(null); r.setDoiOrganisationKey(null); r.setIdentifierStatus(IdentifierStatus.UNRESERVED); r.getEml().getAlternateIdentifiers().clear(); r.getEml().setCitation(null); action.reserveDoi(); DOI reserved3 = new DOI(r.getDoi().toString()); assertTrue(!reserved1.toString().equals(reserved2.toString()) && !reserved1.toString().equals(reserved3.toString()) && !reserved2.toString().equals(reserved3.toString())); // mock VersionHistory: version 1.0 and 1.1 share same registered DOI, version 2.0 has different registered DOI VersionHistory history1 = new VersionHistory(new BigDecimal("1.0"), new Date(), PublicationStatus.PUBLIC); history1.setDoi(reserved1); history1.setStatus(IdentifierStatus.PUBLIC); r.addVersionHistory(history1); VersionHistory history11 = new VersionHistory(new BigDecimal("1.1"), new Date(), PublicationStatus.PUBLIC); history11.setDoi(reserved1); history11.setStatus(IdentifierStatus.PUBLIC); r.addVersionHistory(history11); VersionHistory history2 = new VersionHistory(new BigDecimal("2.0"), new Date(), PublicationStatus.PUBLIC); history2.setDoi(reserved2); history2.setStatus(IdentifierStatus.PUBLIC); r.addVersionHistory(history2); assertEquals(3, r.getVersionHistory().size()); // ensure resource has reserved DOI assertTrue(r.getIdentifierStatus().equals(IdentifierStatus.PUBLIC_PENDING_PUBLICATION)); assertTrue(r.isAlreadyAssignedDoi()); assertTrue(r.isPubliclyAvailable()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); // mock resource having 3 alternate identifier DOIs r.getEml().getAlternateIdentifiers().clear(); r.getEml().getAlternateIdentifiers().add(reserved1.toString()); r.getEml().getAlternateIdentifiers().add(reserved2.toString()); r.getEml().getAlternateIdentifiers().add(reserved3.toString()); assertEquals(3, r.getEml().getAlternateIdentifiers().size()); // check citation identifier properly set assertNotNull(r.getEml().getCitation().getIdentifier()); assertEquals(reserved3.getUrl().toString(), r.getEml().getCitation().getIdentifier()); // delete! action.setDelete("true"); assertEquals("home", action.delete()); assertEquals(PublicationStatus.DELETED, r.getStatus()); assertEquals(IdentifierStatus.UNRESERVED, r.getIdentifierStatus()); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); // should still be 2 alternate identifiers left (only the reserved DOI gets deleted from alternate identifiers list) assertEquals(2, r.getEml().getAlternateIdentifiers().size()); // undelete! action.setUndelete("true"); // since this integration tests undeletes a reserved DOI, this only works in DataCite // TODO: test undelete in EZID if (type.equals(DOIRegistrationAgency.DATACITE)) { assertEquals("success", action.undelete()); assertEquals(PublicationStatus.PUBLIC, r.getStatus()); assertTrue(r.getIdentifierStatus().equals(IdentifierStatus.PUBLIC)); assertEquals(ORGANISATION_KEY, r.getDoiOrganisationKey()); assertEquals(2, r.getEml().getAlternateIdentifiers().size()); // DOI of last published version should be used as citation identifier assertEquals(reserved2.getUrl().toString(), r.getEml().getCitation().getIdentifier()); } } }