Commit 5a4cd3f4 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Merge branch 'portal_links'

parents 6650552d 375822bb
Loading
Loading
Loading
Loading
+19 −8
Original line number Diff line number Diff line
@@ -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);
            }

        }
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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);
        }

    }
+57 −14
Original line number Diff line number Diff line
@@ -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;
@@ -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 {
@@ -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());
@@ -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";
@@ -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;
@@ -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;
        }
    }

+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;
        }
    }

}
+1 −1
Original line number Diff line number Diff line
@@ -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