diff --git a/doi/README.md b/doi/README.md index 3491aeb..63cf2ee 100644 --- a/doi/README.md +++ b/doi/README.md @@ -21,20 +21,17 @@ See cadc-utilcadc-registry. ### doi.properties -The doi.properties configures the DataCite service used to register new DOI's. +The doi.properties configures the DataCite service used to register new DOIs. ``` -# Vault Service resourceID -ca.nrc.cadc.doi.vaultResourceID = {vault service resourceID} - -# Group Management Service (GMS) resourceID -ca.nrc.cadc.doi.gmsResourceID = {GMS service resourceID} - -# Path in vault to the parent folder containing the DOI's. -ca.nrc.cadc.doi.parentPath = {DOI parent folder path} +# VOSpace uri to the parent DOI folder. +ca.nrc.cadc.doi.vospaceParentUri = {parent folder URI} # Prefix to the DOI metadata file -ca.nrc.cadc.doi.metadataFilePrefix = {file prefix} +ca.nrc.cadc.doi.metaDataPrefix = {metadata file prefix} + +# Prefix to the DOI GMS Group name +ca.nrc.cadc.doi.groupPrefix = {group prefix} # DOI landing page url ca.nrc.cadc.doi.landingUrl = {landing page url} @@ -42,42 +39,40 @@ ca.nrc.cadc.doi.landingUrl = {landing page url} # DataCite MDS REST endpoint ca.nrc.cadc.doi.datacite.mdsUrl = {MDS url} -# DataCite account prefix -ca.nrc.cadc.doi.datacite.accountPrefix = {account prefix} - # DataCite account username ca.nrc.cadc.doi.datacite.username = {username} # DataCite account password ca.nrc.cadc.doi.datacite.password = {password} -``` - -_vaultResourceID_ the resourceID to the vault service used to store the DOI metadata and files. -_gmsResourceID_ the resourceID to the GMS service used for authentication and authorization. +# DataCite account prefix +ca.nrc.cadc.doi.datacite.accountPrefix = {account prefix} +``` -_parentPath_ is the path in the vault service to the DOI parent folder. +_parentUri_ is the URI to the DOI parent folder in the VOSpace service. -_metadataFilePrefix_ is the prefix prepended to the DOI name to create the filename for the DOI specific metadata stored in VOSpace. +_metaDataPrefix_ is the prefix prepended to the DOI name used to create the file for the DOI specific metadata stored in VOSpace. -_landingUrl_ is the base URL used to compose URLs to individual DOI's. +_groupPrefix_ is the prefix prepended to the DOI name to create the group name for the DOI. -_mdsUrl_ is the URL to the Datacite MDS rest endpoint used to create and update DOI's. +_landingUrl_ is the base URL used to compose URLs to individual DOIs. -_accountPrefix_ is the registered prefix for the DataCite account. +_mdsUrl_ is the URL to the DataCite MDS rest endpoint used to create and update DOIs. _username_ is the DataCite account username. _password_ is the DataCite account password. +_accountPrefix_ is the registered prefix for a DataCite account. + **For developer testing only:** ``` -# (optional) Create a random DOI name for testing -ca.nrc.cadc.doi.test.randomName = {true|false} - -# (optional) Group URI for a group that has read/write permissions to test DOI's -ca.nrc.cadc.doi.test.groupUri = {group URI} +# (optional) Create a random DOI ID for testing +ca.nrc.cadc.doi.randomTestID = {true|false} ``` + +_randomID_ is a flag to create a random DOI ID for testing purposes, to avoid conflicts with existing DOIs in VOSpace or DOI groups in GMS. + ### required certificates The following certificates are required to run the service, and are expected to be in the `/config` directory. @@ -113,10 +108,10 @@ Usage of this service can be divided into three distinct phases described below. ### DOI creation 1. user enters metadata of a publication using a GUI - title - - first auther + - first author - additional authors - journal reference -2. user submits the metdata +2. user submits the metadata 3. this service then: - validates the metadata - assigns a DOI for the metadata diff --git a/doi/src/intTest/README.md b/doi/src/intTest/README.md index 1979c1d..5169e2f 100644 --- a/doi/src/intTest/README.md +++ b/doi/src/intTest/README.md @@ -1,18 +1,21 @@ # doi service integration tests -The integration tests run against a local doi instance defined by the `ivo://opencadc.org/doi` resourceID, -and a vault service defined by the `ivo://opencadc.org/vault` resourceID'. +The integration tests can run against a local `doi` service, and either a local or remote VOSpace service. -Client test certificates in the `$A/test-certificates/` directory are used to authenticate to the doi service. -The following certificates are expected. -- `doiadmin.pem` owns and has full access to a test DOI. -- `doi-auth.pem` has read-write access to a test DOI. -- `doi-noauth.pem` has read-only access to a test DOI. +## configuration +A file called `intTest.properties` can be in the classpath (in `src/intTest/resources`) to override properties. -The integration tests expect the following entries in `doi.properties`. +### intTest.properties +``` +doiResourceID={resourceID of the doi service} +vospaceParentUri={VOSURI to the DOI parent folder in the VOSpace service} +``` -`ca.nrc.cadc.doi.test.randomName = true` to create random DOI names for testing. +**_vospaceParentUri_** must match `ca.nrc.cadc.doi.vospaceParentUri` configured in the doi service `doi.properties`. -`ca.nrc.cadc.doi.test.groupUri = {group URI}` to specify the group URI that will have read/write permissions to a test DOI. -The `doi-auth.pem` user is a member of this group, giving this user read/write access to a test DOI. -The `doi-noauth.pem` user is not a member of this group, giving this user read-only access to a test DOI. +### certificates +Client test certificates in the `$A/test-certificates/` directory are used to authenticate to the doi service. +The following certificates are expected. +- `doi-admin.pem` owns and has full permissions to the test DOI. +- `doi-auth.pem` is a member of a group that has read-write permissions to the test DOI. +- `doi-noauth.pem` is not a member of any group that has permissions to the test DOI, resulting in read-only permissions to the test DOI. diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/CreateTest.java b/doi/src/intTest/java/ca/nrc/cadc/doi/CreateTest.java deleted file mode 100644 index 7af2c1e..0000000 --- a/doi/src/intTest/java/ca/nrc/cadc/doi/CreateTest.java +++ /dev/null @@ -1,221 +0,0 @@ -/* -************************************************************************ -******************* CANADIAN ASTRONOMY DATA CENTRE ******************* -************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** -* -* (c) 2024. (c) 2024. -* Government of Canada Gouvernement du Canada -* National Research Council Conseil national de recherches -* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 -* All rights reserved Tous droits réservés -* -* NRC disclaims any warranties, Le CNRC dénie toute garantie -* expressed, implied, or énoncée, implicite ou légale, -* statutory, of any kind with de quelque nature que ce -* respect to the software, soit, concernant le logiciel, -* including without limitation y compris sans restriction -* any warranty of merchantability toute garantie de valeur -* or fitness for a particular marchande ou de pertinence -* purpose. NRC shall not be pour un usage particulier. -* liable in any event for any Le CNRC ne pourra en aucun cas -* damages, whether direct or être tenu responsable de tout -* indirect, special or general, dommage, direct ou indirect, -* consequential or incidental, particulier ou général, -* arising from the use of the accessoire ou fortuit, résultant -* software. Neither the name de l'utilisation du logiciel. Ni -* of the National Research le nom du Conseil National de -* Council of Canada nor the Recherches du Canada ni les noms -* names of its contributors may de ses participants ne peuvent -* be used to endorse or promote être utilisés pour approuver ou -* products derived from this promouvoir les produits dérivés -* software without specific prior de ce logiciel sans autorisation -* written permission. préalable et particulière -* par écrit. -* -* This file is part of the Ce fichier fait partie du projet -* OpenCADC project. OpenCADC. -* -* OpenCADC is free software: OpenCADC est un logiciel libre ; -* you can redistribute it and/or vous pouvez le redistribuer ou le -* modify it under the terms of modifier suivant les termes de -* the GNU Affero General Public la “GNU Affero General Public -* License as published by the License” telle que publiée -* Free Software Foundation, par la Free Software Foundation -* either version 3 of the : soit la version 3 de cette -* License, or (at your option) licence, soit (à votre gré) -* any later version. toute version ultérieure. -* -* OpenCADC is distributed in the OpenCADC est distribué -* hope that it will be useful, dans l’espoir qu’il vous -* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE -* without even the implied GARANTIE : sans même la garantie -* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ -* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF -* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence -* General Public License for Générale Publique GNU Affero -* more details. pour plus de détails. -* -* You should have received Vous devriez avoir reçu une -* a copy of the GNU Affero copie de la Licence Générale -* General Public License along Publique GNU Affero avec -* with OpenCADC. If not, see OpenCADC ; si ce n’est -* . pas le cas, consultez : -* . -* -* $Revision: 5 $ -* -************************************************************************ -*/ - -package ca.nrc.cadc.doi; - -import ca.nrc.cadc.doi.datacite.Resource; -import ca.nrc.cadc.doi.datacite.Title; -import ca.nrc.cadc.doi.io.DoiXmlReader; -import ca.nrc.cadc.doi.status.DoiStatus; -import ca.nrc.cadc.doi.status.DoiStatusListXmlReader; -import ca.nrc.cadc.doi.status.DoiStatusXmlReader; -import ca.nrc.cadc.doi.status.Status; -import ca.nrc.cadc.net.HttpGet; -import ca.nrc.cadc.util.Log4jInit; -import java.io.ByteArrayOutputStream; -import java.io.StringReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.List; -import javax.security.auth.Subject; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.junit.Assert; -import org.junit.Test; - -/** - */ -public class CreateTest extends IntTestBase { - private static final Logger log = Logger.getLogger(CreateTest.class); - - static final String JSON = "application/json"; - - static { - Log4jInit.setLevel("ca.nrc.cadc.doi", Level.INFO); - } - - @Test - public void createDOIAndStatusTest() throws Exception { - log.info("createDOIAndStatusTest"); - Subject.doAs(readWriteSubject, (PrivilegedExceptionAction) () -> { - // create new doi - Resource testResource = getTestResource(false, true, true); - String testXML = getResourceXML(testResource); - - String doiSuffix = null; - try { - // check that the service processed the document and added an identifier - String persistedXml = postDOI(doiServiceURL, testXML, TEST_JOURNAL_REF); - DoiXmlReader reader = new DoiXmlReader(); - Resource persistedResource = reader.read(persistedXml); - - String testIdentifier = testResource.getIdentifier().getValue(); - String persistedIdentifier = persistedResource.getIdentifier().getValue(); - Assert.assertNotEquals("New identifier not received from doi service.", - testIdentifier, persistedIdentifier); - doiSuffix = getDOISuffix(persistedIdentifier); - - // Get the DOI in JSON format - URL doiURL = new URL(String.format("%s/%s", doiServiceURL, doiSuffix)); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - HttpGet get = new HttpGet(doiURL, bos); - get.setRequestProperty("Accept", JSON); - get.prepare(); - Assert.assertNull("GET exception", get.getThrowable()); - Assert.assertEquals(JSON, get.getContentType()); - - // Get the DOI status - URL statusURL = new URL(String.format("%s/%s", doiURL, DoiAction.STATUS_ACTION)); - log.debug("statusURL: " + statusURL); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - HttpGet getStatus = new HttpGet(statusURL, baos); - getStatus.run(); - Assert.assertNull("GET exception", get.getThrowable()); - String status = baos.toString(StandardCharsets.UTF_8); - log.debug("status: " + status); - - DoiStatusXmlReader statusReader = new DoiStatusXmlReader(); - DoiStatus doiStatus = statusReader.read(new StringReader(status)); - - Assert.assertEquals("identifier mismatch", - persistedIdentifier, doiStatus.getIdentifier().getValue()); - String expectedDataDirectory = String.format("%s/%s/data", TestUtil.DOI_PARENT_PATH, doiSuffix); - Assert.assertEquals("dataDirectory mismatch", - expectedDataDirectory, doiStatus.getDataDirectory()); - Title expectedTitle = testResource.getTitles().get(0); - Assert.assertEquals("title mismatch", - expectedTitle.getValue(), doiStatus.getTitle().getValue()); - Assert.assertEquals("status mismatch", - Status.DRAFT, doiStatus.getStatus()); - Assert.assertEquals("journalRef mismatch", - TEST_JOURNAL_REF, doiStatus.journalRef); - } finally { - if (doiSuffix != null) { - cleanup(doiSuffix); - } - } - return null; - }); - } - - @Test - public void testGetStatusList() { - log.info("testGetStatusList"); - List testDOIList = new ArrayList<>(); - try { - // create test DOI's - testDOIList.add(createDOI(readWriteSubject)); - testDOIList.add(createDOI(readWriteSubject)); - testDOIList.add(createDOI(readWriteSubject)); - - // invoke the doi list service - List doiStatusList = getDoiStatusList(readWriteSubject); - Assert.assertEquals("test DOI list and status DOI list mismatch", - testDOIList.size(), doiStatusList.size()); - - for (String doiSuffix : testDOIList) { - boolean found = false; - for (DoiStatus doiStatus : doiStatusList) { - log.debug("actual doi: " + doiStatus.getIdentifier().getValue()); - if (doiStatus.getIdentifier().getValue().endsWith(doiSuffix)) { - log.debug("doisuffix match"); - found = true; - break; - } - } - if (!found) { - Assert.fail(String.format("doiSuffix %s not found in DOI status list", doiSuffix)); - } - } - } catch (Exception e) { - log.error("unexpected exception", e); - Assert.fail("unexpected exception: " + e.getMessage()); - } finally { - for (String doiSuffix : testDOIList) { - cleanup(doiSuffix); - } - } - } - - private List getDoiStatusList(Subject testSubject) - throws PrivilegedActionException { - return Subject.doAs(testSubject, (PrivilegedExceptionAction>) () -> { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - HttpGet get = new HttpGet(doiServiceURL, bos); - get.setRequestProperty("Accept", "text/xml"); - get.run(); - DoiStatusListXmlReader reader = new DoiStatusListXmlReader(); - return reader.read(new StringReader(bos.toString(StandardCharsets.UTF_8))); - }); - } - -} \ No newline at end of file diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/InitFolderTest.java b/doi/src/intTest/java/ca/nrc/cadc/doi/InitFolderTest.java deleted file mode 100644 index 84c2caf..0000000 --- a/doi/src/intTest/java/ca/nrc/cadc/doi/InitFolderTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* -************************************************************************ -******************* CANADIAN ASTRONOMY DATA CENTRE ******************* -************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** -* -* (c) 2024. (c) 2024. -* Government of Canada Gouvernement du Canada -* National Research Council Conseil national de recherches -* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 -* All rights reserved Tous droits réservés -* -* NRC disclaims any warranties, Le CNRC dénie toute garantie -* expressed, implied, or énoncée, implicite ou légale, -* statutory, of any kind with de quelque nature que ce -* respect to the software, soit, concernant le logiciel, -* including without limitation y compris sans restriction -* any warranty of merchantability toute garantie de valeur -* or fitness for a particular marchande ou de pertinence -* purpose. NRC shall not be pour un usage particulier. -* liable in any event for any Le CNRC ne pourra en aucun cas -* damages, whether direct or être tenu responsable de tout -* indirect, special or general, dommage, direct ou indirect, -* consequential or incidental, particulier ou général, -* arising from the use of the accessoire ou fortuit, résultant -* software. Neither the name de l'utilisation du logiciel. Ni -* of the National Research le nom du Conseil National de -* Council of Canada nor the Recherches du Canada ni les noms -* names of its contributors may de ses participants ne peuvent -* be used to endorse or promote être utilisés pour approuver ou -* products derived from this promouvoir les produits dérivés -* software without specific prior de ce logiciel sans autorisation -* written permission. préalable et particulière -* par écrit. -* -* This file is part of the Ce fichier fait partie du projet -* OpenCADC project. OpenCADC. -* -* OpenCADC is free software: OpenCADC est un logiciel libre ; -* you can redistribute it and/or vous pouvez le redistribuer ou le -* modify it under the terms of modifier suivant les termes de -* the GNU Affero General Public la “GNU Affero General Public -* License as published by the License” telle que publiée -* Free Software Foundation, par la Free Software Foundation -* either version 3 of the : soit la version 3 de cette -* License, or (at your option) licence, soit (à votre gré) -* any later version. toute version ultérieure. -* -* OpenCADC is distributed in the OpenCADC est distribué -* hope that it will be useful, dans l’espoir qu’il vous -* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE -* without even the implied GARANTIE : sans même la garantie -* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ -* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF -* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence -* General Public License for Générale Publique GNU Affero -* more details. pour plus de détails. -* -* You should have received Vous devriez avoir reçu une -* a copy of the GNU Affero copie de la Licence Générale -* General Public License along Publique GNU Affero avec -* with OpenCADC. If not, see OpenCADC ; si ce n’est -* . pas le cas, consultez : -* . -* -* $Revision: 5 $ -* -************************************************************************ -*/ - -package ca.nrc.cadc.doi; - -import ca.nrc.cadc.doi.datacite.Resource; -import ca.nrc.cadc.net.FileContent; -import ca.nrc.cadc.net.HttpPost; -import ca.nrc.cadc.util.Log4jInit; -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.security.AccessControlException; -import java.security.PrivilegedExceptionAction; -import java.util.HashMap; -import java.util.Map; -import javax.security.auth.Subject; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.junit.Assert; -import org.junit.Test; -import org.opencadc.vospace.DataNode; -import org.opencadc.vospace.VOS; -import org.opencadc.vospace.VOSURI; -import org.opencadc.vospace.client.ClientTransfer; -import org.opencadc.vospace.transfer.Direction; -import org.opencadc.vospace.transfer.Protocol; -import org.opencadc.vospace.transfer.Transfer; - -/** - */ -public class InitFolderTest extends IntTestBase { - private static final Logger log = Logger.getLogger(InitFolderTest.class); - - static { - Log4jInit.setLevel("ca.nrc.cadc.doi", Level.INFO); - } - - /** - * new folder is created - calling user should have read access but not write - * new XML file is created - calling user should have read access - * data folder created - calling user should have write access - */ - @Test - public void testInitDoi() throws Exception { - log.info("testInitDoi"); - Resource testResource = getTestResource(false, true, true); - final String testXML = getResourceXML(testResource); - - // Create the folder for the test, and the initial XML file - Subject.doAs(readWriteSubject, (PrivilegedExceptionAction) () -> { - String doiSuffix = null; - try { - FileContent fileContent = new FileContent(testXML, "text/xml", StandardCharsets.UTF_8); - Map params = new HashMap<>(); - params.put("doiMetadata", fileContent); - params.put("journalref", TEST_JOURNAL_REF); - HttpPost post = new HttpPost(doiServiceURL, params, false); - post.run(); - - Assert.assertNull("POST exception", post.getThrowable()); - Assert.assertEquals("expected 303 response code", 303, post.getResponseCode()); - String doiPath = post.getRedirectURL().getPath(); - log.debug("redirectURL path: " + doiPath); - - // Folder name should be /AstroDataCitationDOI/CISTI.CANFAR/ - String[] doiNumberParts = doiPath.split("/"); - doiSuffix = doiNumberParts[3]; - - String dataNodeName = String.format("%s/data", doiSuffix); - log.debug("write to data folder " + dataNodeName); - - // Test writing to the data directory - String fileName = "doi-test-write-file.txt"; - String dataFileToWrite = dataNodeName + "/" + fileName; - - VOSURI target = getVOSURI(dataFileToWrite); - DataNode dataNode = new DataNode(fileName); - log.debug("PUT target:" + target.getURI()); - vosClient.createNode(target, dataNode); - - Transfer transfer = new Transfer(target.getURI(), Direction.pushToVoSpace); - Protocol put = new Protocol(VOS.PROTOCOL_HTTPS_PUT); - transfer.getProtocols().add(put); - - log.debug("file to be written: " + dataFileToWrite); - ClientTransfer clientTransfer = vosClient.createTransfer(transfer); - File testFile = new File("src/intTest/resources/" + fileName); - clientTransfer.setFile(testFile); - clientTransfer.run(); - - // Check that file is there. - String newFilePath = target.getPath(); - vosClient.getNode(newFilePath); - - // Test that read only subject CAN'T write to the same folder - String writeName = "doi-test-write-failed.txt"; - final String writeFile = dataNodeName + "/" + writeName; - - // Try to write to data directory as read only subject - Subject.doAs(readOnlySubject, (PrivilegedExceptionAction) () -> { - log.debug("write as read only subject"); - try { - VOSURI target1 = getVOSURI(writeFile); - DataNode dataNode1 = new DataNode(writeName); - vosClient.createNode(target1, dataNode1); - } catch (AccessControlException e) { - log.debug("expected exception: " + e.getMessage()); - } catch (Exception e) { - Assert.fail("exception writing file: " + e.getMessage()); - } - return null; - }); - return null; - } finally { - if (doiSuffix != null) { - cleanup(doiSuffix); - } - } - }); - } - -} \ No newline at end of file diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/IntTestBase.java b/doi/src/intTest/java/ca/nrc/cadc/doi/IntTestBase.java index a86ae9c..8cf8c22 100644 --- a/doi/src/intTest/java/ca/nrc/cadc/doi/IntTestBase.java +++ b/doi/src/intTest/java/ca/nrc/cadc/doi/IntTestBase.java @@ -69,11 +69,9 @@ package ca.nrc.cadc.doi; -import ca.nrc.cadc.ac.client.GMSClient; import ca.nrc.cadc.auth.AuthMethod; import ca.nrc.cadc.auth.SSLUtil; import ca.nrc.cadc.doi.datacite.Resource; -import ca.nrc.cadc.doi.io.DoiXmlReader; import ca.nrc.cadc.doi.io.DoiXmlWriter; import ca.nrc.cadc.net.FileContent; import ca.nrc.cadc.net.HttpPost; @@ -96,6 +94,7 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.opencadc.vospace.ContainerNode; +import org.opencadc.vospace.DataNode; import org.opencadc.vospace.VOSURI; import org.opencadc.vospace.client.VOSpaceClient; import org.opencadc.vospace.client.async.RecursiveDeleteNode; @@ -108,6 +107,8 @@ public abstract class IntTestBase extends TestBase { private static final Logger log = Logger.getLogger(IntTestBase.class); + static final String JSON = "application/json"; + static final String XML = "text/xml"; static final String TEST_JOURNAL_REF = "2018, Test Journal ref. ApJ 1000,100"; static Subject adminSubject; @@ -129,12 +130,12 @@ public static void staticInit() { RegistryClient regClient = new RegistryClient(); doiServiceURL = regClient.getServiceURL(TestUtil.DOI_RESOURCE_ID, Standards.DOI_INSTANCES_10, AuthMethod.CERT); - doiParentPathURI = new VOSURI(TestUtil.VAULT_RESOURCE_ID, TestUtil.DOI_PARENT_PATH); - vosClient = new VOSpaceClient(TestUtil.VAULT_RESOURCE_ID); + doiParentPathURI = new VOSURI(TestUtil.VOSPACE_RESOURCE_ID, TestUtil.DOI_PARENT_PATH); + vosClient = new VOSpaceClient(TestUtil.VOSPACE_RESOURCE_ID); } protected VOSURI getVOSURI(String path) { - return new VOSURI(TestUtil.VAULT_RESOURCE_ID, String.format("%s/%s", TestUtil.DOI_PARENT_PATH, path)); + return new VOSURI(TestUtil.VOSPACE_RESOURCE_ID, String.format("%s/%s", TestUtil.DOI_PARENT_PATH, path)); } protected String getDOISuffix(String doiIdentifier) { @@ -152,6 +153,18 @@ protected String getResourceXML(Resource resource) throws IOException { return sb.toString(); } + protected ContainerNode createContainerNode(String path, String name) throws Exception { + ContainerNode node = new ContainerNode(name); + VOSURI nodeURI = getVOSURI(path); + return (ContainerNode) vosClient.createNode(nodeURI, node); + } + + protected DataNode createDataNode(String path, String name) throws Exception { + DataNode node = new DataNode(name); + VOSURI nodeURI = getVOSURI(path); + return (DataNode) vosClient.createNode(nodeURI, node); + } + protected ContainerNode getContainerNode(String path) throws Exception { String nodePath = doiParentPathURI.getPath(); if (StringUtil.hasText(path)) { @@ -160,23 +173,11 @@ protected ContainerNode getContainerNode(String path) throws Exception { return (ContainerNode) vosClient.getNode(nodePath); } - protected String createDOI(Subject testSubject) - throws IOException, PrivilegedActionException { - Resource resource = getTestResource(false, false, true); - final String doiXML = getResourceXML(resource); - return (String) Subject.doAs(testSubject, (PrivilegedExceptionAction) () -> { - String persistedXML = postDOI(doiServiceURL, doiXML, TEST_JOURNAL_REF); - DoiXmlReader reader = new DoiXmlReader(); - Resource persistedResource = reader.read(persistedXML); - return getDOISuffix(persistedResource.getIdentifier().getValue()); - }); - } - protected String postDOI(URL postUrl, String doiXML, String journalRef) throws Exception { Map params = new HashMap<>(); if (StringUtil.hasText(doiXML)) { - FileContent fileContent = new FileContent(doiXML, "text/xml", StandardCharsets.UTF_8); + FileContent fileContent = new FileContent(doiXML, XML, StandardCharsets.UTF_8); params.put("doiMetadata", fileContent); } if (journalRef != null) { @@ -187,15 +188,15 @@ protected String postDOI(URL postUrl, String doiXML, String journalRef) post.prepare(); Assert.assertNull("POST exception", post.getThrowable()); - Assert.assertEquals("non 200 response code", post.getResponseCode(), 200); + Assert.assertEquals("non 200 response code", 200, post.getResponseCode()); return new String(post.getInputStream().readAllBytes(), StandardCharsets.UTF_8); } protected void cleanup(String doiSuffix) { - // delete doi as doiadmin + // delete doi as admin try { Subject.doAs(adminSubject, (PrivilegedExceptionAction) () -> { - log.debug("cleanup as doiadmin"); + log.debug("cleanup as doi admin"); try { VOSURI nodeUri = getVOSURI(doiSuffix); log.debug("recursiveDeleteNode: " + nodeUri); diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/LandingPageTest.java b/doi/src/intTest/java/ca/nrc/cadc/doi/LandingPageTest.java deleted file mode 100644 index eb35140..0000000 --- a/doi/src/intTest/java/ca/nrc/cadc/doi/LandingPageTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - ************************************************************************ - ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* - ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** - * - * (c) 2024. (c) 2024. - * Government of Canada Gouvernement du Canada - * National Research Council Conseil national de recherches - * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 - * All rights reserved Tous droits réservés - * - * NRC disclaims any warranties, Le CNRC dénie toute garantie - * expressed, implied, or énoncée, implicite ou légale, - * statutory, of any kind with de quelque nature que ce - * respect to the software, soit, concernant le logiciel, - * including without limitation y compris sans restriction - * any warranty of merchantability toute garantie de valeur - * or fitness for a particular marchande ou de pertinence - * purpose. NRC shall not be pour un usage particulier. - * liable in any event for any Le CNRC ne pourra en aucun cas - * damages, whether direct or être tenu responsable de tout - * indirect, special or general, dommage, direct ou indirect, - * consequential or incidental, particulier ou général, - * arising from the use of the accessoire ou fortuit, résultant - * software. Neither the name de l'utilisation du logiciel. Ni - * of the National Research le nom du Conseil National de - * Council of Canada nor the Recherches du Canada ni les noms - * names of its contributors may de ses participants ne peuvent - * be used to endorse or promote être utilisés pour approuver ou - * products derived from this promouvoir les produits dérivés - * software without specific prior de ce logiciel sans autorisation - * written permission. préalable et particulière - * par écrit. - * - * This file is part of the Ce fichier fait partie du projet - * OpenCADC project. OpenCADC. - * - * OpenCADC is free software: OpenCADC est un logiciel libre ; - * you can redistribute it and/or vous pouvez le redistribuer ou le - * modify it under the terms of modifier suivant les termes de - * the GNU Affero General Public la “GNU Affero General Public - * License as published by the License” telle que publiée - * Free Software Foundation, par la Free Software Foundation - * either version 3 of the : soit la version 3 de cette - * License, or (at your option) licence, soit (à votre gré) - * any later version. toute version ultérieure. - * - * OpenCADC is distributed in the OpenCADC est distribué - * hope that it will be useful, dans l’espoir qu’il vous - * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE - * without even the implied GARANTIE : sans même la garantie - * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ - * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF - * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence - * General Public License for Générale Publique GNU Affero - * more details. pour plus de détails. - * - * You should have received Vous devriez avoir reçu une - * a copy of the GNU Affero copie de la Licence Générale - * General Public License along Publique GNU Affero avec - * with OpenCADC. If not, see OpenCADC ; si ce n’est - * . pas le cas, consultez : - * . - * - * : 5 $ - * - ************************************************************************ - */ - -package ca.nrc.cadc.doi; - -import ca.nrc.cadc.auth.AuthMethod; -import ca.nrc.cadc.auth.AuthenticationUtil; -import ca.nrc.cadc.net.HttpGet; -import ca.nrc.cadc.reg.Standards; -import ca.nrc.cadc.reg.client.RegistryClient; -import ca.nrc.cadc.util.Log4jInit; -import java.net.URL; -import java.security.PrivilegedExceptionAction; -import javax.security.auth.Subject; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -public class LandingPageTest extends IntTestBase { - private static final Logger log = Logger.getLogger(LandingPageTest.class); - - static { - Log4jInit.setLevel("ca.nrc.cadc.doi", Level.INFO); - } - - private static final String TEST_DOI = "13.0001"; - private static URL prodDoiServiceURL; - - public LandingPageTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception { - RegistryClient regClient = new RegistryClient(); - prodDoiServiceURL = regClient.getServiceURL(TestUtil.PROD_DOI_RESOURCE_ID, Standards.DOI_INSTANCES_10, AuthMethod.CERT); - } - - @Test - public void anonGetTest() { - log.info("anonGetTest"); - try { - URL doiURL = new URL(prodDoiServiceURL.toExternalForm() + "/" + TEST_DOI + "/status/public"); - log.debug("test url: " + doiURL.toExternalForm()); - Subject testSubject = AuthenticationUtil.getAnonSubject(); - - Subject.doAs(testSubject, (PrivilegedExceptionAction) () -> { - HttpGet get = new HttpGet(doiURL, true); - get.prepare(); - - Assert.assertEquals(200, get.getResponseCode()); - return null; - }); - - } catch (Exception e) { - log.error("Unexpected error", e); - Assert.fail("Unexpected error: " + e); - } - } - - @Test - public void authGetTest() { - log.info("authGetTest"); - try { - log.debug("doi instances url: " + doiServiceURL); - URL doiURL = new URL(prodDoiServiceURL.toExternalForm() + "/" + TEST_DOI + "/status/public"); - log.debug("test url: " + doiURL.toExternalForm()); - - Subject.doAs(adminSubject, (PrivilegedExceptionAction) () -> { - HttpGet get = new HttpGet(doiURL, true); - get.prepare(); - - Assert.assertEquals(200, get.getResponseCode()); - return null; - }); - - } catch (Exception e) { - log.error("Unexpected error", e); - Assert.fail("Unexpected error: " + e); - } - } - -} diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/LifecycleTest.java b/doi/src/intTest/java/ca/nrc/cadc/doi/LifecycleTest.java new file mode 100644 index 0000000..1fb4ac2 --- /dev/null +++ b/doi/src/intTest/java/ca/nrc/cadc/doi/LifecycleTest.java @@ -0,0 +1,472 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2024. (c) 2024. + * Government of Canada Gouvernement du Canada + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits réservés + * + * NRC disclaims any warranties, Le CNRC dénie toute garantie + * expressed, implied, or énoncée, implicite ou légale, + * statutory, of any kind with de quelque nature que ce + * respect to the software, soit, concernant le logiciel, + * including without limitation y compris sans restriction + * any warranty of merchantability toute garantie de valeur + * or fitness for a particular marchande ou de pertinence + * purpose. NRC shall not be pour un usage particulier. + * liable in any event for any Le CNRC ne pourra en aucun cas + * damages, whether direct or être tenu responsable de tout + * indirect, special or general, dommage, direct ou indirect, + * consequential or incidental, particulier ou général, + * arising from the use of the accessoire ou fortuit, résultant + * software. Neither the name de l'utilisation du logiciel. Ni + * of the National Research le nom du Conseil National de + * Council of Canada nor the Recherches du Canada ni les noms + * names of its contributors may de ses participants ne peuvent + * be used to endorse or promote être utilisés pour approuver ou + * products derived from this promouvoir les produits dérivés + * software without specific prior de ce logiciel sans autorisation + * written permission. préalable et particulière + * par écrit. + * + * This file is part of the Ce fichier fait partie du projet + * OpenCADC project. OpenCADC. + * + * OpenCADC is free software: OpenCADC est un logiciel libre ; + * you can redistribute it and/or vous pouvez le redistribuer ou le + * modify it under the terms of modifier suivant les termes de + * the GNU Affero General Public la “GNU Affero General Public + * License as published by the License” telle que publiée + * Free Software Foundation, par la Free Software Foundation + * either version 3 of the : soit la version 3 de cette + * License, or (at your option) licence, soit (à votre gré) + * any later version. toute version ultérieure. + * + * OpenCADC is distributed in the OpenCADC est distribué + * hope that it will be useful, dans l’espoir qu’il vous + * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE + * without even the implied GARANTIE : sans même la garantie + * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ + * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF + * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence + * General Public License for Générale Publique GNU Affero + * more details. pour plus de détails. + * + * You should have received Vous devriez avoir reçu une + * a copy of the GNU Affero copie de la Licence Générale + * General Public License along Publique GNU Affero avec + * with OpenCADC. If not, see OpenCADC ; si ce n’est + * . pas le cas, consultez : + * . + * + * : 5 $ + * + ************************************************************************ + */ + +package ca.nrc.cadc.doi; + +import ca.nrc.cadc.doi.datacite.Creator; +import ca.nrc.cadc.doi.datacite.Date; +import ca.nrc.cadc.doi.datacite.DateType; +import ca.nrc.cadc.doi.datacite.Language; +import ca.nrc.cadc.doi.datacite.Resource; +import ca.nrc.cadc.doi.datacite.Title; +import ca.nrc.cadc.doi.datacite.TitleType; +import ca.nrc.cadc.doi.io.DoiXmlReader; +import ca.nrc.cadc.doi.status.DoiStatus; +import ca.nrc.cadc.doi.status.DoiStatusXmlReader; +import ca.nrc.cadc.doi.status.Status; +import ca.nrc.cadc.net.FileContent; +import ca.nrc.cadc.net.HttpGet; +import ca.nrc.cadc.net.HttpPost; +import ca.nrc.cadc.util.Log4jInit; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.StringReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.AccessControlException; +import java.security.PrivilegedExceptionAction; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.security.auth.Subject; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; +import org.opencadc.vospace.ContainerNode; +import org.opencadc.vospace.DataNode; +import org.opencadc.vospace.VOS; +import org.opencadc.vospace.VOSURI; +import org.opencadc.vospace.client.ClientTransfer; +import org.opencadc.vospace.transfer.Direction; +import org.opencadc.vospace.transfer.Protocol; +import org.opencadc.vospace.transfer.Transfer; + +public class LifecycleTest extends IntTestBase { + + private static final Logger log = Logger.getLogger(LifecycleTest.class); + static { + Log4jInit.setLevel("ca.nrc.cadc.doi", Level.INFO); + } + + @Test + public void testLifecycle() throws Exception { + log.debug("testLifecycle()"); + + // Create a new DOI + Resource expected = getTestResource(true, true, true); + + String doiSuffix = Subject.doAs(readWriteSubject, (PrivilegedExceptionAction) () -> { + + // create a new DOI + Resource actual = create(expected); + String doiID = getDOISuffix(actual.getIdentifier().getValue()); + + // update the DOI + update(actual, doiID); + + // publish the DOI + publish(actual, doiID); + + return doiID; + }); + } + + Resource create(Resource expected) throws Exception { + + // Create the folder for the test, and the initial XML file + String doiXML = getResourceXML(expected); + FileContent fileContent = new FileContent(doiXML, XML, StandardCharsets.UTF_8); + Map params = new HashMap<>(); + params.put("doiMetadata", fileContent); + params.put("journalref", TEST_JOURNAL_REF); + HttpPost post = new HttpPost(doiServiceURL, params, false); + post.run(); + + Assert.assertNull("POST exception", post.getThrowable()); + Assert.assertEquals("expected 303 response code", 303, post.getResponseCode()); + String doiPath = post.getRedirectURL().getPath(); + log.debug("redirectURL path: " + doiPath); + + // get the doi suffix + int index = doiPath.lastIndexOf("/"); + String pathDoiSuffix = doiPath.substring(index + 1); + + // Get the new DOI in XML format + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + HttpGet get = new HttpGet(post.getRedirectURL(), bos); + get.setRequestProperty("Accept", XML); + get.run(); + Assert.assertNull("GET exception", get.getThrowable()); + Assert.assertEquals("expected 200 response code", 200, get.getResponseCode()); + + // actual resource + DoiXmlReader reader = new DoiXmlReader(); + Resource actual = reader.read(bos.toString(StandardCharsets.UTF_8)); + + String expectedIdentifier = expected.getIdentifier().getValue(); + String actualIdentifier = actual.getIdentifier().getValue(); + Assert.assertNotEquals("New identifier not received from doi service.", + expectedIdentifier, actualIdentifier); + compareResource(expected, actual, false); + + String doiSuffix = getDOISuffix(actualIdentifier); + Assert.assertEquals("DOI suffix", pathDoiSuffix, doiSuffix); + + // Get the DOI in JSON format + URL doiURL = new URL(String.format("%s/%s", doiServiceURL, doiSuffix)); + bos = new ByteArrayOutputStream(); + get = new HttpGet(doiURL, bos); + get.setRequestProperty("Accept", JSON); + get.prepare(); + Assert.assertNull("GET exception", get.getThrowable()); + Assert.assertEquals(JSON, get.getContentType()); + + // Get the DOI status + URL statusURL = new URL(String.format("%s/%s", doiURL, DoiAction.STATUS_ACTION)); + log.debug("statusURL: " + statusURL); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + HttpGet getStatus = new HttpGet(statusURL, baos); + getStatus.run(); + Assert.assertNull("GET exception", get.getThrowable()); + String status = baos.toString(StandardCharsets.UTF_8); + log.debug("status: " + status); + + DoiStatusXmlReader statusReader = new DoiStatusXmlReader(); + DoiStatus doiStatus = statusReader.read(new StringReader(status)); + + Assert.assertEquals("identifier mismatch", actualIdentifier, doiStatus.getIdentifier().getValue()); + String expectedDataDirectory = String.format("%s/%s/data", TestUtil.DOI_PARENT_PATH, doiSuffix); + Assert.assertEquals("dataDirectory mismatch", expectedDataDirectory, doiStatus.getDataDirectory()); + Title expectedTitle = expected.getTitles().get(0); + Assert.assertEquals("title mismatch", expectedTitle.getValue(), doiStatus.getTitle().getValue()); + Assert.assertEquals("status mismatch", Status.DRAFT, doiStatus.getStatus()); + Assert.assertEquals("journalRef mismatch", TEST_JOURNAL_REF, doiStatus.journalRef); + + // Test writing to the data directory + String dataNodeName = String.format("%s/data", doiSuffix); + log.debug("write to data folder " + dataNodeName); + String fileName = "doi-test-write-file.txt"; + String dataFileToWrite = dataNodeName + "/" + fileName; + + VOSURI target = getVOSURI(dataFileToWrite); + DataNode dataNode = new DataNode(fileName); + log.debug("PUT target:" + target.getURI()); + vosClient.createNode(target, dataNode); + + Transfer transfer = new Transfer(target.getURI(), Direction.pushToVoSpace); + Protocol put = new Protocol(VOS.PROTOCOL_HTTPS_PUT); + transfer.getProtocols().add(put); + + log.debug("file to be written: " + dataFileToWrite); + ClientTransfer clientTransfer = vosClient.createTransfer(transfer); + File testFile = new File("src/intTest/resources/" + fileName); + clientTransfer.setFile(testFile); + clientTransfer.run(); + + // Check that file is there. + String newFilePath = target.getPath(); + vosClient.getNode(newFilePath); + + // Test that read only subject CAN'T write to the same folder + String writeName = "doi-test-write-failed.txt"; + final String writeFile = dataNodeName + "/" + writeName; + + // Try to write to data directory as read only subject + Subject.doAs(readOnlySubject, (PrivilegedExceptionAction) () -> { + log.debug("write as read only subject"); + try { + VOSURI target1 = getVOSURI(writeFile); + DataNode dataNode1 = new DataNode(writeName); + vosClient.createNode(target1, dataNode1); + } catch ( + AccessControlException e) { + log.debug("expected exception: " + e.getMessage()); + } catch (Exception e) { + Assert.fail("exception writing file: " + e.getMessage()); + } + return null; + }); + return actual; + } + + void update(Resource expected, String doiSuffix) throws Exception { + // For DOI tests below + URL doiURL = new URL(String.format("%s/%s", doiServiceURL, doiSuffix)); + + // Only user editable fields are language, publicationYear, creators, titles + expected.language = new Language("en-US"); + expected.getPublicationYear().setValue("2024"); + Creator creator = new Creator(getCreatorName("foo, bar", true)); + expected.getCreators().add(creator); + Title title = new Title("Updated Title"); + title.titleType = TitleType.OTHER; + expected.getTitles().add(title); + + // Update the DOI + Resource actual = doUpdateTest(expected, doiURL); + compareResource(expected, actual, true); + + // remove updated properties + expected.getCreators().remove(creator); + expected.getTitles().remove(title); + + // Update the DOI + actual = doUpdateTest(expected, doiURL); + compareResource(expected, actual, true); + } + + void publish(Resource expected, String doiSuffix) throws Exception { + // For DOI tests below + URL doiURL = new URL(String.format("%s/%s", doiServiceURL, doiSuffix)); + + // verify the DOI containerNode properties + ContainerNode doiNode = getContainerNode(doiSuffix); + Assert.assertFalse("incorrect isPublic property", + doiNode.isPublic != null && doiNode.isPublic); + Assert.assertFalse("should have group read property", + doiNode.getReadOnlyGroup().isEmpty()); + ContainerNode dataNode = getContainerNode(doiSuffix + "/data"); + Assert.assertFalse("should have group write", + dataNode.getReadWriteGroup().isEmpty()); + + // add a file and a subdirectory with a file to the data directory + String testFile1 = "test-file-1.txt"; + String testFile1Path = String.format("%s/data/%s", doiSuffix, testFile1); + DataNode testFileNode = createDataNode(testFile1Path, testFile1); + + String subDir = "subDir"; + String subDirPath = String.format("%s/data/%s", doiSuffix, subDir); + ContainerNode dataSubDirNode = createContainerNode(subDirPath, subDir); + + String testFile2 = "test-file-2.txt"; + String testFile2Path = String.format("%s/data/%s/%s", doiSuffix, subDir, testFile2); + DataNode testFile2Node = createDataNode(testFile2Path, testFile2); + + // mint the document, DRAFT ==> LOCKING_DATA + doMintTest(doiURL); + doiNode = getContainerNode(doiSuffix); + dataNode = getContainerNode(doiSuffix + "/data"); + dataSubDirNode = getContainerNode(doiSuffix + "/data/" + subDir); + Assert.assertEquals("incorrect status", + Status.LOCKING_DATA.getValue(), doiNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); + verifyNodeProperties(doiNode, dataNode, dataSubDirNode); + log.debug("locking data"); + + // mint the document, ERROR_LOCKING_DATA ==> LOCKING_DATA + doiNode.getProperty(DoiAction.DOI_VOS_STATUS_PROP).setValue(Status.ERROR_LOCKING_DATA.getValue()); + VOSURI vosuri = getVOSURI(doiNode.getName()); + vosClient.setNode(vosuri, doiNode); + doMintTest(doiURL); + doiNode = getContainerNode(doiSuffix); + dataNode = getContainerNode(doiSuffix + "/data"); + dataSubDirNode = getContainerNode(doiSuffix + "/data/" + subDir); + Assert.assertEquals("incorrect status", + Status.LOCKING_DATA.getValue(), doiNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); + verifyNodeProperties(doiNode, dataNode, dataSubDirNode); + log.debug("locking data again"); + + // getStatus() changes LOCKING_DATA == > LOCKED_DATA + DoiStatus doiStatus = getStatus(doiURL); + Assert.assertEquals("identifier from DOI status is different", + expected.getIdentifier().getValue(), doiStatus.getIdentifier().getValue()); + Assert.assertEquals("status is incorrect", Status.LOCKED_DATA, doiStatus.getStatus()); + verifyLockedDataPropertyChanges(doiNode, dataNode, dataSubDirNode); + log.debug("locked data"); + + // mint the document, LOCKED_DATA == REGISTERING + doMintTest(doiURL); + doiNode = getContainerNode(doiSuffix); + dataNode = getContainerNode(doiSuffix + "/data"); + dataSubDirNode = getContainerNode(doiSuffix + "/data/" + subDir); + Assert.assertEquals("incorrect status", + Status.MINTED.getValue(), doiNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); + verifyMintedStatePropertyChanges(doiNode, dataNode, dataSubDirNode); + log.debug("registering"); + + // mint the document, ERROR_REGISTERING ==> REGISTERING + // the doiContainerNode doesn't have group read & write anymore, and is owned + // by doi admin, so changes to it must be done with that cert. + doiNode.getProperty(DoiAction.DOI_VOS_STATUS_PROP).setValue(Status.ERROR_REGISTERING.getValue()); + ContainerNode doiParentNode = doiNode; + Subject.doAs(adminSubject, (PrivilegedExceptionAction) () -> { + VOSURI parentVOSURI = getVOSURI(doiParentNode.getName()); + vosClient.setNode(parentVOSURI, doiParentNode); + return null; + }); + log.debug("registering again"); + + doMintTest(doiURL); + doiNode = getContainerNode(doiSuffix); + dataNode = getContainerNode(doiSuffix + "/data"); + dataSubDirNode = getContainerNode(doiSuffix + "/data/" + subDir); + Assert.assertEquals("incorrect status", + Status.MINTED.getValue(), doiNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); + verifyMintedStatePropertyChanges(doiNode, dataNode, dataSubDirNode); + + // getStatus() changes REGISTERING == > MINTED + doiStatus = getStatus(doiURL); + Assert.assertEquals("identifier from DOI status is different", + expected.getIdentifier().getValue(), doiStatus.getIdentifier().getValue()); + Assert.assertEquals("status is incorrect", Status.MINTED, doiStatus.getStatus()); + + // verify the DOI containerNode properties + Assert.assertEquals("incorrect status", Status.MINTED.getValue(), doiNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); + } + + @Override + protected List getDates(boolean optionalAttributes) { + List dates = new ArrayList<>(); + LocalDate localDate = LocalDate.now(ZoneId.of("UTC")); + DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE; + String createdDate = localDate.format(formatter); + Date date = new Date(createdDate, DateType.CREATED); + if (optionalAttributes) { + date.dateInformation = "The date the DOI was created"; + } + dates.add(date); + return dates; + } + + protected Resource doUpdateTest(Resource resource, URL doiURL) throws Exception { + String testXML = getResourceXML(resource); + String persistedXml = postDOI(doiURL, testXML, TEST_JOURNAL_REF); + DoiXmlReader reader = new DoiXmlReader(); + return reader.read(persistedXml); + } + + protected void doMintTest(URL doiURL) throws Exception { + URL mintURL = new URL(doiURL + "/" + DoiAction.MINT_ACTION); + postDOI(mintURL, null, null); + } + + private DoiStatus getStatus(URL doiURL) + throws Exception { + URL statusURL = new URL(doiURL + "/" + DoiAction.STATUS_ACTION); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + HttpGet get = new HttpGet(statusURL, bos); + get.setRequestProperty("Accept", "text/xml"); + get.run(); + Assert.assertNull("GET exception", get.getThrowable()); + DoiStatusXmlReader reader = new DoiStatusXmlReader(); + return reader.read(new StringReader(bos.toString(StandardCharsets.UTF_8))); + } + + private void verifyDataDirNodeProperties(ContainerNode dataContainerNode, + ContainerNode dataSubDirContainerNode) { + // verify the DOI data containerNode properties + Assert.assertTrue("should be public", dataContainerNode.isPublic != null && dataContainerNode.isPublic); + Assert.assertTrue("should not have group read", dataContainerNode.getReadOnlyGroup().isEmpty()); + Assert.assertTrue("should not have group write", dataContainerNode.getReadWriteGroup().isEmpty()); + Assert.assertTrue("incorrect lock property", dataContainerNode.isLocked != null && dataContainerNode.isLocked); + + // verify the DOI data subDir containerNode properties + Assert.assertTrue("should be public", dataSubDirContainerNode.isPublic != null && dataSubDirContainerNode.isPublic); + Assert.assertTrue("should not have group read", dataSubDirContainerNode.getReadOnlyGroup().isEmpty()); + Assert.assertTrue("should not have group write", dataSubDirContainerNode.getReadWriteGroup().isEmpty()); + Assert.assertTrue("incorrect lock property", dataSubDirContainerNode.isLocked != null && dataSubDirContainerNode.isLocked); + } + + private void verifyNodeProperties(ContainerNode doiContainerNode, ContainerNode dataContainerNode, + ContainerNode dataSubDirContainerNode) { + // verify the DOI containerNode properties + Assert.assertFalse("incorrect isPublic property", doiContainerNode.isPublic != null && doiContainerNode.isPublic); + Assert.assertFalse("should have group read", doiContainerNode.getReadWriteGroup().isEmpty()); + Assert.assertFalse("should have group write", doiContainerNode.getReadWriteGroup().isEmpty()); + Assert.assertFalse("incorrect lock property", doiContainerNode.isLocked != null && doiContainerNode.isLocked); + + verifyDataDirNodeProperties(dataContainerNode, dataSubDirContainerNode); + } + + private void verifyLockedDataPropertyChanges(ContainerNode doiContainerNode, ContainerNode dataContainerNode, + ContainerNode dataSubDirContainerNode) { + // verify the DOI containerNode properties + Assert.assertFalse("incorrect isPublic property", doiContainerNode.isPublic != null && doiContainerNode.isPublic); + Assert.assertFalse("should have group read", doiContainerNode.getReadWriteGroup().isEmpty()); + Assert.assertFalse("should have group write", doiContainerNode.getReadWriteGroup().isEmpty()); + Assert.assertFalse("incorrect lock property", doiContainerNode.isLocked != null && doiContainerNode.isLocked); + + verifyDataDirNodeProperties(dataContainerNode, dataSubDirContainerNode); + } + + private void verifyMintedStatePropertyChanges(ContainerNode doiContainerNode, ContainerNode dataContainerNode, + ContainerNode dataSubDirContainerNode) { + // verify the DOI containerNode properties + Assert.assertTrue("incorrect isPublic property", doiContainerNode.isPublic != null && doiContainerNode.isPublic); + Assert.assertTrue("should not have group read", doiContainerNode.getReadWriteGroup().isEmpty()); + Assert.assertTrue("should not have group write", doiContainerNode.getReadWriteGroup().isEmpty()); + + verifyDataDirNodeProperties(dataContainerNode, dataSubDirContainerNode); + } + +} diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/MintTest.java b/doi/src/intTest/java/ca/nrc/cadc/doi/MintTest.java deleted file mode 100644 index b272d0a..0000000 --- a/doi/src/intTest/java/ca/nrc/cadc/doi/MintTest.java +++ /dev/null @@ -1,350 +0,0 @@ -/* -************************************************************************ -******************* CANADIAN ASTRONOMY DATA CENTRE ******************* -************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** -* -* (c) 2024. (c) 2024. -* Government of Canada Gouvernement du Canada -* National Research Council Conseil national de recherches -* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 -* All rights reserved Tous droits réservés -* -* NRC disclaims any warranties, Le CNRC dénie toute garantie -* expressed, implied, or énoncée, implicite ou légale, -* statutory, of any kind with de quelque nature que ce -* respect to the software, soit, concernant le logiciel, -* including without limitation y compris sans restriction -* any warranty of merchantability toute garantie de valeur -* or fitness for a particular marchande ou de pertinence -* purpose. NRC shall not be pour un usage particulier. -* liable in any event for any Le CNRC ne pourra en aucun cas -* damages, whether direct or être tenu responsable de tout -* indirect, special or general, dommage, direct ou indirect, -* consequential or incidental, particulier ou général, -* arising from the use of the accessoire ou fortuit, résultant -* software. Neither the name de l'utilisation du logiciel. Ni -* of the National Research le nom du Conseil National de -* Council of Canada nor the Recherches du Canada ni les noms -* names of its contributors may de ses participants ne peuvent -* be used to endorse or promote être utilisés pour approuver ou -* products derived from this promouvoir les produits dérivés -* software without specific prior de ce logiciel sans autorisation -* written permission. préalable et particulière -* par écrit. -* -* This file is part of the Ce fichier fait partie du projet -* OpenCADC project. OpenCADC. -* -* OpenCADC is free software: OpenCADC est un logiciel libre ; -* you can redistribute it and/or vous pouvez le redistribuer ou le -* modify it under the terms of modifier suivant les termes de -* the GNU Affero General Public la “GNU Affero General Public -* License as published by the License” telle que publiée -* Free Software Foundation, par la Free Software Foundation -* either version 3 of the : soit la version 3 de cette -* License, or (at your option) licence, soit (à votre gré) -* any later version. toute version ultérieure. -* -* OpenCADC is distributed in the OpenCADC est distribué -* hope that it will be useful, dans l’espoir qu’il vous -* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE -* without even the implied GARANTIE : sans même la garantie -* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ -* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF -* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence -* General Public License for Générale Publique GNU Affero -* more details. pour plus de détails. -* -* You should have received Vous devriez avoir reçu une -* a copy of the GNU Affero copie de la Licence Générale -* General Public License along Publique GNU Affero avec -* with OpenCADC. If not, see OpenCADC ; si ce n’est -* . pas le cas, consultez : -* . -* -* $Revision: 5 $ -* -************************************************************************ -*/ - -package ca.nrc.cadc.doi; - -import ca.nrc.cadc.doi.datacite.Resource; -import ca.nrc.cadc.doi.io.DoiXmlReader; -import ca.nrc.cadc.doi.status.DoiStatus; -import ca.nrc.cadc.doi.status.DoiStatusXmlReader; -import ca.nrc.cadc.doi.status.Status; -import ca.nrc.cadc.net.HttpGet; -import ca.nrc.cadc.util.Log4jInit; -import ca.nrc.cadc.uws.ExecutionPhase; -import java.io.ByteArrayOutputStream; -import java.io.StringReader; -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.PrivilegedExceptionAction; -import java.util.concurrent.TimeUnit; -import javax.security.auth.Subject; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.junit.Assert; -import org.junit.Test; -import org.opencadc.vospace.ContainerNode; -import org.opencadc.vospace.DataNode; -import org.opencadc.vospace.NodeProperty; -import org.opencadc.vospace.VOSURI; -import org.opencadc.vospace.client.ClientAbortThread; -import org.opencadc.vospace.client.async.RecursiveSetNode; - -/** - */ -public class MintTest extends IntTestBase { - private static final Logger log = Logger.getLogger(MintTest.class); - - static { - Log4jInit.setLevel("ca.nrc.cadc.doi", Level.INFO); - } - - private static final URI DOI_VOS_IS_LOCKED_PROP = URI.create("ivo://cadc.nrc.ca/vospace/core#islocked"); - - private ContainerNode doiParentNode; - - // test minting DOI instance - @Test - public void testMintingDocument() throws Exception { - log.info("testMintingDocument"); - final Resource testResource = getTestResource(false, true, true); - final String testXML = getResourceXML(testResource); - Subject.doAs(readWriteSubject, (PrivilegedExceptionAction) () -> { - // Create the test DOI document in VOSpace - String persistedXML = postDOI(doiServiceURL, testXML, TEST_JOURNAL_REF); - DoiXmlReader reader = new DoiXmlReader(); - Resource presistedResource = reader.read(persistedXML); - String expectedIdentifier = presistedResource.getIdentifier().getValue(); - Assert.assertNotEquals("New identifier not received from doi service.", testResource.getIdentifier().getValue(), expectedIdentifier); - - // Get the DOI suffix from the identifier - String doiSuffix = getDOISuffix(expectedIdentifier); - try { - // For DOI tests below - URL doiURL = new URL(String.format("%s/%s", doiServiceURL, doiSuffix)); - - // Verify that the DOI document was created successfully - DoiStatus doiStatus = getStatus(doiURL); - Assert.assertEquals("identifier from DOI status is different", expectedIdentifier, - doiStatus.getIdentifier().getValue()); - Assert.assertEquals("status is incorrect", Status.DRAFT, doiStatus.getStatus()); - Assert.assertEquals("journalRef is incorrect", TEST_JOURNAL_REF, doiStatus.journalRef); - - // verify the DOI containerNode properties - ContainerNode doiContainerNode = getContainerNode(doiSuffix); - Assert.assertFalse("incorrect isPublic property", doiContainerNode.isPublic != null && doiContainerNode.isPublic); - Assert.assertFalse("should have group read property", doiContainerNode.getReadOnlyGroup().isEmpty()); - ContainerNode dataContainerNode = getContainerNode(doiSuffix + "/data"); - Assert.assertFalse("should have group write", dataContainerNode.getReadWriteGroup().isEmpty()); - - // add a file and a subdirectory with a file to the data directory - String testFile1 = "test-file-1.txt"; - String testFile1Path = String.format("%s/data/%s", doiSuffix, testFile1); - DataNode testFileNode = createDataNode(testFile1Path, testFile1); - - String subDir = "subDir"; - String subDirPath = String.format("%s/data/%s", doiSuffix, subDir); - ContainerNode dataSubDirContainerNode = createContainerNode(subDirPath, subDir); - - String testFile2 = "test-file-2.txt"; - String testFile2Path = String.format("%s/data/%s/%s", doiSuffix, subDir, testFile2); - DataNode testFile2Node = createDataNode(testFile2Path, testFile2); - - // mint the document, DRAFT ==> LOCKING_DATA - doMintTest(doiURL); - doiContainerNode = getContainerNode(doiSuffix); - dataContainerNode = getContainerNode(doiSuffix + "/data"); - dataSubDirContainerNode = getContainerNode(doiSuffix + "/data/" + subDir); - Assert.assertEquals("incorrect status", Status.LOCKING_DATA.getValue(), doiContainerNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); - verifyNodeProperties(doiContainerNode, dataContainerNode, dataSubDirContainerNode); - log.debug("locking data"); - - // mint the document, ERROR_LOCKING_DATA ==> LOCKING_DATA - doiContainerNode.getProperty(DoiAction.DOI_VOS_STATUS_PROP).setValue(Status.ERROR_LOCKING_DATA.getValue()); - VOSURI vosuri = getVOSURI(doiContainerNode.getName()); - vosClient.setNode(vosuri, doiContainerNode); - doMintTest(doiURL); - doiContainerNode = getContainerNode(doiSuffix); - dataContainerNode = getContainerNode(doiSuffix + "/data"); - dataSubDirContainerNode = getContainerNode(doiSuffix + "/data/" + subDir); - Assert.assertEquals("incorrect status", Status.LOCKING_DATA.getValue(), doiContainerNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); - verifyNodeProperties(doiContainerNode, dataContainerNode, dataSubDirContainerNode); - log.debug("locking data again"); - - // getStatus() changes LOCKING_DATA == > LOCKED_DATA - doiStatus = getStatus(doiURL); - Assert.assertEquals("identifier from DOI status is different", expectedIdentifier, doiStatus.getIdentifier().getValue()); - Assert.assertEquals("status is incorrect", Status.LOCKED_DATA, doiStatus.getStatus()); - verifyLockedDataPropertyChanges(doiContainerNode, dataContainerNode, dataSubDirContainerNode); - log.debug("locked data"); - - // mint the document, LOCKED_DATA == REGISTERING - doMintTest(doiURL); - doiContainerNode = getContainerNode(doiSuffix); - dataContainerNode = getContainerNode(doiSuffix + "/data"); - dataSubDirContainerNode = getContainerNode(doiSuffix + "/data/" + subDir); - Assert.assertEquals("incorrect status", Status.MINTED.getValue(), doiContainerNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); - verifyMintedStatePropertyChanges(doiContainerNode, dataContainerNode, dataSubDirContainerNode); - log.debug("registering"); - - // mint the document, ERROR_REGISTERING ==> REGISTERING - // the doiContainerNode doesn't have group read & write anymore, and is owned - // by doiadmin, so changes to it must be done with that cert. - doiContainerNode.getProperty(DoiAction.DOI_VOS_STATUS_PROP).setValue(Status.ERROR_REGISTERING.getValue()); - doiParentNode = doiContainerNode; - Subject.doAs(adminSubject, (PrivilegedExceptionAction) () -> { - VOSURI parentVOSURI = getVOSURI(doiParentNode.getName()); - vosClient.setNode(parentVOSURI, doiParentNode); - return null; - }); - log.debug("registering again"); - - doMintTest(doiURL); - doiContainerNode = getContainerNode(doiSuffix); - dataContainerNode = getContainerNode(doiSuffix + "/data"); - dataSubDirContainerNode = getContainerNode(doiSuffix + "/data/" + subDir); - Assert.assertEquals("incorrect status", Status.MINTED.getValue(), doiContainerNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); - verifyMintedStatePropertyChanges(doiContainerNode, dataContainerNode, dataSubDirContainerNode); - - // getStatus() changes REGISTERING == > MINTED - doiStatus = getStatus(doiURL); - Assert.assertEquals("identifier from DOI status is different", expectedIdentifier, doiStatus.getIdentifier().getValue()); - Assert.assertEquals("status is incorrect", Status.MINTED, doiStatus.getStatus()); - - // verify the DOI containerNode properties - Assert.assertEquals("incorrect status", Status.MINTED.getValue(), doiContainerNode.getPropertyValue(DoiAction.DOI_VOS_STATUS_PROP)); - } catch (Throwable e) { - log.error("unexpected exception", e); - Assert.fail("unexpected exception: " + e); - } finally { - // cannot delete a DOI when it is in 'MINTED' state, change its state to 'DRAFT' - // node owner is doiadmin, and after minting the group permissions are removed, so - // cleanup needs to be done as doiadmin, not the test subject - Subject.doAs(adminSubject, (PrivilegedExceptionAction) () -> { - ContainerNode doiContainerNode = getContainerNode(doiSuffix); - VOSURI vosuri = getVOSURI(doiContainerNode.getName()); - doiContainerNode.getProperty(DoiAction.DOI_VOS_STATUS_PROP).setValue(Status.DRAFT.getValue()); - vosClient.setNode(vosuri, doiContainerNode); - - // unlock the data directory and delete the DOI - setDataNodeRecursively(doiSuffix); - cleanup(doiSuffix); - return null; - }); - } - return presistedResource; - }); - } - - private DoiStatus getStatus(URL doiURL) - throws Exception { - URL statusURL = new URL(doiURL + "/" + DoiAction.STATUS_ACTION); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - HttpGet get = new HttpGet(statusURL, bos); - get.setRequestProperty("Accept", "text/xml"); - get.run(); - Assert.assertNull("GET exception", get.getThrowable()); - DoiStatusXmlReader reader = new DoiStatusXmlReader(); - return reader.read(new StringReader(bos.toString(StandardCharsets.UTF_8))); - } - - private void doMintTest(URL doiURL) - throws Exception { - URL mintURL = new URL(doiURL + "/" + DoiAction.MINT_ACTION); - postDOI(mintURL, null, null); - } - - private void verifyDataDirNodeProperties(ContainerNode dataContainerNode, - ContainerNode dataSubDirContainerNode) { - // verify the DOI data containerNode properties - Assert.assertTrue("should be public", dataContainerNode.isPublic != null && dataContainerNode.isPublic); - Assert.assertTrue("should not have group read", dataContainerNode.getReadOnlyGroup().isEmpty()); - Assert.assertTrue("should not have group write", dataContainerNode.getReadWriteGroup().isEmpty()); - Assert.assertTrue("incorrect lock property", dataContainerNode.isLocked != null && dataContainerNode.isLocked); - - // verify the DOI data subDir containerNode properties - Assert.assertTrue("should be public", dataSubDirContainerNode.isPublic != null && dataSubDirContainerNode.isPublic); - Assert.assertTrue("should not have group read", dataSubDirContainerNode.getReadOnlyGroup().isEmpty()); - Assert.assertTrue("should not have group write", dataSubDirContainerNode.getReadWriteGroup().isEmpty()); - Assert.assertTrue("incorrect lock property", dataSubDirContainerNode.isLocked != null && dataSubDirContainerNode.isLocked); - } - - private void verifyNodeProperties(ContainerNode doiContainerNode, ContainerNode dataContainerNode, - ContainerNode dataSubDirContainerNode) { - // verify the DOI containerNode properties - Assert.assertFalse("incorrect isPublic property", doiContainerNode.isPublic != null && doiContainerNode.isPublic); - Assert.assertFalse("should have group read", doiContainerNode.getReadWriteGroup().isEmpty()); - Assert.assertFalse("should have group write", doiContainerNode.getReadWriteGroup().isEmpty()); - Assert.assertFalse("incorrect lock property", doiContainerNode.isLocked != null && doiContainerNode.isLocked); - - verifyDataDirNodeProperties(dataContainerNode, dataSubDirContainerNode); - } - - private void verifyLockedDataPropertyChanges(ContainerNode doiContainerNode, ContainerNode dataContainerNode, - ContainerNode dataSubDirContainerNode) { - // verify the DOI containerNode properties - Assert.assertFalse("incorrect isPublic property", doiContainerNode.isPublic != null && doiContainerNode.isPublic); - Assert.assertFalse("should have group read", doiContainerNode.getReadWriteGroup().isEmpty()); - Assert.assertFalse("should have group write", doiContainerNode.getReadWriteGroup().isEmpty()); - Assert.assertFalse("incorrect lock property", doiContainerNode.isLocked != null && doiContainerNode.isLocked); - - verifyDataDirNodeProperties(dataContainerNode, dataSubDirContainerNode); - } - - private void verifyMintedStatePropertyChanges(ContainerNode doiContainerNode, ContainerNode dataContainerNode, - ContainerNode dataSubDirContainerNode) { - // verify the DOI containerNode properties - Assert.assertTrue("incorrect isPublic property", doiContainerNode.isPublic != null && doiContainerNode.isPublic); - Assert.assertTrue("should not have group read", doiContainerNode.getReadWriteGroup().isEmpty()); - Assert.assertTrue("should not have group write", doiContainerNode.getReadWriteGroup().isEmpty()); - - verifyDataDirNodeProperties(dataContainerNode, dataSubDirContainerNode); - } - - private ContainerNode createContainerNode(String path, String name) throws Exception { - ContainerNode node = new ContainerNode(name); - VOSURI nodeURI = getVOSURI(path); - return (ContainerNode) vosClient.createNode(nodeURI, node); - } - - private DataNode createDataNode(String path, String name) throws Exception { - DataNode node = new DataNode(name); - VOSURI nodeURI = getVOSURI(path); - return (DataNode) vosClient.createNode(nodeURI, node); - } - - private void setDataNodeRecursively(String doiSuffix) throws Exception { - Subject.doAs(adminSubject, (PrivilegedExceptionAction) () -> { - VOSURI vosuri = getVOSURI(String.format("%s/data", doiSuffix)); - ContainerNode dataContainerNode = new ContainerNode("data"); - dataContainerNode.getProperties().add(new NodeProperty(DOI_VOS_IS_LOCKED_PROP, "false")); - RecursiveSetNode recursiveSetNode = vosClient.createRecursiveSetNode(vosuri, dataContainerNode); - URL jobURL = recursiveSetNode.getJobURL(); - - // this is an async operation - Thread abortThread = new ClientAbortThread(jobURL); - Runtime.getRuntime().addShutdownHook(abortThread); - recursiveSetNode.setMonitor(true); - recursiveSetNode.run(); - Runtime.getRuntime().removeShutdownHook(abortThread); - - recursiveSetNode = new RecursiveSetNode(jobURL, dataContainerNode); - recursiveSetNode.setSchemaValidation(false); - ExecutionPhase phase = recursiveSetNode.getPhase(20); - while (phase == ExecutionPhase.QUEUED || phase == ExecutionPhase.EXECUTING) { - TimeUnit.SECONDS.sleep(1); - phase = recursiveSetNode.getPhase(); - } - - Assert.assertSame("Failed to unlock test data directory, phase = " + phase, ExecutionPhase.COMPLETED, phase); - return phase.getValue(); - }); - } - -} \ No newline at end of file diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/TestUtil.java b/doi/src/intTest/java/ca/nrc/cadc/doi/TestUtil.java index 4604bcd..8ecc46f 100644 --- a/doi/src/intTest/java/ca/nrc/cadc/doi/TestUtil.java +++ b/doi/src/intTest/java/ca/nrc/cadc/doi/TestUtil.java @@ -69,29 +69,77 @@ package ca.nrc.cadc.doi; +import ca.nrc.cadc.util.FileUtil; +import ca.nrc.cadc.util.Log4jInit; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; +import java.util.MissingResourceException; +import java.util.Properties; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.opencadc.vospace.VOSURI; public class TestUtil { + private static final Logger log = Logger.getLogger(TestUtil.class); + + static { + Log4jInit.setLevel("ca.nrc.cadc.doi", Level.INFO); + } + + // ADMIN_CERT is the owner of the test DOI + static String ADMIN_CERT = "doi-admin.pem"; + + // AUTH_CERT has read/write access to the test DOI + static String AUTH_CERT = "doi-auth.pem"; + + // NO_AUTH_CERT has read only access to the test DOI + static String NO_AUTH_CERT = "doi-noauth.pem"; // resourceID for the local test DOI service - public static URI DOI_RESOURCE_ID = URI.create("ivo://opencadc.org/doi"); + static URI DOI_RESOURCE_ID = URI.create("ivo://opencadc.org/doi"); + + // VOSpace URI to the DOI parent node, + static URI VOSPACE_PARENT_URI = URI.create("vos://opencadc.org~vault/doi"); + + // following derived from VOSPACE_PARENT_URI + // resourceID for the local VOSpace service + static URI VOSPACE_RESOURCE_ID; - // resourceID for the production DOI service - public static URI PROD_DOI_RESOURCE_ID = URI.create("ivo://cadc.nrc.ca/doi"); + // path for the DOI parent node in VOSpace + static String DOI_PARENT_PATH; - // resourceID for the vault service (used to store the DOI metadata) - public static URI VAULT_RESOURCE_ID = URI.create("ivo://opencadc.org/vault"); + static { - // ADMIN_CERT has full access to a test DOI - public static String ADMIN_CERT = "doiadmin.pem"; + try { + File opt = FileUtil.getFileFromResource("intTest.properties", TestUtil.class); + if (opt.exists()) { + Properties props = new Properties(); + props.load(new FileReader(opt)); - // AUTH_CERT has read/write access to a test DOI - public static String AUTH_CERT = "doi-auth.pem"; + if (props.containsKey("doiResourceID")) { + DOI_RESOURCE_ID = URI.create(props.getProperty("doiResourceID").trim()); + } + if (props.containsKey("vospaceParentUri")) { + VOSPACE_PARENT_URI = URI.create(props.getProperty("vospaceParentUri").trim()); + } + } + } + catch (MissingResourceException | FileNotFoundException noFileException) { + log.debug("No intTest.properties supplied. Using defaults."); + } catch (IOException oops) { + throw new RuntimeException(oops.getMessage(), oops); + } - // NO_AUTH_CERT has read only access to a test DOI - public static String NO_AUTH_CERT = "doi-noauth.pem"; + VOSURI vosURI = new VOSURI(VOSPACE_PARENT_URI); + VOSPACE_RESOURCE_ID = vosURI.getServiceURI(); + DOI_PARENT_PATH = vosURI.getPath(); - // expected path for the DOI parent node - public static String DOI_PARENT_PATH = "/AstroDataCitationDOI/CISTI.CANFAR"; + log.debug(String.format("intTest config: %s %s %s %s", + DOI_RESOURCE_ID, VOSPACE_PARENT_URI, VOSPACE_RESOURCE_ID, DOI_PARENT_PATH)); + } -} + } diff --git a/doi/src/intTest/java/ca/nrc/cadc/doi/UpdateTest.java b/doi/src/intTest/java/ca/nrc/cadc/doi/UpdateTest.java deleted file mode 100644 index 8f9d665..0000000 --- a/doi/src/intTest/java/ca/nrc/cadc/doi/UpdateTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* -************************************************************************ -******************* CANADIAN ASTRONOMY DATA CENTRE ******************* -************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** -* -* (c) 2024. (c) 2024. -* Government of Canada Gouvernement du Canada -* National Research Council Conseil national de recherches -* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 -* All rights reserved Tous droits réservés -* -* NRC disclaims any warranties, Le CNRC dénie toute garantie -* expressed, implied, or énoncée, implicite ou légale, -* statutory, of any kind with de quelque nature que ce -* respect to the software, soit, concernant le logiciel, -* including without limitation y compris sans restriction -* any warranty of merchantability toute garantie de valeur -* or fitness for a particular marchande ou de pertinence -* purpose. NRC shall not be pour un usage particulier. -* liable in any event for any Le CNRC ne pourra en aucun cas -* damages, whether direct or être tenu responsable de tout -* indirect, special or general, dommage, direct ou indirect, -* consequential or incidental, particulier ou général, -* arising from the use of the accessoire ou fortuit, résultant -* software. Neither the name de l'utilisation du logiciel. Ni -* of the National Research le nom du Conseil National de -* Council of Canada nor the Recherches du Canada ni les noms -* names of its contributors may de ses participants ne peuvent -* be used to endorse or promote être utilisés pour approuver ou -* products derived from this promouvoir les produits dérivés -* software without specific prior de ce logiciel sans autorisation -* written permission. préalable et particulière -* par écrit. -* -* This file is part of the Ce fichier fait partie du projet -* OpenCADC project. OpenCADC. -* -* OpenCADC is free software: OpenCADC est un logiciel libre ; -* you can redistribute it and/or vous pouvez le redistribuer ou le -* modify it under the terms of modifier suivant les termes de -* the GNU Affero General Public la “GNU Affero General Public -* License as published by the License” telle que publiée -* Free Software Foundation, par la Free Software Foundation -* either version 3 of the : soit la version 3 de cette -* License, or (at your option) licence, soit (à votre gré) -* any later version. toute version ultérieure. -* -* OpenCADC is distributed in the OpenCADC est distribué -* hope that it will be useful, dans l’espoir qu’il vous -* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE -* without even the implied GARANTIE : sans même la garantie -* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ -* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF -* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence -* General Public License for Générale Publique GNU Affero -* more details. pour plus de détails. -* -* You should have received Vous devriez avoir reçu une -* a copy of the GNU Affero copie de la Licence Générale -* General Public License along Publique GNU Affero avec -* with OpenCADC. If not, see OpenCADC ; si ce n’est -* . pas le cas, consultez : -* . -* -* $Revision: 5 $ -* -************************************************************************ -*/ - -package ca.nrc.cadc.doi; - -import ca.nrc.cadc.doi.datacite.Date; -import ca.nrc.cadc.doi.datacite.DateType; -import ca.nrc.cadc.doi.datacite.Language; -import ca.nrc.cadc.doi.datacite.Resource; -import ca.nrc.cadc.doi.io.DoiXmlReader; -import ca.nrc.cadc.util.Log4jInit; -import java.net.URL; -import java.security.PrivilegedExceptionAction; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import javax.security.auth.Subject; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.junit.Assert; -import org.junit.Test; - -/** - * Persist a minimal instance - */ -public class UpdateTest extends IntTestBase { - private static final Logger log = Logger.getLogger(UpdateTest.class); - - static { - Log4jInit.setLevel("ca.nrc.cadc.doi", Level.INFO); - } - - @Override - protected List getDates(boolean optionalAttributes) { - List dates = new ArrayList<>(); - LocalDate localDate = LocalDate.now(ZoneId.of("UTC")); - DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE; - String createdDate = localDate.format(formatter); - Date date = new Date(createdDate, DateType.CREATED); - if (optionalAttributes) { - date.dateInformation = "The date the DOI was created"; - } - dates.add(date); - return dates; - } - - @Test - public void updateDOITest() throws Exception { - log.info("updateDOITest"); - Subject.doAs(readWriteSubject, (PrivilegedExceptionAction) () -> { - // minimally populated required properties - Resource expected = getTestResource(true, true, true); - expected.language = new Language("en-US"); - List testDOIList = new ArrayList<>(); - try { - Resource actual = doTest(expected); - - Assert.assertNotEquals("Identifier's should not match", - expected.getIdentifier().getValue(), actual.getIdentifier().getValue()); - compareResource(expected, actual, false); - - // get the URL to the new DOI - String doiSuffix = getDOISuffix(actual.getIdentifier().getValue()); - testDOIList.add(doiSuffix); - URL doiURL = new URL(String.format("%s/%s", doiServiceURL, doiSuffix)); - - // fully populated required resource - Resource maxResource = getTestResource(false, true, true); - updateResource(expected, maxResource); - actual = doTest(expected); - testDOIList.add(getDOISuffix(actual.getIdentifier().getValue())); - compareResource(expected, actual); - - // back to minimally populated required resource - Resource minResource = getTestResource(false, false, true); - updateResource(expected, minResource); - actual = doTest(expected); - testDOIList.add(getDOISuffix(actual.getIdentifier().getValue())); - compareResource(expected, actual); - - // update PublicationYear - expected.getPublicationYear().setValue("2001"); - actual = doTest(expected); - testDOIList.add(getDOISuffix(actual.getIdentifier().getValue())); - compareResource(expected, actual); - - // update Language - expected.language = new Language("en-GB"); - actual = doTest(expected); - testDOIList.add(getDOISuffix(actual.getIdentifier().getValue())); - compareResource(expected, actual); - } finally { - for (String doiSuffix : testDOIList) { - cleanup(doiSuffix); - } - } - return null; - }); - } - - protected void updateResource(Resource destination, Resource source) { - destination.getCreators().clear(); - destination.getCreators().addAll(source.getCreators()); - - destination.getTitles().clear(); - destination.getTitles().addAll(source.getTitles()); - - destination.getPublisher().publisherIdentifier = source.getPublisher().publisherIdentifier; - destination.getPublisher().publisherIdentifierScheme = source.getPublisher().publisherIdentifierScheme; - destination.getPublisher().schemeURI = source.getPublisher().schemeURI; - destination.getPublisher().lang = source.getPublisher().lang; - - destination.getResourceType().value = source.getResourceType().value; - } - - protected Resource doTest(Resource resource) throws Exception { - String testXML = getResourceXML(resource); - String persistedXml = postDOI(doiServiceURL, testXML, TEST_JOURNAL_REF); - DoiXmlReader reader = new DoiXmlReader(); - return reader.read(persistedXml); - } - - @Override - protected void compareResource(Resource expected, Resource actual) { - Assert.assertNotEquals("Identifier's should not match", - expected.getIdentifier().getValue(), actual.getIdentifier().getValue()); - compareResource(expected, actual, false); - } - -} \ No newline at end of file diff --git a/doi/src/main/java/ca/nrc/cadc/doi/DeleteAction.java b/doi/src/main/java/ca/nrc/cadc/doi/DeleteAction.java index 96f128d..cdc0540 100644 --- a/doi/src/main/java/ca/nrc/cadc/doi/DeleteAction.java +++ b/doi/src/main/java/ca/nrc/cadc/doi/DeleteAction.java @@ -90,7 +90,7 @@ public DeleteAction() { public void doAction() throws Exception { super.init(true); - // Do all subsequent work as doiadmin + // Do all subsequent work as doi admin Subject.doAs(getAdminSubject(), (PrivilegedExceptionAction) () -> { doActionImpl(); return null; @@ -119,7 +119,7 @@ private void doActionImpl() throws Exception { Integer numericID = Integer.parseInt(doiRequester); Subject requestorSubject = acIdentMgr.toSubject(numericID); if (!checkSubjectsMatch(callingSubject, requestorSubject)) { - // if doiadmin is the calling user, it has permission to delete any of the DOIs as well + // if doi admin is the calling user, it has permission to delete any of the DOIs as well if (!checkSubjectsMatch(callingSubject, getAdminSubject())) { throw new AccessControlException("Not permitted to delete DOI"); } diff --git a/doi/src/main/java/ca/nrc/cadc/doi/DoiAction.java b/doi/src/main/java/ca/nrc/cadc/doi/DoiAction.java index 85a1fd8..206f47b 100644 --- a/doi/src/main/java/ca/nrc/cadc/doi/DoiAction.java +++ b/doi/src/main/java/ca/nrc/cadc/doi/DoiAction.java @@ -71,6 +71,8 @@ import ca.nrc.cadc.ac.client.GMSClient; import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.SSLUtil; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.reg.client.LocalAuthority; import ca.nrc.cadc.rest.InlineContentHandler; import ca.nrc.cadc.rest.RestAction; import ca.nrc.cadc.util.MultiValuedProperties; @@ -79,16 +81,14 @@ import java.net.URISyntaxException; import java.net.UnknownHostException; import java.security.AccessControlException; +import java.util.Set; import javax.security.auth.Subject; -import javax.security.auth.x500.X500Principal; import org.apache.log4j.Logger; import org.opencadc.vospace.VOSURI; public abstract class DoiAction extends RestAction { private static final Logger log = Logger.getLogger(DoiAction.class); - public static final X500Principal DOIADMIN_X500 = new X500Principal("C=ca,O=hia,OU=cadc,CN=doiadmin_045"); - public static final URI DOI_VOS_JOB_URL_PROP = URI.create("ivo://cadc.nrc.ca/vospace/doi#joburl"); public static final URI DOI_VOS_REQUESTER_PROP = URI.create("ivo://cadc.nrc.ca/vospace/doi#requester"); public static final URI DOI_VOS_STATUS_PROP = URI.create("ivo://cadc.nrc.ca/vospace/doi#status"); @@ -98,6 +98,7 @@ public abstract class DoiAction extends RestAction { public static final String MINT_ACTION = "mint"; public static final String JOURNALREF_PARAM = "journalref"; public static final String DOI_GROUP_PREFIX = "DOI-"; + public static final String TEST_DOI_GROUP_PREFIX = "TEST.DOI-"; protected Subject callingSubject; protected Long callersNumericId; @@ -130,10 +131,18 @@ protected void init(boolean authorize) throws URISyntaxException, UnknownHostException { // load doi properties this.config = DoiInitAction.getConfig(); - this.vaultResourceID = URI.create(config.getFirstPropertyValue(DoiInitAction.VAULT_RESOURCE_ID_KEY)); - this.gmsResourceID = URI.create(config.getFirstPropertyValue(DoiInitAction.GMS_RESOURCE_ID_KEY)); + this.vaultResourceID = DoiInitAction.getVospaceResourceID(config); + this.parentPath = DoiInitAction.getParentPath(config); this.accountPrefix = config.getFirstPropertyValue(DoiInitAction.DATACITE_ACCOUNT_PREFIX_KEY); - this.parentPath = config.getFirstPropertyValue(DoiInitAction.PARENT_PATH_KEY); + + LocalAuthority localAuthority = new LocalAuthority(); + Set gmsServices = localAuthority.getServiceURIs(Standards.GMS_SEARCH_10); + if (gmsServices.isEmpty()) { + throw new IllegalStateException("GMS service not found"); + } else if (gmsServices.size() > 1) { + throw new IllegalStateException("multiple GMS services found"); + } + this.gmsResourceID = gmsServices.iterator().next(); // get calling subject callingSubject = AuthenticationUtil.getCurrentSubject(); @@ -152,7 +161,7 @@ protected void init(boolean authorize) protected String getDoiFilename(String suffix) { return String.format("%s%s.xml", - config.getFirstPropertyValue(DoiInitAction.METADATA_FILE_PREFIX_KEY), suffix); + config.getFirstPropertyValue(DoiInitAction.METADATA_PREFIX_KEY), suffix); } protected VOSURI getVOSURI(String path) { @@ -160,7 +169,6 @@ protected VOSURI getVOSURI(String path) { } protected GMSClient getGMSClient() { - URI gmsResourceID = URI.create(config.getFirstPropertyValue(DoiInitAction.GMS_RESOURCE_ID_KEY)); return new GMSClient(gmsResourceID); } diff --git a/doi/src/main/java/ca/nrc/cadc/doi/DoiInitAction.java b/doi/src/main/java/ca/nrc/cadc/doi/DoiInitAction.java index ee5bf60..6f48976 100644 --- a/doi/src/main/java/ca/nrc/cadc/doi/DoiInitAction.java +++ b/doi/src/main/java/ca/nrc/cadc/doi/DoiInitAction.java @@ -76,7 +76,6 @@ import ca.nrc.cadc.rest.InitAction; import ca.nrc.cadc.util.MultiValuedProperties; import ca.nrc.cadc.util.PropertiesReader; -import ca.nrc.cadc.util.StringUtil; import java.io.File; import java.net.MalformedURLException; import java.net.URI; @@ -88,25 +87,23 @@ import org.apache.log4j.Logger; import org.opencadc.vospace.ContainerNode; import org.opencadc.vospace.Node; +import org.opencadc.vospace.VOSURI; import org.opencadc.vospace.client.VOSpaceClient; public class DoiInitAction extends InitAction { private static final Logger log = Logger.getLogger(DoiInitAction.class); public static final String DOI_KEY = "ca.nrc.cadc.doi"; - public static final String VAULT_RESOURCE_ID_KEY = DOI_KEY + ".vaultResourceID"; - public static final String GMS_RESOURCE_ID_KEY = DOI_KEY + ".gmsResourceID"; - public static final String PARENT_PATH_KEY = DOI_KEY + ".parentPath"; - public static final String METADATA_FILE_PREFIX_KEY = DOI_KEY + ".metadataFilePrefix"; + public static final String VOSPACE_PARENT_URI_KEY = DOI_KEY + ".vospaceParentUri"; + public static final String METADATA_PREFIX_KEY = DOI_KEY + ".metaDataPrefix"; public static final String LANDING_URL_KEY = DOI_KEY + ".landingUrl"; public static final String DATACITE_MDS_URL_KEY = DOI_KEY + ".datacite.mdsUrl"; - public static final String DATACITE_ACCOUNT_PREFIX_KEY = DOI_KEY + ".datacite.accountPrefix"; public static final String DATACITE_MDS_USERNAME_KEY = DOI_KEY + ".datacite.username"; public static final String DATACITE_MDS_PASSWORD_KEY = DOI_KEY + ".datacite.password"; + public static final String DATACITE_ACCOUNT_PREFIX_KEY = DOI_KEY + ".datacite.accountPrefix"; // optional properties - public static final String TEST_RANDOM_NAME_KEY = DOI_KEY + ".test.randomName"; - public static final String TEST_GROUP_URI_KEY = DOI_KEY + ".test.groupURI"; + public static final String RANDOM_TEST_ID_KEY = DOI_KEY + ".randomTestID"; @Override public void doInit() { @@ -118,6 +115,28 @@ public static MultiValuedProperties getConfig() { return getConfig(false); } + public static URI getVospaceResourceID(MultiValuedProperties props) { + String vospaceParentUri = props.getFirstPropertyValue(VOSPACE_PARENT_URI_KEY); + VOSURI vosURI; + try { + vosURI = new VOSURI(vospaceParentUri); + } catch (URISyntaxException e) { + throw new IllegalStateException("invalid VOSpace URI: " + vospaceParentUri); + } + return vosURI.getServiceURI(); + } + + public static String getParentPath(MultiValuedProperties props) { + String vospaceParentUri = props.getFirstPropertyValue(VOSPACE_PARENT_URI_KEY); + VOSURI vosURI; + try { + vosURI = new VOSURI(vospaceParentUri); + } catch (URISyntaxException e) { + throw new IllegalStateException("invalid VOSpace URI: " + vospaceParentUri); + } + return vosURI.getPath(); + } + private static MultiValuedProperties getConfig(boolean verify) { PropertiesReader reader = new PropertiesReader("doi.properties"); MultiValuedProperties props = reader.getAllProperties(); @@ -125,43 +144,24 @@ private static MultiValuedProperties getConfig(boolean verify) { StringBuilder sb = new StringBuilder(); boolean ok = true; - String vaultResourceID = props.getFirstPropertyValue(VAULT_RESOURCE_ID_KEY); - sb.append(String.format("\n\t%s: ", VAULT_RESOURCE_ID_KEY)); - if (vaultResourceID == null) { + String parentUri = props.getFirstPropertyValue(VOSPACE_PARENT_URI_KEY); + sb.append(String.format("\n\t%s: ", VOSPACE_PARENT_URI_KEY)); + if (parentUri == null) { sb.append("MISSING"); ok = false; - } else if (verify) { - try { - new URI(vaultResourceID); - sb.append("OK"); - } catch (URISyntaxException e) { - sb.append("INVALID VAULT RESOURCE ID: ").append(e.getMessage()); - ok = false; - } } else { - sb.append("OK"); - } - - String gmsResourceID = props.getFirstPropertyValue(GMS_RESOURCE_ID_KEY); - sb.append(String.format("\n\t%s: ", GMS_RESOURCE_ID_KEY)); - if (gmsResourceID == null) { - sb.append("MISSING"); - ok = false; - } else if (verify) { try { - new URI(gmsResourceID); + new VOSURI(parentUri); sb.append("OK"); } catch (URISyntaxException e) { - sb.append("INVALID GMS RESOURCE ID: ").append(e.getMessage()); + sb.append("INVALID VOSPACE URI: ").append(e.getMessage()); ok = false; } - } else { - sb.append("OK"); } - String metadataFilePrefix = props.getFirstPropertyValue(METADATA_FILE_PREFIX_KEY); - sb.append(String.format("\n\t%s: ", METADATA_FILE_PREFIX_KEY)); - if (metadataFilePrefix == null) { + String metaDataPrefix = props.getFirstPropertyValue(METADATA_PREFIX_KEY); + sb.append(String.format("\n\t%s: ", METADATA_PREFIX_KEY)); + if (metaDataPrefix == null) { sb.append("MISSING"); ok = false; } else { @@ -185,24 +185,6 @@ private static MultiValuedProperties getConfig(boolean verify) { sb.append("OK"); } - String parentPath = props.getFirstPropertyValue(PARENT_PATH_KEY); - sb.append(String.format("\n\t%s: ", PARENT_PATH_KEY)); - if (parentPath == null) { - sb.append("MISSING"); - ok = false; - } else { - sb.append("OK"); - } - - String accountPrefix = props.getFirstPropertyValue(DATACITE_ACCOUNT_PREFIX_KEY); - sb.append(String.format("\n\t%s: ", DATACITE_ACCOUNT_PREFIX_KEY)); - if (accountPrefix == null) { - sb.append("MISSING"); - ok = false; - } else { - sb.append("OK"); - } - String mdsEndpoint = props.getFirstPropertyValue(DATACITE_MDS_URL_KEY); sb.append(String.format("\n\t%s: ", DATACITE_MDS_URL_KEY)); if (mdsEndpoint == null) { @@ -238,92 +220,71 @@ private static MultiValuedProperties getConfig(boolean verify) { sb.append("OK"); } - // optional properties - String testRandomName = props.getFirstPropertyValue(TEST_RANDOM_NAME_KEY); - sb.append(String.format("\n\t%s: ", TEST_RANDOM_NAME_KEY)); - if (testRandomName == null) { + String accountPrefix = props.getFirstPropertyValue(DATACITE_ACCOUNT_PREFIX_KEY); + sb.append(String.format("\n\t%s: ", DATACITE_ACCOUNT_PREFIX_KEY)); + if (accountPrefix == null) { sb.append("MISSING"); + ok = false; } else { sb.append("OK"); } - String testGroupURI = props.getFirstPropertyValue(TEST_GROUP_URI_KEY); - sb.append(String.format("\n\t%s: ", TEST_GROUP_URI_KEY)); - if (testGroupURI == null) { + // optional properties + String randomTestID = props.getFirstPropertyValue(RANDOM_TEST_ID_KEY); + sb.append(String.format("\n\t%s: ", RANDOM_TEST_ID_KEY)); + if (randomTestID == null) { sb.append("MISSING"); } else { - if (verify) { - try { - new URI(testGroupURI); - sb.append("OK"); - } catch (URISyntaxException e) { - sb.append("INVALID URI"); - ok = false; - } - } else { - sb.append("OK"); - } + sb.append("OK"); } - if (!ok) { throw new IllegalStateException("incomplete config: " + sb); } return props; } - // check that the DOI parent node path, configured with the V0SPACE_PARENT_PATH_KEY property, + // check that the DOI parent node uri, configured with the VOSPACE_PARENT_URI_KEY property, // exists and has the expected properties. private static void checkParentFolders() { - MultiValuedProperties config = getConfig(); - URI vospaceResourceID = URI.create(config.getFirstPropertyValue(DoiInitAction.VAULT_RESOURCE_ID_KEY)); - String parentPath = config.getFirstPropertyValue(PARENT_PATH_KEY); Subject adminSubject = SSLUtil.createSubject(new File("/config/doiadmin.pem")); adminSubject = AuthenticationUtil.augmentSubject(adminSubject); String adminUsername = getUsername(adminSubject); - // TODO is it necessary to check all nodes in the path for ownership and permissions, - // or is it sufficient to check the last node? + MultiValuedProperties config = getConfig(); + URI vospaceResourceID = DoiInitAction.getVospaceResourceID(config); + String parentPath = DoiInitAction.getParentPath(config); VOSpaceClient vosClient = new VOSpaceClient(vospaceResourceID); - String currentPath = ""; - String[] paths = parentPath.split("/"); - for (String path : paths) { - // skip first empty path if parentPath begins with a / - if (!StringUtil.hasText(path)) { - continue; - } - currentPath = String.format("%s/%s", currentPath, path); - Node node; - try { - node = vosClient.getNode(currentPath); - } catch (ResourceNotFoundException e) { - throw new IllegalStateException(String.format("node %s not found", path)); - } catch (Exception e) { - throw new IllegalStateException(String.format("node %s error because %s", path, e.getMessage())); - } + Node node; + try { + node = vosClient.getNode(parentPath); + } catch (ResourceNotFoundException e) { + throw new IllegalStateException(String.format("DOI parent node %s not found", parentPath)); + } catch (Exception e) { + throw new IllegalStateException(String.format("DOI parent node %s error because %s", parentPath, e.getMessage())); + } - // confirm it's a ContainerNode - if (!(node instanceof ContainerNode)) { - throw new IllegalStateException(String.format("node %s is not a ContainerNode", path)); - } - ContainerNode containerNode = (ContainerNode) node; + // confirm it's a ContainerNode + if (!(node instanceof ContainerNode)) { + throw new IllegalStateException(String.format("DOI parent node %s is not a ContainerNode", parentPath)); + } + ContainerNode containerNode = (ContainerNode) node; - // check node owner - String ownerID = containerNode.ownerDisplay; - if (!adminUsername.equals(ownerID)) { - throw new IllegalStateException(String.format("node %s owner %s doesn't match configured admin user %s", path, ownerID, adminUsername)); - } + // check node owner + String ownerID = containerNode.ownerDisplay; + if (!adminUsername.equals(ownerID)) { + throw new IllegalStateException(String.format("DOI parent node %s owner %s doesn't match configured admin user %s", parentPath, ownerID, adminUsername)); + } - // check node has public access - if (!containerNode.isPublic) { - throw new IllegalStateException(String.format("node %s must have isPublic set to true", path)); - } + // check node has public access + if (!containerNode.isPublic) { + throw new IllegalStateException(String.format("DOI parent node %s must have isPublic set to true", parentPath)); + } - // check inheritPermissions is true (does inheritPermissions need to be true?) - if (!containerNode.inheritPermissions) { - throw new IllegalStateException(String.format("node %s must have inheritPermissions set to true", path)); - } + // check inheritPermissions is true + if (!containerNode.inheritPermissions) { + throw new IllegalStateException(String.format("DOI parent node %s must have inheritPermissions set to true", parentPath)); } } diff --git a/doi/src/main/java/ca/nrc/cadc/doi/GetAction.java b/doi/src/main/java/ca/nrc/cadc/doi/GetAction.java index 68537f5..fcfa2a5 100644 --- a/doi/src/main/java/ca/nrc/cadc/doi/GetAction.java +++ b/doi/src/main/java/ca/nrc/cadc/doi/GetAction.java @@ -237,7 +237,7 @@ private List getOwnedDOIList() throws Exception { ContainerNode doiRootNode = vospaceDoiClient.getContainerNode(""); if (doiRootNode != null) { for (Node childNode : doiRootNode.getNodes()) { - // TODO: configure doiadmin viewing of all nodes + // TODO: configure doi admin viewing of all nodes NodeProperty requester = childNode.getProperty(DOI_VOS_REQUESTER_PROP); if (requester != null && requester.getValue() != null) { try { diff --git a/doi/src/main/java/ca/nrc/cadc/doi/PostAction.java b/doi/src/main/java/ca/nrc/cadc/doi/PostAction.java index c6ed3be..956250d 100644 --- a/doi/src/main/java/ca/nrc/cadc/doi/PostAction.java +++ b/doi/src/main/java/ca/nrc/cadc/doi/PostAction.java @@ -86,7 +86,6 @@ import ca.nrc.cadc.net.OutputStreamWrapper; import ca.nrc.cadc.net.ResourceNotFoundException; import ca.nrc.cadc.util.Base64; -import ca.nrc.cadc.util.StringUtil; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -133,7 +132,7 @@ public PostAction() { public void doAction() throws Exception { super.init(true); - // Do DOI creation work as doiadmin + // Do DOI creation work as doi admin Subject.doAs(getAdminSubject(), (PrivilegedExceptionAction) () -> { if (doiAction != null) { performDoiAction(); @@ -494,7 +493,7 @@ private void verifyImmutableFields(Resource r1, Resource r2) { String msg = String.format("namespace update is not allowed, expected: %s, actual: %s", r2.getNamespace(), r1.getNamespace()); throw new IllegalArgumentException(msg); - } else if (!r1.getPublisher().equals(r2.getPublisher())) { + } else if (!r1.getPublisher().getValue().equals(r2.getPublisher().getValue())) { String msg = String.format("software error, publisher is different, expected: %s, actual: %s", r2.getPublisher(), r1.getPublisher()); throw new IllegalArgumentException(msg); @@ -523,7 +522,7 @@ private void verifyNull(Object o1, Object o2, String field) { } private void verifyIdentifier(Identifier i1, Identifier i2) { - if (!i1.equals(i2)) { + if (!i1.getValue().equals(i2.getValue()) && !i1.getIdentifierType().equals(i2.getIdentifierType())) { String msg = String.format("identifier update is not allowed, expected: %s, actual: %s", i2, i1); throw new IllegalArgumentException(msg); @@ -543,7 +542,7 @@ private void verifyResourceType(ResourceType rt1, ResourceType rt2) { private void setPermissions(Node node, GroupURI doiGroup) { // Before completion, directory is visible in AstroDataCitationDOI directory, but not readable - // except by doiadmin and calling user's group + // except by doi admin and calling user's group node.isPublic = false; // All folders will be only readable by requester @@ -560,14 +559,14 @@ private void createDOI() throws Exception { throw new IllegalArgumentException("No content"); } - boolean randomName = Boolean.parseBoolean(config.getFirstPropertyValue(DoiInitAction.TEST_RANDOM_NAME_KEY)); + boolean randomTestID = Boolean.parseBoolean(config.getFirstPropertyValue(DoiInitAction.RANDOM_TEST_ID_KEY)); String nextDoiSuffix; - if (randomName) { + if (randomTestID) { nextDoiSuffix = getRandomDOISuffix(); log.warn("Random DOI suffix: " + nextDoiSuffix); } else { - // Determine next DOI number - // Note: The generated DOI number is the suffix which should be case insensitive. + // Determine next DOI ID + // Note: The generated DOI ID is the suffix which should be case insensitive. // Since we are using a number, it does not matter. However if we decide // to use a String, we should only generate either a lowercase or an // uppercase String. (refer to https://support.datacite.org/docs/doi-basics) @@ -575,10 +574,10 @@ private void createDOI() throws Exception { log.debug("Next DOI suffix: " + nextDoiSuffix); } - // update the template with the new DOI number + // Update the resource with the DOI ID assignIdentifier(resource.getIdentifier(), accountPrefix + "/" + nextDoiSuffix); - //Add a Created date to the Resource object + // Add a Created date to the Resource object LocalDate localDate = LocalDate.now(); String createdDate = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); Date doiDate = new Date(createdDate, DateType.CREATED); @@ -586,17 +585,16 @@ private void createDOI() throws Exception { resource.dates = new ArrayList<>(); resource.dates.add(doiDate); - // Create the group that is able to administer the DOI process, use configured test GroupURI if found - GroupURI guri; - String configuredGroupUri = config.getFirstPropertyValue(DoiInitAction.TEST_GROUP_URI_KEY); - if (StringUtil.hasText(configuredGroupUri)) { - guri = new GroupURI(URI.create(configuredGroupUri)); - log.warn("Configured DOI group: " + guri); + // Create the group that is able to administer the DOI process + String groupName; + if (randomTestID) { + groupName = TEST_DOI_GROUP_PREFIX + nextDoiSuffix; } else { - guri = createDoiGroup(nextDoiSuffix); - log.debug("Created DOI group: " + guri); + groupName = DOI_GROUP_PREFIX + nextDoiSuffix; } - + GroupURI guri = createDoiGroup(groupName); + log.debug("Created DOI group: " + guri); + // Create the VOSpace area for DOI work ContainerNode doiFolder = createDOIDirectory(guri, nextDoiSuffix); @@ -621,8 +619,7 @@ private void createDOI() throws Exception { private GroupURI createDoiGroup(String groupName) throws Exception { // Create group to use for applying permissions - String gmsResourceID = config.getFirstPropertyValue(DoiInitAction.GMS_RESOURCE_ID_KEY); - String group = String.format("%s?%s%s", gmsResourceID, DOI_GROUP_PREFIX, groupName); + String group = String.format("%s?%s", gmsResourceID, groupName); GroupURI guri = new GroupURI(URI.create(group)); log.debug("creating group: " + guri); @@ -639,7 +636,6 @@ private GroupURI createDoiGroup(String groupName) throws Exception { // expose it as a server error throw new RuntimeException(gaeex); } - log.debug("doi group created: " + guri); return guri; } @@ -664,7 +660,7 @@ private ContainerNode createDOIDirectory(GroupURI guri, String folderName) ContainerNode newFolder = new ContainerNode(folderName); // Before completion, directory is visible in AstroDataCitationDOI directory, - // but not readable except by doiadmin and calling user's group + // but not readable except by doi admin and calling user's group setPermissions(newFolder, guri); newFolder.getProperties().addAll(properties); diff --git a/doi/src/main/java/ca/nrc/cadc/doi/ServiceAvailability.java b/doi/src/main/java/ca/nrc/cadc/doi/ServiceAvailability.java index 88cf3e7..01cefdc 100644 --- a/doi/src/main/java/ca/nrc/cadc/doi/ServiceAvailability.java +++ b/doi/src/main/java/ca/nrc/cadc/doi/ServiceAvailability.java @@ -112,7 +112,7 @@ public Availability getStatus() { String note = "service is accepting requests"; try { MultiValuedProperties config = DoiInitAction.getConfig(); - URI vaultResourceID = URI.create(config.getFirstPropertyValue(DoiInitAction.VAULT_RESOURCE_ID_KEY)); + URI vaultResourceID = DoiInitAction.getVospaceResourceID(config); log.debug("vault resourceID: " + vaultResourceID); // check other services we depend on (vault, gms, datacite)