diff --git a/src/main/java/it/inaf/oats/vospace/SetNodeController.java b/src/main/java/it/inaf/oats/vospace/SetNodeController.java index d5c62267ba2cc670d0b9f10fca59651990fe40ee..c9b4c7d5f4b11d0c086d949e9a56894082a48734 100644 --- a/src/main/java/it/inaf/oats/vospace/SetNodeController.java +++ b/src/main/java/it/inaf/oats/vospace/SetNodeController.java @@ -10,15 +10,20 @@ 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.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; 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 { + + private static final Logger LOG = LoggerFactory.getLogger(SetNodeController.class); @Autowired private NodeDAO nodeDao; @@ -29,9 +34,10 @@ public class SetNodeController extends BaseNodeController { @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) { + public ResponseEntity<Node> setNode(@RequestBody Node node, User principal, HttpServletRequest request) { String path = getPath(); + LOG.debug("setNode called for path {}", path); //The service SHALL throw a HTTP 404 status code including a NodeNotFound //fault in the entity-body if the target Node does not exist @@ -48,10 +54,15 @@ public class SetNodeController extends BaseNodeController { // 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. + String storedNodeType = toBeModifiedNode.getType(); + String newNodeType = node.getType(); + if(!storedNodeType.equals(newNodeType)) { + LOG.debug("setNode trying to modify type. Stored ", storedNodeType + ", requested " + newNodeType); + throw new PermissionDeniedException(path); + } // 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. - - + // For this case, throws exception in NodeDAO + // This method cannot be used to create children of a container Node. (Non capisco, Sara) // 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 @@ -76,19 +87,18 @@ public class SetNodeController extends BaseNodeController { } - } - - + } //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 node; + return ResponseEntity.ok(nodeDao.setNode(node)); } diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java index 5ab50a8927b4d554444a013f210de69826170d74..d8d05985732b2b2ed86997c19bee597280ff1b82 100644 --- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java @@ -1,9 +1,12 @@ package it.inaf.oats.vospace.persistence; +import it.inaf.oats.vospace.DeleteNodeController; import it.inaf.oats.vospace.datamodel.NodeProperties; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.InternalFaultException; +import it.inaf.oats.vospace.exception.NodeNotFoundException; +import it.inaf.oats.vospace.exception.PermissionDeniedException; import java.sql.Array; import net.ivoa.xml.vospace.v2.Node; import java.sql.PreparedStatement; @@ -22,6 +25,8 @@ import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Property; import net.ivoa.xml.vospace.v2.StructuredDataNode; import net.ivoa.xml.vospace.v2.View; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; @@ -30,6 +35,8 @@ import org.springframework.stereotype.Repository; @Repository public class NodeDAO { + private static final Logger LOG = LoggerFactory.getLogger(DeleteNodeController.class); + @Value("${vospace-authority}") private String authority; @@ -46,17 +53,7 @@ public class NodeDAO { String path = nodeURI.replaceAll("vos://[^/]+", ""); String parentPath = NodeUtils.getParentPath(path); - String sql = "SELECT path, relative_path from " - + "node n join node_vos_path p on n.node_id = p.node_id " - + "where p.vos_path = ?"; - - List<NodePaths> paths = jdbcTemplate.query(conn -> { - PreparedStatement ps = conn.prepareStatement(sql); - ps.setString(1, parentPath); - return ps; - }, (row, index) -> { - return getPathsFromResultSet(row); - }); + List<NodePaths> paths = getNodePathsFromDB(nodeURI); if (paths.isEmpty()) { throw new IllegalStateException("Unable to find parent node during node creation"); @@ -126,6 +123,101 @@ public class NodeDAO { return Optional.of(node); } + + public Node setNode(Node newNode) { + + // Verify that the node is in the database + String nodeURI = newNode.getUri(); + List<NodePaths> paths = getNodePathsFromDB(nodeURI); + + if (paths.isEmpty()) { + throw new IllegalStateException("Unable to find node during node update"); + } + if (paths.size() > 1) { + throw new IllegalStateException("Multiple ltree parent paths during node update"); + } + + // 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 + // Check if trying to change views getting Views (if present) of the modified node + if (newNode instanceof DataNode) { + DataNode dataNode = (DataNode)newNode; + List<View> requestedAcceptedViews = dataNode.getAccepts(); + List<View> savedAcceptedViews = getAcceptedViewsFromDB(paths.get(0).getPath()); + // Get the Views of the saved node + List<View> requestedProvidedViews = dataNode.getProvides(); + List<View> savedProvidedViews = getProvidedViewsFromDB(paths.get(0).getPath()); + + if (!requestedAcceptedViews.isEmpty()) { + //se sono non nulle, devo fare i controlli, altrimenti di sicuro l'utente non sta chiedendo di cambiarle + // Check if requestedAcceptedViews are different from the already stored + if (!checkIfViewsAreEquals(requestedAcceptedViews,savedAcceptedViews)) { + LOG.debug("setNode trying to modify accepted Views."); + throw new PermissionDeniedException("Trying to modify accepted views"); + } + } + + if (!requestedProvidedViews.isEmpty()) { + //se sono non nulle, devo fare i controlli, altrimenti di sicuro l'utente non sta chiedendo di cambiarle + // Check if requestedAcceptedViews are different from the already stored + if (!checkIfViewsAreEquals(requestedProvidedViews,savedProvidedViews)) { + LOG.debug("setNode trying to modify provided Views."); + throw new PermissionDeniedException("Trying to modify provided views"); + } + } + } + + StringBuilder sb = new StringBuilder(); + + sb.append("UPDATE node"); + sb.append(" SET owner_id = ?, group_read = ?, group_write = ?, is_public = ? " ); + sb.append(" FROM node_vos_path p WHERE p.vos_path = ? AND p.node_id = node.node_id "); + + jdbcTemplate.update(conn -> { + PreparedStatement ps = conn.prepareStatement(sb.toString()); + int i = 0; + ps.setString(++i, NodeProperties.getPropertiesStringByURI(newNode, NodeProperties.getPropertyURI("creator"))); + ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getPropertiesStringByURI(newNode, NodeProperties.getPropertyURI("groupread")))); + ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getPropertiesStringByURI(newNode, NodeProperties.getPropertyURI("groupwrite")))); + ps.setBoolean(++i, Boolean.valueOf(NodeProperties.getPropertiesStringByURI(newNode, NodeProperties.getPropertyURI("publicread")))); + ps.setString(++i, paths.get(0).getPath()); + return ps; + }); + + return newNode; + + + } + + + private Optional<Node> getNode(String path) { + + String sql = "SELECT node_id, type, async_trans, sticky, busy_state, creator_id, group_read, group_write,\n" + + "is_public, content_length, created_on, last_modified, accept_views, provide_views \n" + + "FROM node WHERE path = ?"; + + List<Node> nodes = jdbcTemplate.query(conn -> { + PreparedStatement ps = conn.prepareStatement(sql); + ps.setString(1, path); + return ps; + }, (row, index) -> { + return getNodeFromResultSet(row); + }); + + if (nodes.isEmpty()) { + return Optional.empty(); + } + + // Query returns the node (can be container or data) + Node node = nodes.get(0); + + return Optional.of(node); + + } + + + + private String getFirstLevelChildrenSelector(String path) { String select = "(SELECT path FROM node WHERE node_id = (SELECT node_id FROM node_vos_path WHERE vos_path = ?))::varchar || '"; @@ -322,17 +414,112 @@ public class NodeDAO { return null; } - private List<View> getViews(Array array) throws SQLException { + private List<View> getViews(Array array) { if (array == null) { return null; } - return Arrays.stream((String[]) array.getArray()) + try { + return Arrays.stream((String[]) array.getArray()) .map(uri -> { View view = new View(); view.setUri(uri); return view; }) .collect(Collectors.toList()); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + + } + + + 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; + + } + + + private List<View> getAcceptedViewsFromDB(String nodePath) { + + return getViewsFromDB(nodePath, "accept_views"); + + } + + + private List<View> getProvidedViewsFromDB(String nodePath) { + + return getViewsFromDB(nodePath, "provide_views"); + + } + + + private List<View> getViewsFromDB(String nodePath, String viewType) { + + String sql = "SELECT accept_views FROM node WHERE node_id = " + + "(SELECT node_id FROM node_vos_path WHERE vos_path = ?)"; + + Array results = jdbcTemplate.query(sql, ps -> { + ps.setString(1, nodePath); + }, (rs) -> { + if(rs.next()) + return rs.getArray("accept_views"); + else + return null; + }); + + return getViews(results); + + } + + + private List<NodePaths> getNodePathsFromDB(String nodeURI) { + + //String nodeURI = myNode.getUri(); + String path = nodeURI.replaceAll("vos://[^/]+", ""); + String parentPath = NodeUtils.getParentPath(path); + + String sql = "SELECT path, relative_path from " + + "node n join node_vos_path p on n.node_id = p.node_id " + + "where p.vos_path = ?"; + + List<NodePaths> paths = jdbcTemplate.query(conn -> { + PreparedStatement ps = conn.prepareStatement(sql); + ps.setString(1, parentPath); + return ps; + }, (row, index) -> { + return getPathsFromResultSet(row); + }); + + return paths; } private class NodePaths { @@ -350,5 +537,14 @@ public class NodeDAO { public String toString() { return relativePath + " " + path; } + + public String getPath() { + return this.path; + } + + public String getRelativePath() { + return this.relativePath; + } + } }