diff --git a/cadc-test-vos/build.gradle b/cadc-test-vos/build.gradle index f7b22843..cf7cf14a 100644 --- a/cadc-test-vos/build.gradle +++ b/cadc-test-vos/build.gradle @@ -16,7 +16,7 @@ sourceCompatibility = 1.8 group = 'org.opencadc' -version = '2.1.6' +version = '2.1.7' description = 'OpenCADC VOSpace test library' def git_url = 'https://github.com/opencadc/vos' @@ -26,7 +26,7 @@ dependencies { implementation 'org.opencadc:cadc-gms:[1.0.5,)' implementation 'org.opencadc:cadc-vos:[2.0,)' implementation 'org.opencadc:cadc-uws:[1.0,2.0)' - implementation 'org.opencadc:cadc-registry:[1.7.4,2.0)' + implementation 'org.opencadc:cadc-registry:[1.7.6,2.0)' implementation 'junit:junit:[4.0,)' implementation 'org.apache.commons:commons-compress:[1.12,)' diff --git a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/FilesTest.java b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/FilesTest.java index fc1fc5ff..e40a03e6 100644 --- a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/FilesTest.java +++ b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/FilesTest.java @@ -76,16 +76,26 @@ import ca.nrc.cadc.net.HttpPost; import ca.nrc.cadc.reg.Standards; import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.util.HexUtil; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.StringWriter; import java.net.URI; import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.LocalDateTime; import javax.security.auth.Subject; import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Test; +import org.opencadc.vospace.DataNode; +import org.opencadc.vospace.NodeProperty; import org.opencadc.vospace.VOS; import org.opencadc.vospace.VOSURI; import org.opencadc.vospace.transfer.Direction; @@ -103,8 +113,8 @@ protected FilesTest(URI resourceID, File testCert) { super(resourceID, testCert); RegistryClient regClient = new RegistryClient(); - this.filesServiceURL = regClient.getServiceURL(resourceID, Standards.VOSPACE_FILES_20, AuthMethod.ANON); - log.info(String.format("%s: %s", Standards.VOSPACE_FILES_20, filesServiceURL)); + this.filesServiceURL = regClient.getServiceURL(resourceID, Standards.VOSPACE_FILES, AuthMethod.ANON); + log.info(String.format("%s: %s", Standards.VOSPACE_FILES, filesServiceURL)); } @Test @@ -116,6 +126,9 @@ public void fileTest() { VOSURI nodeURI = getVOSURI(name); log.debug("files-data-node URL: " + nodeURL); + // cleanup + delete(nodeURL, false); + // Create a Transfer Transfer transfer = new Transfer(nodeURI.getURI(), Direction.pushToVoSpace); transfer.version = VOS.VOSPACE_21; @@ -165,8 +178,26 @@ public void fileTest() { ByteArrayInputStream is = new ByteArrayInputStream(expected.getBytes()); put(endpoint, is, VOSTest.TEXT_CONTENT_TYPE); - // get the file using files endpoint URL fileURL = getNodeURL(filesServiceURL, name); + + // test HEAD + log.info("HEAD: " + fileURL); + + HttpGet headFile = new HttpGet(fileURL, out); + headFile.setHeadOnly(true); + Subject.doAs(authSubject, new RunnableAction(headFile)); + log.info("GET response: " + headFile.getResponseCode() + " " + headFile.getThrowable()); + Assert.assertEquals("expected GET response code = 200", 200, headFile.getResponseCode()); + Assert.assertNull("expected GET throwable == null", headFile.getThrowable()); + Assert.assertEquals(expected.getBytes().length, headFile.getContentLength()); + String contentDisposition = "inline; filename=\"" + name + "\""; + Assert.assertTrue(contentDisposition.equals(headFile.getResponseHeader("Content-Disposition"))); + if (headFile.getDigest() != null) { + Assert.assertTrue(computeChecksumURI(expected.getBytes()).equals(headFile.getDigest())); + } + Assert.assertTrue(System.currentTimeMillis() > headFile.getLastModified().getTime()); + Assert.assertEquals(VOSTest.TEXT_CONTENT_TYPE, headFile.getContentType()); + log.info("GET: " + fileURL); out = new ByteArrayOutputStream(); HttpGet getFile = new HttpGet(fileURL, out); @@ -174,6 +205,14 @@ public void fileTest() { log.info("GET response: " + getFile.getResponseCode() + " " + getFile.getThrowable()); Assert.assertEquals("expected GET response code = 200", 200, getFile.getResponseCode()); Assert.assertNull("expected GET throwable == null", getFile.getThrowable()); + Assert.assertEquals(expected.getBytes().length, headFile.getContentLength()); + Assert.assertTrue(contentDisposition.equals(headFile.getResponseHeader("Content-Disposition"))); + if (headFile.getDigest() != null) { + Assert.assertTrue(computeChecksumURI(expected.getBytes()).equals(headFile.getDigest())); + } + Assert.assertTrue(System.currentTimeMillis() > headFile.getLastModified().getTime()); + Assert.assertEquals(VOSTest.TEXT_CONTENT_TYPE, headFile.getContentType()); + String actual = out.toString(); log.debug("file content: " + actual); @@ -181,11 +220,65 @@ public void fileTest() { // Delete the node delete(nodeURL); - } catch (Exception e) { log.error("Unexpected error", e); Assert.fail("Unexpected error: " + e); } } + @Test + public void emptyFileTest() throws Exception { + // Put an empty DataNode + String name = "empty-files-data-node"; + URL nodeURL = getNodeURL(nodesServiceURL, name); + VOSURI nodeURI = getVOSURI(name); + log.debug("empty-files-data-node URL: " + nodeURL); + // cleanup + delete(nodeURL, false); + + // PUT the node + log.info("put: " + nodeURI + " -> " + nodeURL); + DataNode testNode = new DataNode(name); + put(nodeURL, nodeURI, testNode); + + URL fileURL = getNodeURL(filesServiceURL, name); + + // test HEAD + log.info("HEAD: " + fileURL); + OutputStream out = new ByteArrayOutputStream(); + HttpGet headFile = new HttpGet(fileURL, out); + headFile.setHeadOnly(true); + Subject.doAs(authSubject, new RunnableAction(headFile)); + log.info("GET response: " + headFile.getResponseCode() + " " + headFile.getThrowable()); + Assert.assertEquals("expected GET response code = 200", 200, headFile.getResponseCode()); + Assert.assertNull("expected GET throwable == null", headFile.getThrowable()); + Assert.assertEquals(0, headFile.getContentLength()); + String contentDisposition = "inline; filename=\"" + name + "\""; + Assert.assertTrue(contentDisposition.equals(headFile.getResponseHeader("Content-Disposition"))); + Assert.assertTrue(System.currentTimeMillis() > headFile.getLastModified().getTime()); + + log.info("GET: " + fileURL); + out = new ByteArrayOutputStream(); + HttpGet getFile = new HttpGet(fileURL, out); + Subject.doAs(authSubject, new RunnableAction(getFile)); + log.info("GET response: " + getFile.getResponseCode() + " " + getFile.getThrowable()); + Assert.assertEquals("expected GET response code = 204", 204, getFile.getResponseCode()); + Assert.assertNull("expected GET throwable == null", getFile.getThrowable()); + Assert.assertEquals(0, headFile.getContentLength()); + Assert.assertTrue(contentDisposition.equals(headFile.getResponseHeader("Content-Disposition"))); + Assert.assertTrue(System.currentTimeMillis() > headFile.getLastModified().getTime()); + + + // Delete the node + delete(nodeURL); + + } + + protected static URI computeChecksumURI(byte[] input) throws NoSuchAlgorithmException, IOException { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(input); + byte[] digest = md.digest(); + return URI.create("md5:" + HexUtil.toHex(digest)); + } + } diff --git a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/NodesTest.java b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/NodesTest.java index e1658b39..2cc5e189 100644 --- a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/NodesTest.java +++ b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/NodesTest.java @@ -78,6 +78,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.security.PrivilegedExceptionAction; @@ -224,7 +225,7 @@ public void testContainerNode() { put(subDirURL, subDirURI, subDirNode); Assert.fail("New node should fail when parent is locked"); } catch (AssertionError ex) { - Assert.assertEquals("expected PUT response code = 200 expected:<200> but was:<403>", + Assert.assertEquals("expected PUT response code in [200, 201]", ex.getMessage()); } } @@ -909,45 +910,30 @@ public void testDetail() { } } - //@Test - public void dataViewTest() { + @Test + public void testDataView() { try { - // upload test file String name = "view-data-node"; URL nodeURL = getNodeURL(nodesServiceURL, name); VOSURI nodeURI = getVOSURI(name); DataNode node = new DataNode(name); + + // cleanup + delete(nodeURL, false); + put(nodeURL, nodeURI, node); - // get the node with view=data - URL getURL = new URL(nodeURL + "?view=data"); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - HttpGet get = new HttpGet(getURL, out); - get.setFollowRedirects(false); - Subject.doAs(authSubject, new RunnableAction(get)); - Assert.assertNotNull(get.getThrowable()); - URL redirectURL = get.getRedirectURL(); - Assert.assertNotNull(redirectURL); - log.debug("location = " + redirectURL); - - String query = redirectURL.getQuery(); - String[] params = query.split("&"); - Assert.assertEquals(3, params.length); - for (String p : params) { - String[] pv = p.split("="); - String key = pv[0]; - String val = NetUtil.decode(pv[1]); - Assert.assertEquals(2, pv.length); - if ("target".equalsIgnoreCase(key)) { - Assert.assertEquals(nodeURI.getURI().toASCIIString(), val); - } else if ("protocol".equalsIgnoreCase(key)) { - Assert.assertEquals(VOS.PROTOCOL_HTTPS_GET.toASCIIString(), val); - } else if ("direction".equalsIgnoreCase(key)) { - Assert.assertEquals(Direction.pullFromVoSpace.getValue(), val); - } else { - Assert.fail(String.format("unexpected transfer parameter: %s = %s", key, val)); - } - } + URL viewDataNodeUrl = getNodeURL(nodesServiceURL, name + "?view=data"); + HttpGet getRequest = new HttpGet(viewDataNodeUrl, false); + log.debug("GET: " + viewDataNodeUrl); + Subject.doAs(authSubject, new RunnableAction(getRequest)); + log.debug("GET responseCode: " + getRequest.getResponseCode() + " " + getRequest.getThrowable()); + Assert.assertEquals(HttpURLConnection.HTTP_SEE_OTHER, getRequest.getResponseCode()); + Assert.assertNull(getRequest.getThrowable()); + String expectedFilesLocation = nodeURL.toString().replace("/nodes/", "/files/"); + log.debug("view=data redirects " + expectedFilesLocation + " vs " + + getRequest.getResponseHeader("Location")); + Assert.assertTrue(expectedFilesLocation.equalsIgnoreCase(getRequest.getResponseHeader("Location"))); if (cleanupOnSuccess) { delete(nodeURL); diff --git a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/PackageTest.java b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/PackageTest.java index 70b52bc7..1e5decc4 100644 --- a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/PackageTest.java +++ b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/PackageTest.java @@ -313,7 +313,7 @@ public void multipleTargetTest() { expected.add(file2); doTest(targets, expected, TAR_CONTENT_TYPE); -// doTest(targets, expected, ZIP_CONTENT_TYPE); + // doTest(targets, expected, ZIP_CONTENT_TYPE); // cleanup delete(nodes); @@ -327,14 +327,14 @@ public void multipleTargetTest() { @Test public void fullTest() { try { - // /root-folder/ - // /root-folder/file-1.txt - // /root-folder/folder-1/ - // /root-folder/folder-2/ - // /root-folder/folder-2/file-2.txt - // /root-folder/folder-2/file-3.txt - // /root-folder/folder-2/folder-3/ - // /root-folder/folder-2/folder-3/link-1.txt + // /root-folder/ + // /root-folder/file-1.txt + // /root-folder/folder-1/ + // /root-folder/folder-2/ + // /root-folder/folder-2/file-2.txt + // /root-folder/folder-2/file-3.txt + // /root-folder/folder-2/folder-3/ + // /root-folder/folder-2/folder-3/link-1.txt // nodes paths String root = "full-root-folder"; @@ -387,7 +387,7 @@ public void fullTest() { expected.add(file3); doTest(targets, expected, TAR_CONTENT_TYPE); -// doTest(targets, expected, ZIP_CONTENT_TYPE); + // doTest(targets, expected, ZIP_CONTENT_TYPE); // cleanup delete(nodes); @@ -567,7 +567,7 @@ private File extractPackage(File packageFile, String contentType) ArchiveInputStream archiveInputStream = archiveStreamFactory.createArchiveInputStream( archiveType, inputStream); ArchiveEntry entry; - while((entry = archiveInputStream.getNextEntry()) != null) { + while ((entry = archiveInputStream.getNextEntry()) != null) { if (!archiveInputStream.canReadEntryData(entry)) { log.debug("unable to read archive entry: " + entry.getName()); continue; diff --git a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/VOSTest.java b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/VOSTest.java index 81cceb8d..1fbe9a3b 100644 --- a/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/VOSTest.java +++ b/cadc-test-vos/src/main/java/org/opencadc/conformance/vos/VOSTest.java @@ -89,6 +89,9 @@ import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import javax.security.auth.Subject; import org.apache.log4j.Level; import org.apache.log4j.Logger; @@ -205,9 +208,9 @@ public void put(URL nodeURL, InputStream is, String contentType) { log.debug("PUT " + nodeURL); Subject.doAs(authSubject, new RunnableAction(put)); log.debug("PUT responseCode: " + put.getResponseCode()); - // TODO revert response code back to expected 201 - Assert.assertEquals("expected PUT response code = 200", - 200, put.getResponseCode()); + Integer[] validCodes = new Integer[]{200, 201}; + Assert.assertTrue("expected PUT response code in [200, 201]", + Arrays.asList(validCodes).contains(put.getResponseCode())); Assert.assertNull("expected PUT throwable == null", put.getThrowable()); } diff --git a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/actions/GetNodeAction.java b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/actions/GetNodeAction.java index baa14cce..c4082d1e 100644 --- a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/actions/GetNodeAction.java +++ b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/actions/GetNodeAction.java @@ -72,6 +72,8 @@ import ca.nrc.cadc.net.HttpTransfer; import ca.nrc.cadc.util.StringUtil; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; import javax.security.auth.Subject; import org.apache.log4j.Logger; import org.opencadc.vospace.ContainerNode; @@ -118,10 +120,24 @@ public GetNodeAction() { @Override public void doAction() throws Exception { VOSURI target = getTargetURI(); + + String view = syncInput.getParameter(QUERY_PARAM_VIEW); + if ("data".equalsIgnoreCase(view)) { + // makes the assumption that /files endpoint is a sibling of /nodes + URI requestURI = URI.create(syncInput.getRequestURI()); + String filesPath = syncInput.getContextPath() + "/files" + target.getPath(); + // query params are not passed through + URI filesURI = new URI(requestURI.getScheme(), requestURI.getHost(), filesPath, null); + String location = filesURI.toASCIIString(); + log.debug("Redirecting view=data request to " + location); + syncOutput.setHeader("Location", location); + syncOutput.setCode(HttpURLConnection.HTTP_SEE_OTHER); + return; + } + final String detailLevel = syncInput.getParameter(QUERY_PARAM_DETAIL); // get parent node - // TBD: resolveLinks=true? PathResolver pathResolver = new PathResolver(nodePersistence, voSpaceAuthorizer); Node serverNode = pathResolver.getNode(target.getPath(), false); if (serverNode == null) { diff --git a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/pkg/VospacePackageRunner.java b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/pkg/VospacePackageRunner.java index 6e3eb94a..26d09335 100644 --- a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/pkg/VospacePackageRunner.java +++ b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/pkg/VospacePackageRunner.java @@ -220,10 +220,8 @@ protected URL getURL(VOSURI nodeURI) // Use a temporary Job with just the remote IP to avoid the original Job in // a transfer URL which may close the Job. - Job pkgJob = new Job(); - pkgJob.setRemoteIP(remoteIP); TransferGenerator transferGenerator = nodePersistence.getTransferGenerator(); - List protocols = transferGenerator.getEndpoints(nodeURI, packageTransfer, pkgJob, null); + List protocols = transferGenerator.getEndpoints(nodeURI, packageTransfer, null); log.debug("num transfer protocols: " + protocols.size()); // Get the node endpoint from the first protocol diff --git a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/InternalTransferAction.java b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/InternalTransferAction.java index 857866fc..371106f2 100644 --- a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/InternalTransferAction.java +++ b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/InternalTransferAction.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2021. (c) 2021. +* (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 @@ -155,8 +155,8 @@ public void doAction() throws Exception { } if ((rn.node != null) && !(rn.node instanceof ContainerNode)) { - throw NodeFault.NodeNotFound.getStatus("Resolved parent (" + - loc.getURI(rn.node) + ") is not ContainerNode in destination " + destURI.getPath()); + throw NodeFault.NodeNotFound.getStatus("Resolved parent (" + + loc.getURI(rn.node) + ") is not ContainerNode in destination " + destURI.getPath()); } ContainerNode destContainer; String destName; @@ -178,8 +178,8 @@ public void doAction() throws Exception { String destPath = Utils.getPath(destContainer); if (destPath.contains(srcPath)) { throw NodeFault.ContainerNotFound.getStatus( - "Cannot move container node into its descendants: source " + - "path " + srcPath + " in resolved path " + destPath); + "Cannot move container node into its descendants: source " + + "path " + srcPath + " in resolved path " + destPath); } } log.debug("Resolved move dest: " + destContainer + " move name: " + destName); diff --git a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferDetailsServlet.java b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferDetailsServlet.java index a42a5bca..00fcad2b 100644 --- a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferDetailsServlet.java +++ b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferDetailsServlet.java @@ -324,7 +324,7 @@ public Object run() throws Exception { result.setContentLength(transfer.getContentLength()); // Add protocol list with endpoints just built - List protos = transferGen.getEndpoints(target, transfer, job, additionalParams); + List protos = transferGen.getEndpoints(target, transfer, additionalParams); result.getProtocols().addAll(protos); result.setView(transfer.getView()); diff --git a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferGenerator.java b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferGenerator.java index fb712dc8..205c6df0 100644 --- a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferGenerator.java +++ b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferGenerator.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2023. (c) 2023. +* (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 @@ -69,7 +69,6 @@ package org.opencadc.vospace.server.transfers; -import ca.nrc.cadc.uws.Job; import ca.nrc.cadc.uws.Parameter; import java.util.List; import org.opencadc.vospace.VOSURI; @@ -94,7 +93,6 @@ and can sort the result list in order of preference (since clients should * * @param target single target URI from the transfer * @param transfer The transfer object with requested protocol(s) - * @param job The UWS job associated with the transfer request. * @param additionalParams Any additional parameters associated with the request. * * @return list of protocol(s) with endpoints @@ -102,6 +100,6 @@ and can sort the result list in order of preference (since clients should * @throws Exception implementations are responsible for throwing right exception * to generate the correct output */ - List getEndpoints(VOSURI target, Transfer transfer, Job job, List additionalParams) + List getEndpoints(VOSURI target, Transfer transfer, List additionalParams) throws Exception; } diff --git a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferRunner.java b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferRunner.java index 2128eccc..9b8d1848 100644 --- a/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferRunner.java +++ b/cadc-vos-server/src/main/java/org/opencadc/vospace/server/transfers/TransferRunner.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2009. (c) 2009. + * (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 @@ -387,7 +387,7 @@ private void doTransferRedirect(Transfer transfer, List additionalPar TransferGenerator gen = nodePersistence.getTransferGenerator(); //List plist = TransferUtil.getTransferEndpoints(trans, job, additionalParameters); - List plist = gen.getEndpoints(target, transfer, job, additionalParameters); + List plist = gen.getEndpoints(target, transfer, additionalParameters); if (plist.isEmpty()) { sendError(ExecutionPhase.EXECUTING, ErrorType.FATAL, "requested transfer specs not supported", diff --git a/cavern/build.gradle b/cavern/build.gradle index 0d1e033e..040e9378 100644 --- a/cavern/build.gradle +++ b/cavern/build.gradle @@ -30,7 +30,7 @@ dependencies { implementation 'org.opencadc:cadc-util-fs:[1.1.0,)' implementation 'org.opencadc:cadc-log:[1.0,)' implementation 'org.opencadc:cadc-permissions:[0.3.3,)' - implementation 'org.opencadc:cadc-registry:[1.7.2,)' + implementation 'org.opencadc:cadc-registry:[1.7.6,)' implementation 'org.opencadc:cadc-vosi:[1.4.3,)' implementation 'org.opencadc:cadc-rest:[1.3.18,)' implementation 'org.opencadc:cadc-uws:[1.0.3,)' @@ -50,6 +50,6 @@ dependencies { testImplementation 'org.opencadc:cadc-test-uws:[1.0,)' testImplementation 'org.opencadc:cadc-access-control-identity:[1.1.0,)' - intTestImplementation 'org.opencadc:cadc-test-vos:[2.1.6,)' + intTestImplementation 'org.opencadc:cadc-test-vos:[2.1.7,)' intTestImplementation 'org.opencadc:cadc-test-vosi:[1.0.11,)' } diff --git a/cavern/src/main/java/org/opencadc/cavern/files/CavernURLGenerator.java b/cavern/src/main/java/org/opencadc/cavern/files/CavernURLGenerator.java index 041649c7..2f4c7206 100644 --- a/cavern/src/main/java/org/opencadc/cavern/files/CavernURLGenerator.java +++ b/cavern/src/main/java/org/opencadc/cavern/files/CavernURLGenerator.java @@ -76,7 +76,6 @@ import ca.nrc.cadc.reg.Interface; import ca.nrc.cadc.reg.Standards; import ca.nrc.cadc.reg.client.RegistryClient; -import ca.nrc.cadc.uws.Job; import ca.nrc.cadc.uws.Parameter; import java.io.File; import java.io.FileNotFoundException; @@ -136,7 +135,7 @@ public CavernURLGenerator(FileSystemNodePersistence nodePersistence) { } @Override - public List getEndpoints(VOSURI target, Transfer transfer, Job job, List additionalParams) throws Exception { + public List getEndpoints(VOSURI target, Transfer transfer, List additionalParams) throws Exception { log.debug("getEndpoints: " + target); if (target == null) { throw new IllegalArgumentException("target is required"); @@ -315,7 +314,7 @@ void initFilesCap() throws IOException, ResourceNotFoundException { // ugh: self lookup RegistryClient reg = new RegistryClient(); Capabilities caps = reg.getCapabilities(nodePersistence.getResourceID()); - this.filesCap = caps.findCapability(Standards.VOSPACE_FILES_20); + this.filesCap = caps.findCapability(Standards.VOSPACE_FILES); } } } diff --git a/cavern/src/main/java/org/opencadc/cavern/files/FileAction.java b/cavern/src/main/java/org/opencadc/cavern/files/FileAction.java index 5b1af0a9..c974a75e 100644 --- a/cavern/src/main/java/org/opencadc/cavern/files/FileAction.java +++ b/cavern/src/main/java/org/opencadc/cavern/files/FileAction.java @@ -120,7 +120,7 @@ protected VOSURI getNodeURI() { } @Override - public void initAction() throws ResourceNotFoundException, IllegalArgumentException { + public void initAction() throws IllegalArgumentException { String jndiNodePersistence = appName + "-" + NodePersistence.class.getName(); try { Context ctx = new InitialContext(); @@ -132,6 +132,11 @@ public void initAction() throws ResourceNotFoundException, IllegalArgumentExcept } catch (NamingException oops) { throw new RuntimeException("BUG: NodePersistence implementation not found with JNDI key " + jndiNodePersistence, oops); } + + checkReadable(); + if (this instanceof PutAction) { + checkWritable(); + } } private VOSURI parsePath(String path, boolean hasToken) { diff --git a/cavern/src/main/java/org/opencadc/cavern/files/GetAction.java b/cavern/src/main/java/org/opencadc/cavern/files/GetAction.java index e3f093aa..544cec83 100644 --- a/cavern/src/main/java/org/opencadc/cavern/files/GetAction.java +++ b/cavern/src/main/java/org/opencadc/cavern/files/GetAction.java @@ -97,12 +97,17 @@ public void doAction() throws Exception { ByteCountOutputStream out = null; try { Path source = resolveAndSetMetadata(); - - out = new ByteCountOutputStream(syncOutput.getOutputStream()); - log.debug("Starting copy of file " + source); - Files.copy(source, out); - log.debug("Completed copy of file " + source); - out.flush(); + + if (source.toFile().length() == 0) { + syncOutput.setCode(204); + log.debug("Empty file " + source); + } else { + out = new ByteCountOutputStream(syncOutput.getOutputStream()); + log.debug("Starting copy of file " + source); + Files.copy(source, out); + log.debug("Completed copy of file " + source); + out.flush(); + } } catch (NodeNotFoundException | FileNotFoundException | NoSuchFileException e) { log.debug("404 error with GET: ", e); diff --git a/cavern/src/main/java/org/opencadc/cavern/files/HeadAction.java b/cavern/src/main/java/org/opencadc/cavern/files/HeadAction.java index 18a6da76..fbebbb80 100644 --- a/cavern/src/main/java/org/opencadc/cavern/files/HeadAction.java +++ b/cavern/src/main/java/org/opencadc/cavern/files/HeadAction.java @@ -79,6 +79,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.security.AccessControlException; +import java.util.Date; import javax.security.auth.Subject; import org.apache.log4j.Logger; import org.opencadc.permissions.ReadGrant; @@ -88,6 +89,7 @@ import org.opencadc.vospace.NodeNotFoundException; import org.opencadc.vospace.VOS; import org.opencadc.vospace.VOSURI; +import org.opencadc.vospace.io.NodeWriter; import org.opencadc.vospace.server.NodeFault; /** @@ -147,15 +149,19 @@ Path resolveAndSetMetadata() throws Exception { log.debug("node path resolved: " + node.getName()); log.debug("node type: " + node.getClass().getCanonicalName()); - syncOutput.setHeader("Content-Disposition", "inline; filename=" + nodeURI.getName()); + syncOutput.setHeader("Content-Disposition", "inline; filename=\"" + nodeURI.getName() + "\""); syncOutput.setHeader("Content-Type", node.getPropertyValue(VOS.PROPERTY_URI_TYPE)); syncOutput.setHeader("Content-Encoding", node.getPropertyValue(VOS.PROPERTY_URI_CONTENTENCODING)); syncOutput.setHeader("Content-Length", node.getPropertyValue(VOS.PROPERTY_URI_CONTENTLENGTH)); + if (node.getPropertyValue(VOS.PROPERTY_URI_DATE) != null) { + Date lastMod = NodeWriter.getDateFormat().parse(node.getPropertyValue(VOS.PROPERTY_URI_DATE)); + syncOutput.setLastModified(lastMod); + } String contentMD5 = node.getPropertyValue(VOS.PROPERTY_URI_CONTENTMD5); if (contentMD5 != null) { try { - URI md5 = new URI(contentMD5); + URI md5 = new URI("md5:" + contentMD5); syncOutput.setDigest(md5); } catch (URISyntaxException ex) { log.error("found invalid checksum attribute " + contentMD5 + " on node " + nodeURI); diff --git a/cavern/src/main/webapp/capabilities.xml b/cavern/src/main/webapp/capabilities.xml index 090778b2..7ef57079 100644 --- a/cavern/src/main/webapp/capabilities.xml +++ b/cavern/src/main/webapp/capabilities.xml @@ -89,7 +89,7 @@ --> - + https://replace.com/cavern/files diff --git a/cavern/src/main/webapp/service.yaml b/cavern/src/main/webapp/service.yaml index 9f2cec56..9c5c9da5 100644 --- a/cavern/src/main/webapp/service.yaml +++ b/cavern/src/main/webapp/service.yaml @@ -83,34 +83,27 @@ paths: description: Service busy default: description: Unexpected error - put: + head: description: | - Create a file at a specified location + Get the metadata of the specified file. tags: - Files - consumes: - - text/xml responses: '200': description: Successful response '400': - description: If filePath ends in a data node. + description: If the user requested a container node (directory). '403': description: If the user does not have permission. '404': - description: If the path to the file could not be found. + description: If the file or part of the path to the file could not be found. '500': description: Internal error '503': description: Service busy default: description: Unexpected error - parameters: - - name: filePath - in: path - description: The path for the file - required: true - type: string + /nodes/{nodePath}: get: description: | @@ -150,6 +143,13 @@ paths: description: for container nodes, the number of children to return. required: false type: string + - name: view + in: query + description: for data nodes, a specific view + required: false + type: string + enum: + - data post: description: | Set the property values for a specific Node diff --git a/cavern/src/test/java/org/opencadc/cavern/nodes/PosixIdentityManagerTest.java b/cavern/src/test/java/org/opencadc/cavern/nodes/PosixIdentityManagerTest.java index a4054b27..3d327a26 100644 --- a/cavern/src/test/java/org/opencadc/cavern/nodes/PosixIdentityManagerTest.java +++ b/cavern/src/test/java/org/opencadc/cavern/nodes/PosixIdentityManagerTest.java @@ -67,26 +67,14 @@ package org.opencadc.cavern.nodes; - -import org.opencadc.cavern.nodes.PosixIdentityManager; -import ca.nrc.cadc.ac.ACIdentityManager; import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.HttpPrincipal; -import ca.nrc.cadc.auth.IdentityManager; -import ca.nrc.cadc.auth.NumericPrincipal; import ca.nrc.cadc.auth.PosixPrincipal; -import ca.nrc.cadc.auth.SSLUtil; -import ca.nrc.cadc.util.FileUtil; import ca.nrc.cadc.util.Log4jInit; - -import java.io.File; import java.security.Principal; import java.security.PrivilegedExceptionAction; import java.util.Set; -import java.util.UUID; - import javax.security.auth.Subject; - import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.Assert;