Loading src/main/java/it/inaf/oats/vospace/BaseNodeController.java +19 −8 Original line number Diff line number Diff line Loading @@ -48,13 +48,24 @@ public abstract class BaseNodeController { } protected void validateInternalLinkNode(LinkNode linkNode) { protected void validateLinkNode(LinkNode linkNode) { String target = linkNode.getTarget(); // I validate it here to add context easily if (target == null) { throw new InvalidArgumentException("LinkNode in payload has no target element specified"); } if (URIUtils.isURIInternal(target)) { URIUtils.returnVosPathFromNodeURI(linkNode.getTarget(), authority); } else { // TODO: Let's discuss if we need to combine this validation with // protocol endpoints management (URIService, ProtocolType) // Let's start with http and https only for now if (!(target.toLowerCase().startsWith("http://") || target.toLowerCase().startsWith("https://"))) { throw new InvalidArgumentException("LinkNode target malformed or unsupported protocol: " + target); } } } } src/main/java/it/inaf/oats/vospace/CreateNodeController.java +1 −2 Original line number Diff line number Diff line Loading @@ -6,7 +6,6 @@ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.exception.InvalidURIException; import net.ivoa.xml.vospace.v2.LinkNode; import net.ivoa.xml.vospace.v2.Node; import org.springframework.http.MediaType; Loading Loading @@ -45,7 +44,7 @@ public class CreateNodeController extends BaseNodeController { private void validateInputNode(Node node) { if (node instanceof LinkNode) { this.validateInternalLinkNode((LinkNode) node); this.validateLinkNode((LinkNode) node); } } Loading src/main/java/it/inaf/oats/vospace/FileServiceClient.java +57 −14 Original line number Diff line number Diff line Loading @@ -7,12 +7,19 @@ package it.inaf.oats.vospace; import com.fasterxml.jackson.databind.ObjectMapper; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.datamodel.Views; import it.inaf.oats.vospace.exception.InvalidArgumentException; import it.inaf.oats.vospace.exception.NodeNotFoundException; import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor; import it.inaf.oats.vospace.persistence.NodeDAO; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; Loading @@ -21,6 +28,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import static org.springframework.web.servlet.mvc.method.RequestMappingInfo.paths; @Component public class FileServiceClient { Loading @@ -39,6 +47,12 @@ public class FileServiceClient { @Autowired private HttpServletRequest request; @Autowired private LinkService linkService; @Autowired private NodeDAO nodeDAO; public String startArchiveJob(Transfer transfer, String jobId) { String target = transfer.getTarget().substring("vos://".length() + authority.length()); Loading @@ -64,9 +78,38 @@ public class FileServiceClient { vosPaths.add(target); } // Generate descriptors // Expand container nodes into direct children list List<String> expandedVosPaths = new ArrayList<>(); for (String vosPath : vosPaths) { Node node = nodeDAO.listNode(vosPath) .orElseThrow(() -> { throw new NodeNotFoundException(vosPath); }); if (node instanceof ContainerNode) { List<Node> nodes = ((ContainerNode) node).getNodes(); if (nodes.isEmpty()) { expandedVosPaths.add(NodeUtils.getVosPath(node)); } else { expandedVosPaths.addAll(nodes .stream().map(n -> NodeUtils.getVosPath(n)) .collect(Collectors.toList())); } } else { expandedVosPaths.add(vosPath); } } // follow links to links in vosPaths List<ArchiveEntryDescriptor> entryDescriptors = linkService.followLinksForArchiveService(expandedVosPaths); ArchiveRequest archiveRequest = new ArchiveRequest(); archiveRequest.setJobId(jobId); archiveRequest.setPaths(vosPaths); archiveRequest.setEntryDescriptors(entryDescriptors); archiveRequest.setType(archiveTypeFromViewUri(transfer.getView().getUri())); String url = fileServiceUrl + "/archive"; Loading Loading @@ -150,7 +193,7 @@ public class FileServiceClient { private String type; private String jobId; private List<String> paths; private List<ArchiveEntryDescriptor> entryDescriptors; public String getType() { return type; Loading @@ -168,12 +211,12 @@ public class FileServiceClient { this.jobId = jobId; } public List<String> getPaths() { return paths; public List<ArchiveEntryDescriptor> getEntryDescriptors() { return entryDescriptors; } public void setPaths(List<String> paths) { this.paths = paths; public void setEntryDescriptors(List<ArchiveEntryDescriptor> entryDescriptors) { this.entryDescriptors = entryDescriptors; } } Loading src/main/java/it/inaf/oats/vospace/LinkService.java 0 → 100644 +105 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import net.ivoa.xml.vospace.v2.LinkNode; import net.ivoa.xml.vospace.v2.Node; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service public class LinkService { @Value("${vospace-authority}") private String authority; @Value("${link-max-depth}") private int linkMaxDepth; @Autowired private NodeDAO nodeDao; // Returns a new list = the list in argument with paths of links to links are substituted with // their actual destination vos path. Only links to external resources // (http:// ... ) should remain public List<ArchiveEntryDescriptor> followLinksForArchiveService(List<String> vosPaths) { List<LinkNode> linkNodesInPaths = nodeDao.returnLinkNodesInList(vosPaths); // No links no change if(linkNodesInPaths.isEmpty()) return vosPaths.stream().map(p -> new ArchiveEntryDescriptor(p)) .collect(Collectors.toList()); List<String> linkVosPaths = linkNodesInPaths.stream() .map(ln -> NodeUtils.getVosPath(ln)).collect(Collectors.toList()); // Safe copy of argument List<String> resultVosPaths = new ArrayList<>(vosPaths); resultVosPaths.removeAll(linkVosPaths); // Generate descriptors from non link vospaths List<ArchiveEntryDescriptor> resultDescriptors = resultVosPaths.stream().map(p -> new ArchiveEntryDescriptor(p)) .collect(Collectors.toList()); // Add descriptors from links resultDescriptors.addAll( linkNodesInPaths.stream().map(p -> getLinkNodeArchiveEntryDescriptor(p)) .collect(Collectors.toList()) ); return resultDescriptors; } private ArchiveEntryDescriptor getLinkNodeArchiveEntryDescriptor(LinkNode node){ String vosPath = NodeUtils.getVosPath(node); String targetNodeVosPath = NodeUtils.getVosPath(this.followLink(node)); return new ArchiveEntryDescriptor(vosPath, targetNodeVosPath); } public Node followLink(LinkNode linkNode) { return this.followLinkRecursive(linkNode, 0); } private Node followLinkRecursive(LinkNode linkNode, int depth) { if (depth >= linkMaxDepth) { throw new InternalFaultException("Max link depth reached at link node: " + NodeUtils.getVosPath(linkNode)); } String linkTarget = linkNode.getTarget(); if (URIUtils.isURIInternal(linkTarget)) { String targetPath = URIUtils.returnVosPathFromNodeURI(linkTarget, authority); Optional<Node> targetNodeOpt = nodeDao.listNode(targetPath); Node targetNode = targetNodeOpt.orElseThrow(() -> new InternalFaultException("Broken Link to target: " + targetPath)); if (targetNode instanceof LinkNode) { return this.followLinkRecursive(linkNode, ++depth); } else { return targetNode; } } else { return linkNode; } } } src/main/java/it/inaf/oats/vospace/SetNodeController.java +1 −1 Original line number Diff line number Diff line Loading @@ -73,7 +73,7 @@ public class SetNodeController extends BaseNodeController { if (node instanceof DataNode) { checkViews((DataNode) node, (DataNode) toBeModifiedNode); } else if(node instanceof LinkNode) { this.validateInternalLinkNode((LinkNode) node); this.validateLinkNode((LinkNode) node); } //The service SHOULD throw a HTTP 500 status code including an InternalFault fault Loading Loading
src/main/java/it/inaf/oats/vospace/BaseNodeController.java +19 −8 Original line number Diff line number Diff line Loading @@ -48,13 +48,24 @@ public abstract class BaseNodeController { } protected void validateInternalLinkNode(LinkNode linkNode) { protected void validateLinkNode(LinkNode linkNode) { String target = linkNode.getTarget(); // I validate it here to add context easily if (target == null) { throw new InvalidArgumentException("LinkNode in payload has no target element specified"); } if (URIUtils.isURIInternal(target)) { URIUtils.returnVosPathFromNodeURI(linkNode.getTarget(), authority); } else { // TODO: Let's discuss if we need to combine this validation with // protocol endpoints management (URIService, ProtocolType) // Let's start with http and https only for now if (!(target.toLowerCase().startsWith("http://") || target.toLowerCase().startsWith("https://"))) { throw new InvalidArgumentException("LinkNode target malformed or unsupported protocol: " + target); } } } }
src/main/java/it/inaf/oats/vospace/CreateNodeController.java +1 −2 Original line number Diff line number Diff line Loading @@ -6,7 +6,6 @@ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.exception.InvalidURIException; import net.ivoa.xml.vospace.v2.LinkNode; import net.ivoa.xml.vospace.v2.Node; import org.springframework.http.MediaType; Loading Loading @@ -45,7 +44,7 @@ public class CreateNodeController extends BaseNodeController { private void validateInputNode(Node node) { if (node instanceof LinkNode) { this.validateInternalLinkNode((LinkNode) node); this.validateLinkNode((LinkNode) node); } } Loading
src/main/java/it/inaf/oats/vospace/FileServiceClient.java +57 −14 Original line number Diff line number Diff line Loading @@ -7,12 +7,19 @@ package it.inaf.oats.vospace; import com.fasterxml.jackson.databind.ObjectMapper; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.datamodel.Views; import it.inaf.oats.vospace.exception.InvalidArgumentException; import it.inaf.oats.vospace.exception.NodeNotFoundException; import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor; import it.inaf.oats.vospace.persistence.NodeDAO; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; Loading @@ -21,6 +28,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import static org.springframework.web.servlet.mvc.method.RequestMappingInfo.paths; @Component public class FileServiceClient { Loading @@ -39,6 +47,12 @@ public class FileServiceClient { @Autowired private HttpServletRequest request; @Autowired private LinkService linkService; @Autowired private NodeDAO nodeDAO; public String startArchiveJob(Transfer transfer, String jobId) { String target = transfer.getTarget().substring("vos://".length() + authority.length()); Loading @@ -64,9 +78,38 @@ public class FileServiceClient { vosPaths.add(target); } // Generate descriptors // Expand container nodes into direct children list List<String> expandedVosPaths = new ArrayList<>(); for (String vosPath : vosPaths) { Node node = nodeDAO.listNode(vosPath) .orElseThrow(() -> { throw new NodeNotFoundException(vosPath); }); if (node instanceof ContainerNode) { List<Node> nodes = ((ContainerNode) node).getNodes(); if (nodes.isEmpty()) { expandedVosPaths.add(NodeUtils.getVosPath(node)); } else { expandedVosPaths.addAll(nodes .stream().map(n -> NodeUtils.getVosPath(n)) .collect(Collectors.toList())); } } else { expandedVosPaths.add(vosPath); } } // follow links to links in vosPaths List<ArchiveEntryDescriptor> entryDescriptors = linkService.followLinksForArchiveService(expandedVosPaths); ArchiveRequest archiveRequest = new ArchiveRequest(); archiveRequest.setJobId(jobId); archiveRequest.setPaths(vosPaths); archiveRequest.setEntryDescriptors(entryDescriptors); archiveRequest.setType(archiveTypeFromViewUri(transfer.getView().getUri())); String url = fileServiceUrl + "/archive"; Loading Loading @@ -150,7 +193,7 @@ public class FileServiceClient { private String type; private String jobId; private List<String> paths; private List<ArchiveEntryDescriptor> entryDescriptors; public String getType() { return type; Loading @@ -168,12 +211,12 @@ public class FileServiceClient { this.jobId = jobId; } public List<String> getPaths() { return paths; public List<ArchiveEntryDescriptor> getEntryDescriptors() { return entryDescriptors; } public void setPaths(List<String> paths) { this.paths = paths; public void setEntryDescriptors(List<ArchiveEntryDescriptor> entryDescriptors) { this.entryDescriptors = entryDescriptors; } } Loading
src/main/java/it/inaf/oats/vospace/LinkService.java 0 → 100644 +105 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import net.ivoa.xml.vospace.v2.LinkNode; import net.ivoa.xml.vospace.v2.Node; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service public class LinkService { @Value("${vospace-authority}") private String authority; @Value("${link-max-depth}") private int linkMaxDepth; @Autowired private NodeDAO nodeDao; // Returns a new list = the list in argument with paths of links to links are substituted with // their actual destination vos path. Only links to external resources // (http:// ... ) should remain public List<ArchiveEntryDescriptor> followLinksForArchiveService(List<String> vosPaths) { List<LinkNode> linkNodesInPaths = nodeDao.returnLinkNodesInList(vosPaths); // No links no change if(linkNodesInPaths.isEmpty()) return vosPaths.stream().map(p -> new ArchiveEntryDescriptor(p)) .collect(Collectors.toList()); List<String> linkVosPaths = linkNodesInPaths.stream() .map(ln -> NodeUtils.getVosPath(ln)).collect(Collectors.toList()); // Safe copy of argument List<String> resultVosPaths = new ArrayList<>(vosPaths); resultVosPaths.removeAll(linkVosPaths); // Generate descriptors from non link vospaths List<ArchiveEntryDescriptor> resultDescriptors = resultVosPaths.stream().map(p -> new ArchiveEntryDescriptor(p)) .collect(Collectors.toList()); // Add descriptors from links resultDescriptors.addAll( linkNodesInPaths.stream().map(p -> getLinkNodeArchiveEntryDescriptor(p)) .collect(Collectors.toList()) ); return resultDescriptors; } private ArchiveEntryDescriptor getLinkNodeArchiveEntryDescriptor(LinkNode node){ String vosPath = NodeUtils.getVosPath(node); String targetNodeVosPath = NodeUtils.getVosPath(this.followLink(node)); return new ArchiveEntryDescriptor(vosPath, targetNodeVosPath); } public Node followLink(LinkNode linkNode) { return this.followLinkRecursive(linkNode, 0); } private Node followLinkRecursive(LinkNode linkNode, int depth) { if (depth >= linkMaxDepth) { throw new InternalFaultException("Max link depth reached at link node: " + NodeUtils.getVosPath(linkNode)); } String linkTarget = linkNode.getTarget(); if (URIUtils.isURIInternal(linkTarget)) { String targetPath = URIUtils.returnVosPathFromNodeURI(linkTarget, authority); Optional<Node> targetNodeOpt = nodeDao.listNode(targetPath); Node targetNode = targetNodeOpt.orElseThrow(() -> new InternalFaultException("Broken Link to target: " + targetPath)); if (targetNode instanceof LinkNode) { return this.followLinkRecursive(linkNode, ++depth); } else { return targetNode; } } else { return linkNode; } } }
src/main/java/it/inaf/oats/vospace/SetNodeController.java +1 −1 Original line number Diff line number Diff line Loading @@ -73,7 +73,7 @@ public class SetNodeController extends BaseNodeController { if (node instanceof DataNode) { checkViews((DataNode) node, (DataNode) toBeModifiedNode); } else if(node instanceof LinkNode) { this.validateInternalLinkNode((LinkNode) node); this.validateLinkNode((LinkNode) node); } //The service SHOULD throw a HTTP 500 status code including an InternalFault fault Loading