Skip to content
NodeDAO.java 13.2 KiB
Newer Older
Sara Bertocco's avatar
Sara Bertocco committed
package it.inaf.oats.vospace.persistence;

Sara Bertocco's avatar
Sara Bertocco committed
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;
Sara Bertocco's avatar
Sara Bertocco committed
import net.ivoa.xml.vospace.v2.Node;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
Sara Bertocco's avatar
Sara Bertocco committed
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
Sara Bertocco's avatar
Sara Bertocco committed
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 net.ivoa.xml.vospace.v2.StructuredDataNode;
import net.ivoa.xml.vospace.v2.View;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class NodeDAO {

    @Value("${vospace-authority}")
    private String authority;
Sara Bertocco's avatar
Sara Bertocco committed

    private final JdbcTemplate jdbcTemplate;

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

Sonia Zorba's avatar
Sonia Zorba committed
    public void createNode(Node myNode) {

        String nodeURI = myNode.getUri();
        String path = nodeURI.replaceAll("vos://[^/]+", "");
Sara Bertocco's avatar
Sara Bertocco committed
        String parentPath = NodeUtils.getParentPath(path);
Sonia Zorba's avatar
Sonia Zorba committed

        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 = ?";
Sonia Zorba's avatar
Sonia Zorba committed

        List<NodePaths> paths = jdbcTemplate.query(conn -> {
Sonia Zorba's avatar
Sonia Zorba committed
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, parentPath);
            return ps;
            return getPathsFromResultSet(row);
        if (paths.isEmpty()) {
            throw new IllegalStateException("Unable to find parent node during node creation");
Sonia Zorba's avatar
Sonia Zorba committed
        }
        if (paths.size() > 1) {
Sonia Zorba's avatar
Sonia Zorba committed
            throw new IllegalStateException("Multiple ltree parent paths found for " + parentPath);
        }
Sonia Zorba's avatar
Sonia Zorba committed

Sara Bertocco's avatar
Sara Bertocco committed
        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, parent_relative_path, type, accept_views, provide_views)");
        sb.append(" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
Sonia Zorba's avatar
Sonia Zorba committed

        jdbcTemplate.update(conn -> {
            PreparedStatement ps = conn.prepareStatement(sb.toString());
Sara Bertocco's avatar
Sara Bertocco committed
            ps.setString(++i, NodeUtils.getNodeName(myNode));
            ps.setBoolean(++i, NodeUtils.getIsBusy(myNode));
            ps.setString(++i, NodeProperties.getPropertiesStringByURI(myNode, NodeProperties.getPropertyURI("creator")));
            ps.setString(++i, NodeProperties.getPropertiesStringByURI(myNode, NodeProperties.getPropertyURI("creator")));
            ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getPropertiesStringByURI(myNode, NodeProperties.getPropertyURI("groupread"))));
            ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getPropertiesStringByURI(myNode, NodeProperties.getPropertyURI("groupwrite"))));
            ps.setBoolean(++i, Boolean.valueOf(NodeProperties.getPropertiesStringByURI(myNode, NodeProperties.getPropertyURI("publicread"))));
            ps.setObject(++i, paths.get(0).path, Types.OTHER);
            ps.setObject(++i, paths.get(0).relativePath, Types.OTHER);
Sara Bertocco's avatar
Sara Bertocco committed
            ps.setObject(++i, NodeUtils.getDbNodeType(myNode), Types.OTHER);
            ps.setObject(++i, fromViewsToArray(ps, myNode, d -> d.getAccepts()), Types.OTHER);
            ps.setObject(++i, fromViewsToArray(ps, myNode, d -> d.getProvides()), Types.OTHER);
Sara Bertocco's avatar
Sara Bertocco committed
    }

    public Optional<Node> listNode(String path) {
Sara Bertocco's avatar
Sara Bertocco committed

Sonia Zorba's avatar
Sonia Zorba committed
        String sql = "SELECT os.vos_path, n.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 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";
Sara Bertocco's avatar
Sara Bertocco committed

        List<Node> parentAndChildren = jdbcTemplate.query(conn -> {
Sara Bertocco's avatar
Sara Bertocco committed
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, path);
            ps.setString(2, path);
Sara Bertocco's avatar
Sara Bertocco committed
            return ps;
        }, (row, index) -> {
            return getNodeFromResultSet(row);
Sara Bertocco's avatar
Sara Bertocco committed
        });

        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;
Sara Bertocco's avatar
Sara Bertocco committed
    }

    private String getAllLevelsChildrenSelector(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;
    }
Sara Bertocco's avatar
Sara Bertocco committed

    private Node getNodeFromResultSet(ResultSet rs) throws SQLException {

Sara Bertocco's avatar
Sara Bertocco committed
        Node node = NodeUtils.getTypedNode(rs.getString("type"));

            DataNode dataNode = (DataNode) node;

            dataNode.setBusy(rs.getBoolean("busy_state"));
            dataNode.setAccepts(getViews(rs.getArray("accept_views")));
            dataNode.setProvides(getViews(rs.getArray("provide_views")));
        node.setUri(getUri(rs.getString("vos_path")));
        List<Property> properties = new ArrayList<>();
Sara Bertocco's avatar
Sara Bertocco committed
        addProperty(NodeProperties.getPropertyURI("length"), rs.getString("content_length"),
Sara Bertocco's avatar
Sara Bertocco committed
        addProperty(NodeProperties.getPropertyURI("btime"), rs.getString("created_on"),
Sara Bertocco's avatar
Sara Bertocco committed
        addProperty(NodeProperties.getPropertyURI("creator"), rs.getString("creator_id"),
Sonia Zorba's avatar
Sonia Zorba committed
                properties);

Sara Bertocco's avatar
Sara Bertocco committed
        addProperty(NodeProperties.getPropertyURI("mtime"), rs.getString("last_modified"),
Sara Bertocco's avatar
Sara Bertocco committed
        addProperty(NodeProperties.getPropertyURI("groupread"), getGroupsString(rs, "group_read"),
Sara Bertocco's avatar
Sara Bertocco committed
        addProperty(NodeProperties.getPropertyURI("groupwrite"), getGroupsString(rs, "group_write"),
Sara Bertocco's avatar
Sara Bertocco committed
        addProperty(NodeProperties.getPropertyURI("publicread"), String.valueOf(rs.getBoolean("is_public")),
Sonia Zorba's avatar
Sonia Zorba committed
        addProperty("urn:async_trans", String.valueOf(rs.getBoolean("async_trans")),
Sonia Zorba's avatar
Sonia Zorba committed
        addProperty("urn:sticky", String.valueOf(rs.getBoolean("sticky")), properties);

        int nodesWithPath = countNodesWithPath(path);
            throw new IllegalStateException("Node at path "
            throw new IllegalStateException("Multiple nodes at path " + path);
        }

        String insertSql = "INSERT INTO deleted_node "
                + "(node_id, parent_path, parent_relative_path, "
                + "name, os_name, tstamp_wrapper_dir, type, location_id, format, "
                + "async_trans, busy_state, owner_id, creator_id, group_read, "
                + "group_write, is_public, delta, content_type, content_encoding, "
                + "content_length, content_md5, created_on, last_modified, "
Sonia Zorba's avatar
Sonia Zorba committed
                + "accept_views, provide_views, protocols, sticky)\n";

        String deleteSql = "DELETE \n"
                + "FROM node n\n"
                + "USING node_vos_path os\n"
                + "WHERE n.node_id = os.node_id AND\n"
                + "(n.path ~ (" + getAllLevelsChildrenSelector(path) + ")::lquery\n"
                + "OR os.vos_path = ?) RETURNING\n"
                + "n.node_id, parent_path, parent_relative_path, "
                + "name, os_name, tstamp_wrapper_dir, type, location_id, format, "
                + "async_trans, busy_state, owner_id, creator_id, group_read, "
                + "group_write, is_public, delta, content_type, content_encoding, "
                + "content_length, content_md5, created_on, last_modified, "
Sonia Zorba's avatar
Sonia Zorba committed
                + "accept_views, provide_views, protocols, sticky\n";

        String withSql = "WITH del AS (" + deleteSql + ")";

        String sql = withSql + insertSql + "SELECT * FROM del\n";

        jdbcTemplate.update(sql, path, path);
    public int countNodesWithPath(String path) {
        String sql = "SELECT COUNT(*) from "
                + "node_vos_path p "
                + "where p.vos_path = ?";
        Object[] args = {path};
        int[] types = {Types.VARCHAR};
        return jdbcTemplate.queryForObject(sql, args, types, Integer.class);
    }
    
    public String getNodeOsName(String vosPath) {
        String sql = "SELECT \n"
                + "(CASE WHEN os_name IS NOT NULL THEN os_name ELSE name END) AS os_name\n"
                + "FROM node n\n"
                + "JOIN node_vos_path p ON n.node_id = p.node_id\n"
                + "WHERE p.vos_path = ?";

        Object[] args = {vosPath};
        int[] types = {Types.VARCHAR};

        return jdbcTemplate.queryForObject(sql, args, types, String.class);
    }
    
    public void setNodeLocation(String vosPath, int locationId, String nodeOsName) {

        String sql = "UPDATE node SET location_id = ?, os_name = ? WHERE node_id = "
                + "(SELECT node_id FROM node_vos_path WHERE vos_path = ?)";

        int updated = jdbcTemplate.update(sql, ps -> {
            ps.setInt(1, locationId);
            ps.setString(2, nodeOsName);
            ps.setString(3, vosPath);
        });

        if (updated != 1) {
            throw new InternalFaultException("Unable to set node location for path " + vosPath);
        }
    }
    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<Property> list) {
        if (value != null) {
            Property prop = new Property();
            prop.setUri(uri);
            prop.setValue(value);
            list.add(prop);
        }
    }
Sara Bertocco's avatar
Sara Bertocco committed
    

    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"));
Sonia Zorba's avatar
Sonia Zorba committed
        return paths;
Sara Bertocco's avatar
Sara Bertocco committed
    
    private Array fromPropertyToArray(PreparedStatement ps, String myProperty) throws SQLException {
        if (myProperty == null || myProperty.isBlank()) {
        } else {
            return ps.getConnection().createArrayOf("varchar", myProperty.split(" "));
Sonia Zorba's avatar
Sonia Zorba committed
        }
    private Array fromViewsToArray(PreparedStatement ps, Node node, Function<DataNode, List<View>> viewsExtractor) throws SQLException {
        if (node instanceof DataNode) {
            DataNode dataNode = (DataNode) node;
            List<View> views = viewsExtractor.apply(dataNode);
            if (views != null && !views.isEmpty()) {
                Object[] array = views.stream().map(v -> v.getUri()).toArray();
                return ps.getConnection().createArrayOf("varchar", array);
            }
        }
        return null;
    }

    private List<View> getViews(Array array) throws SQLException {
        if (array == null) {
            return null;
        }
        return Arrays.stream((String[]) array.getArray())
                .map(uri -> {
                    View view = new View();
                    view.setUri(uri);
                    return view;
                })
                .collect(Collectors.toList());
    }

        private final String path;
        private final String relativePath;
        public NodePaths(String myPath, String myRelativePath) {
            this.relativePath = myRelativePath;
Sonia Zorba's avatar
Sonia Zorba committed
        }

        @Override
            return relativePath + " " + path;
Sara Bertocco's avatar
Sara Bertocco committed
}