Skip to content
SetNodeController.java 6.64 KiB
Newer Older
Sonia Zorba's avatar
Sonia Zorba committed
/*
 * This file is part of vospace-rest
 * Copyright (C) 2021 Istituto Nazionale di Astrofisica
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
Sara Bertocco's avatar
Sara Bertocco committed
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.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.LinkNode;
Sara Bertocco's avatar
Sara Bertocco committed
import net.ivoa.xml.vospace.v2.Node;
Sara Bertocco's avatar
Sara Bertocco committed
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.http.ResponseEntity;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SetNodeController extends BaseNodeController {
Sara Bertocco's avatar
Sara Bertocco committed
    private static final Logger LOG = LoggerFactory.getLogger(SetNodeController.class);
Sara Bertocco's avatar
Sara Bertocco committed

    @Autowired
    private NodeDAO nodeDao;

    @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 ResponseEntity<Node> setNode(@RequestBody Node node, User principal, HttpServletRequest request,
            @RequestParam(value = "recursive", required = false, defaultValue = "false") boolean recursive) {
Sara Bertocco's avatar
Sara Bertocco committed

        String path = getPath();
Sara Bertocco's avatar
Sara Bertocco committed
        LOG.debug("setNode called for path {}", path);
        
        // Validate and check payload node URI consistence with request 
        this.validateAndCheckPayloadURIConsistence(node);
Sara Bertocco's avatar
Sara Bertocco committed
        //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));

Sara Bertocco's avatar
Sara Bertocco committed
        // 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 PermissionDeniedException.forPath(path);
Sara Bertocco's avatar
Sara Bertocco committed
        }
Sara Bertocco's avatar
Sara Bertocco committed
        // 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.
Sara Bertocco's avatar
Sara Bertocco committed
        String storedNodeType = toBeModifiedNode.getType();
        String newNodeType = node.getType();
Sara Bertocco's avatar
Sara Bertocco committed
            LOG.debug("setNode trying to modify type. Stored ", storedNodeType + ", requested " + newNodeType);
            throw PermissionDeniedException.forPath(path);
        }

        // This method cannot be used to modify the accepts or provides list of Views for the Node.
        // Only DataNodes has Views (see VOSpace Data Model)
        // Also if input node is LinkNode, target must be validated as internal URI
        if (node instanceof DataNode) {
            checkViews((DataNode) node, (DataNode) toBeModifiedNode);
        } else if(node instanceof LinkNode) {
            this.validateInternalLinkNode((LinkNode) node);            
        }

        //The service SHOULD throw a HTTP 500 status code including an InternalFault fault 
        // in the entity-body if the operation fails
        // Done in NodeDAO
        // 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 ResponseEntity.ok(nodeDao.setNode(node, recursive));
    }

    private void checkViews(DataNode requestedDataNode, DataNode savedDataNode) {

        checkAcceptsView(requestedDataNode, savedDataNode);
        checkProvidesView(requestedDataNode, savedDataNode);
    }

    private void checkAcceptsView(DataNode requestedDataNode, DataNode savedDataNode) {
        List<View> requestedAcceptedViews = requestedDataNode.getAccepts();
        List<View> savedAcceptedViews = savedDataNode.getAccepts();

        // If views are present it is necessary to perform the check, otherwise
        // it means that the user is not going to change them
        if (!requestedAcceptedViews.isEmpty() && !checkIfViewsAreEquals(requestedAcceptedViews, savedAcceptedViews)) {
            LOG.debug("setNode trying to modify accepted Views.");
            throw new PermissionDeniedException("Trying to modify accepted views");
Sara Bertocco's avatar
Sara Bertocco committed
        }
    }

    private void checkProvidesView(DataNode requestedDataNode, DataNode savedDataNode) {

        List<View> requestedProvidedViews = requestedDataNode.getProvides();
        List<View> savedProvidedViews = savedDataNode.getProvides();

        // If views are present it is necessary to perform the check, otherwise
        // it means that the user is not going to change them
        if (!requestedProvidedViews.isEmpty() && !checkIfViewsAreEquals(requestedProvidedViews, savedProvidedViews)) {
            LOG.debug("setNode trying to modify provided Views.");
            throw new PermissionDeniedException("Trying to modify provided views");
        }
    }

    private boolean checkIfViewsAreEquals(List<View> viewsA, List<View> viewsB) {

        boolean viewsAreEqual = true;
        if ((viewsA == null || viewsA.isEmpty())
                && (viewsB == null || viewsB.isEmpty())) {
            return true;
        }

        if ((viewsA == null || viewsA.isEmpty())
                || (viewsB == null || viewsB.isEmpty())) {
            return false;
        }

        if (viewsA.size() != viewsB.size()) {
            return false;
        }

        for (int i = 0; i < viewsA.size(); i++) {
            String viewUriA = viewsA.get(i).getUri();
            boolean found = false;
            for (int j = 0; j < viewsB.size(); j++) {
                String viewUriB = viewsB.get(i).getUri();
                if (viewUriA.compareTo(viewUriB) == 0) {
                    found = true;
                }
            }

            if (found == false) {
                viewsAreEqual = false;
                break;
            }
        }

        return viewsAreEqual;
    }
Sara Bertocco's avatar
Sara Bertocco committed
}