Commit 7456a69e authored by Sara Bertocco's avatar Sara Bertocco
Browse files

Aligning with master

parents 7eeb9e8f 6456e46e
Loading
Loading
Loading
Loading
+28 −13
Original line number Diff line number Diff line
package it.inaf.oats.vospace;

import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.persistence.JobDAO;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -43,35 +45,48 @@ public class JobService {

    private void startJob(JobSummary job) {

        switch (getJobType(job)) {
        Transfer transfer = uriService.getTransfer(job);

        switch (getJobType(transfer)) {
            case pullToVoSpace:
                handlePullToVoSpace(job);
                handlePullToVoSpace(job, transfer);
                break;
            case pullFromVoSpace:
            case pushToVoSpace:
                handleVoSpaceUrlsListResult(job);
                handleVoSpaceUrlsListResult(job, transfer);
                break;
            default:
                throw new UnsupportedOperationException("Not implemented yet");
        }
    }

    private void handlePullToVoSpace(JobSummary job) {
        // TODO: check protocol
    private void handlePullToVoSpace(JobSummary job, Transfer transfer) {

        for (Protocol protocol : transfer.getProtocols()) {
            switch (protocol.getUri()) {
                case "ia2:async-recall":
                    asyncTransfService.startJob(job);
                    return;
                case "ivo://ivoa.net/vospace/core#httpget":
                    String nodeUri = transfer.getTarget();
                    String contentUri = protocol.getEndpoint();
                    uriService.setNodeRemoteLocation(nodeUri, contentUri);
                    uriService.setTransferJobResult(job, transfer);
                    jobDAO.updateJob(job);
                    return;
                default:
                    throw new InternalFaultException("Unsupported pullToVoSpace protocol: " + protocol.getUri());
            }
        }
    }

    private void handleVoSpaceUrlsListResult(JobSummary job) {
    private void handleVoSpaceUrlsListResult(JobSummary job, Transfer transfer) {
        job.setPhase(ExecutionPhase.EXECUTING);
        uriService.setTransferJobResult(job);
        uriService.setTransferJobResult(job, transfer);
        jobDAO.updateJob(job);
    }

    private JobType getJobType(JobSummary job) {

        // TODO: check types
        Transfer transfer = (Transfer) job.getJobInfo().getAny().get(0);

    private JobType getJobType(Transfer transfer) {
        return JobType.valueOf(transfer.getDirection());
    }

+95 −0
Original line number Diff line number Diff line
package it.inaf.oats.vospace;


import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.LinkFoundException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.NodeDAO;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.Node;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SetNodeController extends BaseNodeController {

    @Autowired
    private NodeDAO nodeDao;

    @Value("${vospace-authority}")
    private String authority;

    @PostMapping(value = {"/nodes", "/nodes/**"},
            consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},
            produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public Node setNode(@RequestBody Node node, User principal, HttpServletRequest request) {

        String path = getPath();
        
        //The service SHALL throw a HTTP 404 status code including a NodeNotFound 
        //fault in the entity-body if the target Node does not exist
        Node toBeModifiedNode = nodeDao.listNode(path)
                        .orElseThrow(() -> new NodeNotFoundException(path));
        
                    
        // The service SHALL throw a HTTP 403 status code including a PermissionDenied fault 
        // in the entity-body if the user does not have permissions to perform the operation
        if(!NodeUtils.checkIfWritable(toBeModifiedNode, principal.getName(), principal.getGroups())) {
            throw new PermissionDeniedException(path);
        }
        
        // The service SHALL throw a HTTP 403 status code including a PermissionDenied fault 
        // in the entity-body if the request attempts to modify a read-only Property.
        // This method cannot be used to modify the Node type.
        // This method cannot be used to modify the accepts or provides list of Views for the Node.
        // This method cannot be used to create children of a container Node.
        
        
        
        // If a parent node in the URI path does not exist then the service SHALL throw a 
        // HTTP 404 status code including a ContainerNotFound fault in the entity-body
        // For example, given the URI path /a/b/c, the service must throw a HTTP 404 status 
        // code including a ContainerNotFound fault in the entity-body if either /a or /a/b 
        // do not exist.
        List<String> pathComponents = NodeUtils.subPathComponents(path);
        if (pathComponents.size() == 0) { 
            
            // Manage root node
            throw new PermissionDeniedException("root");
            
        } else {
            
            // Manage all precursors in full path
            for (int i = 0; i < pathComponents.size(); i++) { 
                String tmpPath = pathComponents.get(i);
                Node mynode = nodeDao.listNode(tmpPath)
                        .orElseThrow(() -> new NodeNotFoundException(tmpPath));
                if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size()-1) // a LinkNode leaf can be deleted
                    throw new LinkFoundException(tmpPath);
                            
            }
                    
        }
        
        
        
        
        //The service SHOULD throw a HTTP 500 status code including an InternalFault fault 
        // in the entity-body if the operation fails
       
        // to be fixed
        // A HTTP 200 status code and a Node representation in the entity-body The full 
        // expanded record for the node SHALL be returned, including any xsi:type 
        // specific extensions.
        return node;
        
    }    
    
}
+68 −14
Original line number Diff line number Diff line
@@ -5,11 +5,18 @@ import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.rap.client.call.TokenExchangeRequest;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.LocationDAO;
import it.inaf.oats.vospace.persistence.NodeDAO;
import it.inaf.oats.vospace.persistence.model.Location;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.ResultReference;
import net.ivoa.xml.vospace.v2.Node;
@@ -31,21 +38,25 @@ public class UriService {
    @Autowired
    private NodeDAO nodeDao;
    
    @Autowired
    private LocationDAO locationDAO;

    @Autowired
    private HttpServletRequest servletRequest;

    @Autowired
    private ServletRapClient rapClient;

    public void setTransferJobResult(JobSummary job) {
    public void setTransferJobResult(JobSummary job, Transfer transfer) {

        List<ResultReference> results = new ArrayList<>();

        ResultReference result = new ResultReference();
        result.setHref(getEndpoint(job));
        result.setHref(getEndpoint(job, transfer));
        results.add(result);

        job.setResults(results);
        job.setPhase(ExecutionPhase.COMPLETED);
    }

    public void setSyncTransferEndpoints(JobSummary job) {
@@ -58,22 +69,35 @@ public class UriService {
                && !"ivo://ivoa.net/vospace/core#httpput".equals(protocol.getUri())) {
            throw new IllegalStateException("Unsupported protocol " + protocol.getUri());
        }
        protocol.setEndpoint(getEndpoint(job));
        protocol.setEndpoint(getEndpoint(job, transfer));
    }

    private String getEndpoint(JobSummary job) {

        Transfer transfer = getTransfer(job);
    private String getEndpoint(JobSummary job, Transfer transfer) {

        String relativePath = transfer.getTarget().substring("vos://".length() + authority.length());

        Node node = nodeDao.listNode(relativePath).orElseThrow(() -> new NodeNotFoundException(relativePath));

        // TODO build the path according to node type
        //
        String endpoint = fileServiceUrl + urlEncodePath(relativePath) + "?jobId=" + job.getJobId();
        Location location = locationDAO.getNodeLocation(relativePath).orElseThrow(()
                -> new InternalFaultException("No registered location found for vos_path " + relativePath));

        if (!"true".equals(NodeProperties.getNodePropertiesListByURI(node, NodeProperties.PUBLIC_READ_URI))) {
        String endpoint;
        switch (location.getType()) {
            case PORTAL:
                String fileName = nodeDao.getNodeOsName(relativePath);
                endpoint = "http://" + location.getSource().getHostname() + location.getSource().getBasePath();
                if (!endpoint.endsWith("/")) {
                    endpoint += "/";
                }
                endpoint += fileName;
                break;
            default:
                endpoint = fileServiceUrl + urlEncodePath(relativePath);
        }

        endpoint += "?jobId=" + job.getJobId();

        if (!"true".equals(NodeProperties.getNodePropertyAsListByURI(node, NodeProperties.PUBLIC_READ_URI))) {
            endpoint += "&token=" + getEndpointToken(fileServiceUrl + relativePath);
        }

@@ -85,8 +109,7 @@ public class UriService {
        String token = ((User) servletRequest.getUserPrincipal()).getAccessToken();

        if (token == null) {
            // TODO: use PermissionDenied VoSpaceException
            throw new IllegalStateException("Token is null");
            throw new PermissionDeniedException("Token is null");
        }

        TokenExchangeRequest exchangeRequest = new TokenExchangeRequest()
@@ -97,8 +120,39 @@ public class UriService {
        return rapClient.exchangeToken(exchangeRequest, servletRequest);
    }

    private Transfer getTransfer(JobSummary job) {
        // TODO add checks on data type
    public void setNodeRemoteLocation(String nodeUri, String contentUri) {

        URL url;
        try {
            url = new URL(contentUri);
        } catch (MalformedURLException ex) {
            throw new InternalFaultException(ex);
        }

        Location location = locationDAO.findPortalLocation(url.getHost()).orElseThrow(()
                -> new InternalFaultException("No registered location found for host " + url.getHost()));

        String vosPath = nodeUri.replaceAll("vos://[^/]+", "");

        String fileName = url.getPath().substring(url.getPath().lastIndexOf("/") + 1);

        nodeDao.setNodeLocation(vosPath, location.getId(), fileName);
    }

    public Transfer getTransfer(JobSummary job) {

        List<Object> jobPayload = job.getJobInfo().getAny();
        if (jobPayload.isEmpty()) {
            throw new IllegalStateException("Empty job payload for job " + job.getJobId());
        }
        if (jobPayload.size() > 1) {
            throw new IllegalStateException("Multiple objects in job payload not supported");
        }
        if (!(jobPayload.get(0) instanceof Transfer)) {
            throw new IllegalStateException(jobPayload.get(0).getClass().getCanonicalName()
                    + " not supported as job payload. Job id: " + job.getJobId());
        }

        return (Transfer) job.getJobInfo().getAny().get(0);
    }
}
+14 −8
Original line number Diff line number Diff line
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package it.inaf.oats.vospace.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;


@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)   // Status code 500
public class InternalFaultException extends VoSpaceException {

    private static final Logger LOG = LoggerFactory.getLogger(InternalFaultException.class);

    public InternalFaultException(String msg) {
        super("InternalFaultException: " + msg);
    }

    public InternalFaultException(Throwable cause) {
        super("InternalFaultException: " + getMessage(cause));
    }

    private static String getMessage(Throwable cause) {
        LOG.error("Exception caught", cause);
        return cause.getMessage() != null ? cause.getMessage() : cause.getClass().getCanonicalName();
    }
}
+113 −0
Original line number Diff line number Diff line
package it.inaf.oats.vospace.persistence;

import it.inaf.oats.vospace.persistence.model.Location;
import it.inaf.oats.vospace.persistence.model.LocationType;
import it.inaf.oats.vospace.persistence.model.Storage;
import it.inaf.oats.vospace.persistence.model.StorageType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class LocationDAO {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public LocationDAO(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public Optional<Location> findPortalLocation(String host) {

        String sql = "SELECT location_id, location_type, storage_src_id, storage_dest_id,\n"
                + "storage_id, storage_type, base_path, hostname\n"
                + "FROM location l\n"
                + "JOIN storage s ON l.storage_src_id = s.storage_id OR l.storage_dest_id = s.storage_id\n"
                + "WHERE hostname = ?";

        return jdbcTemplate.query(sql, ps -> {
            ps.setString(1, host);
        }, rs -> {
            return getLocation(rs, () -> new IllegalStateException("Found multiple locations for the same hostname"));
        });
    }

    public Optional<Location> getNodeLocation(String vosPath) {

        String sql = "SELECT location_id, location_type, storage_src_id, storage_dest_id,\n"
                + "storage_id, storage_type, base_path, hostname\n"
                + "FROM location l\n"
                + "JOIN storage s ON l.storage_src_id = s.storage_id OR l.storage_dest_id = s.storage_id\n"
                + "WHERE location_id = (\n"
                + "SELECT location_id FROM node n\n"
                + "JOIN node_vos_path p ON n.node_id = p.node_id\n"
                + "WHERE p.vos_path = ?)";

        return jdbcTemplate.query(sql, ps -> {
            ps.setString(1, vosPath);
        }, rs -> {
            return getLocation(rs, () -> new IllegalStateException("Found multiple locations for the same vos_path"));
        });
    }

    private Optional<Location> getLocation(ResultSet rs, Supplier<IllegalStateException> exceptionSupplier) throws SQLException {
        List<Location> locations = getLocations(rs);
        if (locations.isEmpty()) {
            return Optional.empty();
        }
        if (locations.size() > 1) {
            throw exceptionSupplier.get();
        }
        return Optional.of(locations.get(0));
    }

    private List<Location> getLocations(ResultSet rs) throws SQLException {

        Map<Integer, Storage> storagesMap = new HashMap<>();
        Map<Integer, Location> locationsMap = new HashMap<>();

        while (rs.next()) {
            int locationId = rs.getInt("location_id");
            Location location = locationsMap.get(locationId);
            if (location == null) {
                location = new Location();
                location.setId(locationId);
                locationsMap.put(locationId, location);
            }
            location.setType(LocationType.parse(rs.getString("location_type")));

            int storageId = rs.getInt("storage_id");
            Storage storage = storagesMap.get(storageId);
            if (storage == null) {
                storage = new Storage();
                storage.setId(storageId);
                storagesMap.put(storageId, storage);
            }

            storage.setType(StorageType.parse(rs.getString("storage_type")));
            storage.setBasePath(rs.getString("base_path"));
            storage.setHostname(rs.getString("hostname"));

            Storage storageSrc = storagesMap.get(rs.getInt("storage_src_id"));
            if (storageSrc != null) {
                location.setSource(storageSrc);
            }
            Storage storageDest = storagesMap.get(rs.getInt("storage_dest_id"));
            if (storageDest != null) {
                location.setDestination(storageDest);
            }
        }

        return new ArrayList<>(locationsMap.values());
    }
}
Loading