package it.inaf.oats.vospace.persistence; import java.sql.Array; import net.ivoa.xml.vospace.v2.Node; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.List; import java.util.Optional; import javax.sql.DataSource; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Property; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /** * * @author bertocco */ @Repository public class NodeDAO { @Value("${vospace-authority}") private String authority; private final JdbcTemplate jdbcTemplate; @Autowired public NodeDAO(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } public void createNode(Node myNode) { // check if parent path exist, else throw HTTP 404 , secondo specifica vospace /* Retrieve the path (ltree) and the relative_path (ltree) of the parent node: select path, relative_path from node n join node_vos_path p on n.node_id = p.node_id where p.vos_path = '/path/to/parent’ Then perform the insert using the path and relative_path to fill the parent_path and parent_relative_path columns of the new node. */ String nodeURI = myNode.getUri(); String path = nodeURI.replaceAll("vos://[^/]+", ""); String parentPath = 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 paths = jdbcTemplate.query(conn -> { PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, parentPath); return ps; }, (row, index) -> { return getPathsFromResultSet(row); }); if(paths.isEmpty()) { throw new IllegalStateException("Unable to find parent node during node creation"); } StringBuilder sb = new StringBuilder(); sb.append("INSERT INTO node"); sb.append(" (name, busy_state, owner_id, creator_id, group_read, group_write,"); sb.append(" is_public, parent_path, type)"); sb.append(" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ? )"); jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement(sb.toString()); ps.setString(1, getNodeName(myNode)); ps.setBoolean(2, getIsBusy(myNode)); ps.setString(3, getProperty(myNode, getPropertyURI("creator"))); ps.setString(4, getProperty(myNode, getPropertyURI("creator"))); ps.setArray(5, fromPropertyToArray(ps, getProperty(myNode, getPropertyURI("groupread")))); ps.setArray(6, fromPropertyToArray(ps, getProperty(myNode, getPropertyURI("groupwrite")))); ps.setBoolean(7, Boolean.valueOf(getProperty(myNode, getPropertyURI("publicread")))); ps.setObject(8, paths.get(0).parentPath, Types.OTHER); ps.setObject(9, getDbNodeType(myNode), Types.OTHER); return ps; }); } public Optional listNode(String path) { String sql = "SELECT os.vos_path, n.node_id, type, async_trans, busy_state, owner_id, group_read, group_write, is_public, content_length, created_on, last_modified from node n\n" + "JOIN node_vos_path os ON n.node_id = os.node_id\n" + "WHERE n.path ~ (" + getFirstLevelChildrenSelector(path) + ")::lquery\n" + "OR os.vos_path = ? ORDER BY vos_path"; List parentAndChildren = jdbcTemplate.query(conn -> { PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, path); ps.setString(2, path); return ps; }, (row, index) -> { return getNodeFromResultSet(row); }); if (parentAndChildren.isEmpty()) { return Optional.empty(); } // Query returns parent as first node Node node = parentAndChildren.get(0); // Fill children if (node instanceof ContainerNode && parentAndChildren.size() > 1) { ContainerNode parent = (ContainerNode) node; for (int i = 1; i < parentAndChildren.size(); i++) { parent.getNodes().add(parentAndChildren.get(i)); } } 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 || '"; if (!"/".equals(path)) { select += "."; } select += "*{1}'"; return select; } private Node getNodeFromResultSet(ResultSet rs) throws SQLException { Node node = getTypedNode(rs.getString("type")); if (node instanceof DataNode) { ((DataNode) node).setBusy(rs.getBoolean("busy_state")); } node.setUri(getUri(rs.getString("vos_path"))); List properties = new ArrayList<>(); addProperty(getPropertyURI("length"), rs.getString("content_length"), properties); addProperty(getPropertyURI("btime"), rs.getString("created_on"), properties); addProperty(getPropertyURI("mtime"), rs.getString("last_modified"), properties); addProperty(getPropertyURI("groupread"), getGroupsString(rs, "group_read"), properties); addProperty(getPropertyURI("groupwrite"), getGroupsString(rs, "group_write"), properties); addProperty(getPropertyURI("publicread"), rs.getString("is_public"), properties); addProperty("urn:async_trans", rs.getString("async_trans"), properties); node.setProperties(properties); return node; } private String getPropertyURI(String propertyName) { return "ivo://ivoa.net/vospace/core#".concat(propertyName); } private String getGroupsString(ResultSet rs, String column) throws SQLException { Array array = rs.getArray(column); if (array == null) { return null; } return String.join(" ", (String[]) array.getArray()); } // If value is null does nothing private void addProperty(String uri, String value, List list) { if (value != null) { Property prop = new Property(); prop.setUri(uri); prop.setValue(value); list.add(prop); } } private Node getTypedNode(String type) { Node node; switch (type) { case "container": node = new ContainerNode(); break; case "data": node = new DataNode(); break; default: throw new UnsupportedOperationException("Node type " + type + " not supported yet"); } return node; } private String getUri(String path) { return "vos://" + authority + path; } private NodePaths getPathsFromResultSet(ResultSet rs) throws SQLException { NodePaths paths = new NodePaths(rs.getString("path"), rs.getString("relative_path")); return paths; } private String getNodeName(String path) { String[] parsedPath = path.split("/"); return parsedPath[parsedPath.length -1]; } private String getNodeName(Node myNode) { String uri = myNode.getUri(); return getNodeName(uri); } private boolean getIsBusy(Node myNode) { if (myNode instanceof DataNode) { DataNode dataNode = (DataNode)myNode; return dataNode.isBusy(); } return false; } private String getParentPath(String path) { String[] parsedPath = path.split("/"); StringBuilder sb = new StringBuilder(); for (int i = 0; i < parsedPath.length - 1; i++) { sb.append("/").append(parsedPath[i]); } return sb.toString(); } private String getProperty(Node node, String uri) { for (Property property : node.getProperties()) { if (uri.equals(property.getUri())) { return property.getValue(); } } return null; } private Array fromPropertyToArray(PreparedStatement ps, String myProperty) throws SQLException { if (myProperty == null || myProperty.isBlank()) { return null; } else { return ps.getConnection().createArrayOf("varchar", myProperty.split(" ")); } } private String getDbNodeType(Node node) { if(node instanceof ContainerNode) { return "container"; } else if(node instanceof DataNode) { return "data"; } throw new UnsupportedOperationException("Unable to retrieve database node type for class " + node.getClass().getCanonicalName()); } private class NodePaths { private String path; private String parentPath; public NodePaths(String myPath, String myParentPath) { this.path = myPath; this.parentPath = myParentPath; } public String toString() { return parentPath + " " + path; } } }