Commit 43628f25 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Used Jsoup for generating server-side HTML fragments

parent 84d7c5fc
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -34,6 +34,11 @@
            <artifactId>auth-lib</artifactId>
            <version>2.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.13.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
+15 −8
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@ package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.ListNodeData;
import it.inaf.ia2.vospace.ui.service.NodesService;
import it.inaf.ia2.vospace.ui.service.NodesHtmlGenerator;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import java.util.List;
import java.util.Map;
@@ -12,6 +12,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
@@ -36,9 +37,6 @@ public class NodesController extends BaseController {
    @Value("${vospace-authority}")
    private String authority;

    @Autowired
    private NodesService nodesService;

    @Autowired
    private VOSpaceClient client;

@@ -51,7 +49,16 @@ public class NodesController extends BaseController {
        String path = getPath("/nodes/");
        LOG.debug("listNodes called for path {}", path);

        return ResponseEntity.ok(nodesService.generateNodesHtml(path, principal));
        ListNodeData listNodeData = new ListNodeData();

        Node node = client.getNode(path);

        listNodeData.setWritable(NodeUtils.checkIfWritable(node, principal.getName(), principal.getGroups()));

        NodesHtmlGenerator htmlGenerator = new NodesHtmlGenerator(node, principal, authority);
        listNodeData.setHtmlTable(htmlGenerator.generateNodes());

        return ResponseEntity.ok(listNodeData);
    }

    @GetMapping(value = "/download/**")
+17 −3
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui.service;

import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import java.util.List;
import java.util.Optional;
import net.ivoa.xml.vospace.v2.DataNode;
@@ -29,8 +31,10 @@ public class NodeInfo {
    private final boolean sticky;
    private final boolean busy;
    private final boolean listOfFiles;
    private final boolean writable;
    private final boolean deletable;

    public NodeInfo(Node node, String authority) {
    public NodeInfo(Node node, User user, String authority) {
        this.authority = authority;
        this.path = getPath(node);
        this.name = path.substring(path.lastIndexOf("/") + 1);
@@ -44,6 +48,8 @@ public class NodeInfo {
        this.sticky = isSticky(node);
        this.busy = isBusy(node);
        this.listOfFiles = isListOfFiles(node);
        this.writable = NodeUtils.checkIfWritable(node, user.getName(), user.getGroups());
        this.deletable = writable && !sticky;
    }

    private String getPath(Node node) {
@@ -189,4 +195,12 @@ public class NodeInfo {
    public boolean isListOfFiles() {
        return listOfFiles;
    }

    public boolean isWritable() {
        return writable;
    }

    public boolean isDeletable() {
        return deletable;
    }
}
+223 −0
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui.service;

import it.inaf.ia2.aa.data.User;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Node;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class NodesHtmlGenerator {

    private final Node parentNode;
    private final User user;
    private final String authority;

    private Element tbody;

    public NodesHtmlGenerator(Node node, User user, String authority) {
        this.parentNode = node;
        this.user = user;
        this.authority = authority;
    }

    public String generateNodes() {

        Document html = Jsoup.parse("<html></html>");

        tbody = html.body().appendElement("tbody");
        tbody.attr("id", "nodes");

        if (parentNode instanceof ContainerNode) {
            ContainerNode folder = (ContainerNode) parentNode;
            for (Node child : folder.getNodes()) {
                addChild(child);
            }
        }

        return tbody.toString();
    }

    private void addChild(Node child) {
        NodeInfo nodeInfo = new NodeInfo(child, user, authority);

        if (nodeInfo.isListOfFiles()) {
            // hidden file
            return;
        }

        Element row = tbody.appendElement("tr");

        addSelectionCell(nodeInfo, row);
        addLinkCell(nodeInfo, row);
        addSizeCell(nodeInfo, row);
        addGroupReadCell(nodeInfo, row);
        addGroupWriteCell(nodeInfo, row);
        addActionsCell(nodeInfo, row);
    }

    private void addSelectionCell(NodeInfo nodeInfo, Element row) {

        Element cell = row.appendElement("td");

        Element input = cell.appendElement("input");
        input.attr("type", "checkbox");
        input.attr("data-node", nodeInfo.getPath());

        if (nodeInfo.isAsyncTrans()) {
            input.addClass("async");
        } else if (nodeInfo.isDeletable()) {
            input.addClass("deletable");
        }
    }

    private void addLinkCell(NodeInfo nodeInfo, Element row) {

        Element cell = row.appendElement("td");
        addNodeIcon(nodeInfo, cell);
        addLink(nodeInfo, cell);
    }

    private void addSizeCell(NodeInfo nodeInfo, Element row) {
        Element cell = row.appendElement("td");
        cell.text(nodeInfo.getSize());
    }

    private void addGroupReadCell(NodeInfo nodeInfo, Element row) {
        Element cell = row.appendElement("td");
        fillGroupCell(cell, nodeInfo.getGroupRead());
    }

    private void addGroupWriteCell(NodeInfo nodeInfo, Element row) {
        Element cell = row.appendElement("td");
        fillGroupCell(cell, nodeInfo.getGroupWrite());
    }

    private void fillGroupCell(Element cell, String groups) {
        String[] values = groups.split(" ");
        List<String> personGroups = new ArrayList<>();
        List<String> peopleGroups = new ArrayList<>();
        for (String value : values) {
            if (value.startsWith("people.")) {
                personGroups.add(value.substring("people.".length()).replace("\\.", "."));
            } else {
                peopleGroups.add(value);
            }
        }
        if (!personGroups.isEmpty()) {
            Element personIcon = cell.appendElement("span");
            personIcon.attr("class", "icon person-icon");
            cell.appendText(String.join(" ", personGroups));
            cell.append("&nbsp;");
        }
        if (!peopleGroups.isEmpty()) {
            Element personIcon = cell.appendElement("span");
            personIcon.attr("class", "icon people-icon");
            cell.appendText(String.join(" ", peopleGroups));
        }
    }

    private void addActionsCell(NodeInfo nodeInfo, Element row) {
        Element cell = row.appendElement("td");
        if (nodeInfo.isWritable()) {
            Element shareIcon = cell.appendElement("span");
            shareIcon.attr("class", "icon share-icon pointer");
            shareIcon.attr("onclick", "shareNode(" + makeJsArg(nodeInfo.getPath())
                    + "," + makeJsArg(nodeInfo.getGroupRead())
                    + "," + makeJsArg(nodeInfo.getGroupWrite()) + ")");
        }
        if (nodeInfo.isDeletable()) {
            cell.append("&nbsp;");
            Element deleteIcon = cell.appendElement("span");
            deleteIcon.attr("class", "icon trash-icon pointer");
            deleteIcon.attr("onclick", "deleteNode(" + makeJsArg(nodeInfo.getPath()) + ")");
        }
    }

    private String makeJsArg(String arg) {
        return "'" + arg.replace("\\", "\\\\").replace("'", "\\'") + "'";
    }

    private void addNodeIcon(NodeInfo nodeInfo, Element cell) {

        Element iconContainer = cell;

        if (nodeInfo.isBusy()) {
            Element loadingWrapper = cell.appendElement("span");
            iconContainer = loadingWrapper;
            loadingWrapper.addClass("node-busy");
            Element spinner = loadingWrapper.appendElement("span");
            spinner.attr("role", "status");
            spinner.addClass("spinner-border");
            Element srEl = spinner.appendElement("span");
            srEl.addClass("sr-only");
            srEl.text("Loading...");
        }

        Element icon = cell.appendElement("span");
        icon.addClass("icon");

        if (nodeInfo.isFolder()) {
            if (nodeInfo.isAsyncTrans()) {
                icon.addClass("folder-x-icon");
            } else {
                icon.addClass("folder-icon");
            }
        } else {
            if (nodeInfo.isAsyncTrans()) {
                icon.addClass("file-x-icon");
            } else {
                icon.addClass("file-icon");
            }
        }

        iconContainer.append("&nbsp;");
    }

    private void addLink(NodeInfo nodeInfo, Element cell) {
        if (isDownloadable(nodeInfo, user)) {
            Element link = cell.appendElement("a");
            String href;
            if (nodeInfo.isFolder()) {
                href = "#/nodes" + urlEncodePath(nodeInfo.getPath());
            } else {
                href = "download" + urlEncodePath(nodeInfo.getPath());
                link.attr("target", "blank_");
            }
            link.attr("href", href);
            link.text(nodeInfo.getName());
        } else {
            cell.text(nodeInfo.getName());
        }
    }

    private boolean isDownloadable(NodeInfo nodeInfo, User user) {
        if (nodeInfo.isFile()) {
            if (nodeInfo.isAsyncTrans() || nodeInfo.isBusy()) {
                return false;
            }
        }
        if (nodeInfo.isPublic()) {
            return true;
        }

        if (nodeInfo.getCreator().equals(user.getName())) {
            return true;
        }

        if (user.getGroups() != null && !user.getGroups().isEmpty() && !nodeInfo.getGroupRead().isEmpty()) {
            List<String> groupRead = Arrays.asList(nodeInfo.getGroupRead().split(" "));
            for (String group : groupRead) {
                if (user.getGroups().contains(group)) {
                    return true;
                }
            }
        }

        return false;
    }
}
+0 −149
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui.service;

import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.ListNodeData;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.List;
import net.ivoa.xml.vospace.v2.ContainerNode;
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.stereotype.Service;

@Service
public class NodesService {

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

    @Autowired
    private VOSpaceClient client;

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

    public ListNodeData generateNodesHtml(String path, User user) {

        ListNodeData listNodeData = new ListNodeData();

        Node node = client.getNode(path);

        listNodeData.setWritable(NodeUtils.checkIfWritable(node, user.getName(), user.getGroups()));

        try (StringWriter sw = new StringWriter()) {

            if (node instanceof ContainerNode) {
                ContainerNode folder = (ContainerNode) node;
                sw.write("<tbody id=\"nodes\">");
                for (Node child : folder.getNodes()) {
                    sw.write(getNodeHtml(child, user));
                }
                sw.write("</tbody>");
            }

            listNodeData.setHtmlTable(sw.toString());

            return listNodeData;
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    private String getNodeHtml(Node node, User user) {

        NodeInfo nodeInfo = new NodeInfo(node, authority);

        if (nodeInfo.isListOfFiles()) {
            // hidden file
            return "";
        }

        boolean deletable = NodeUtils.checkIfWritable(node, user.getName(), user.getGroups()) && !nodeInfo.isSticky();

        String html = "<tr>";
        html += "<td><input type=\"checkbox\" data-node=\"" + nodeInfo.getPath().replace("\"", "\\\"") + "\" ";
        if (nodeInfo.isAsyncTrans()) {
            html += "class=\"async\"";
        } else if (deletable) {
            html += "class=\"deletable\"";
        }
        html += "/></td>";
        html += "<td>" + getIcon(nodeInfo) + getLink(nodeInfo, user) + "</td>";
        html += "<td>" + nodeInfo.getSize() + "</td>";
        html += "<td>" + nodeInfo.getGroupRead() + "</td>";
        html += "<td>" + nodeInfo.getGroupWrite() + "</td>";
        html += "<td>";
        if (deletable) {
            html += "<span class=\"icon trash-icon pointer\" onclick=\"deleteNode('" + nodeInfo.getPath() + "')\"></span>";
        }
        html += "</td>";
        html += "</tr>";
        return html;
    }

    private String getIcon(NodeInfo nodeInfo) {
        String html = "";
        if (nodeInfo.isBusy()) {
            html += "<span class=\"node-busy\"><span role=\"status\" class=\"spinner-border\"><span class=\"sr-only\">Loading...</span></span>";
        }
        html += "<span class=\"icon ";
        if (nodeInfo.isFolder()) {
            html += "folder";
        } else {
            html += "file";
        }
        if (nodeInfo.isAsyncTrans()) {
            html += "-x";
        }
        html += "-icon\"></span>";
        if (nodeInfo.isBusy()) {
            html += "</span>";
        }
        html += "&nbsp;";
        return html;
    }

    private String getLink(NodeInfo nodeInfo, User user) {
        if (isDownloadable(nodeInfo, user)) {
            if (nodeInfo.isFolder()) {
                return "<a href=\"#/nodes" + urlEncodePath(nodeInfo.getPath()) + "\">" + nodeInfo.getName() + "</a>";
            } else {
                return "<a href=\"download" + urlEncodePath(nodeInfo.getPath()) + "\" target=\"blank_\">" + nodeInfo.getName() + "</a>";
            }
        }
        return nodeInfo.getName();
    }

    private boolean isDownloadable(NodeInfo nodeInfo, User user) {
        if (nodeInfo.isFile()) {
            if (nodeInfo.isAsyncTrans() || nodeInfo.isBusy()) {
                return false;
            }
        }
        if (nodeInfo.isPublic()) {
            return true;
        }

        if (nodeInfo.getCreator().equals(user.getName())) {
            return true;
        }

        if (user.getGroups() != null && !user.getGroups().isEmpty() && !nodeInfo.getGroupRead().isEmpty()) {
            List<String> groupRead = Arrays.asList(nodeInfo.getGroupRead().split(" "));
            for (String group : groupRead) {
                if (user.getGroups().contains(group)) {
                    return true;
                }
            }
        }

        return false;
    }
}
Loading