Package org.jboss.seam.wiki.core.action

Source Code of org.jboss.seam.wiki.core.action.DirectoryBrowser

/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.seam.wiki.core.action;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.framework.EntityNotFoundException;
import org.jboss.seam.annotations.*;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.security.Restrict;
import org.jboss.seam.international.Messages;
import org.jboss.seam.international.StatusMessages;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Identity;
import org.jboss.seam.wiki.core.model.*;
import org.jboss.seam.wiki.core.dao.WikiNodeDAO;
import org.jboss.seam.wiki.core.dao.UserDAO;
import org.jboss.seam.wiki.core.exception.InvalidWikiRequestException;
import org.jboss.seam.wiki.util.WikiUtil;
import org.richfaces.component.UITree;
import org.richfaces.component.html.HtmlTree;
import org.richfaces.event.NodeExpandedEvent;
import org.richfaces.event.NodeSelectedEvent;

import static org.jboss.seam.international.StatusMessage.Severity.WARN;
import static org.jboss.seam.international.StatusMessage.Severity.INFO;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.*;

/**
* AJAX-oriented backend for browsing directories and copy/pasting files.
*
* @author Christian Bauer
*/
@Name("directoryBrowser")
@Scope(ScopeType.CONVERSATION)
public class DirectoryBrowser implements Serializable {

    @Logger
    Log log;

    @In
    Clipboard clipboard;

    @In
    StatusMessages statusMessages;

    @In
    EntityManager restrictedEntityManager;

    @In
    WikiNodeDAO wikiNodeDAO;

    @In
    WikiDirectory wikiRoot;

    @In(value = "directoryBrowserSettings")
    DirectoryBrowserSettings settings;

    @Create
    @Begin // This conversation ends through timeout only
    public void create() {
        log.debug("instantiating directory browser conversation");
        resetPager();
    }

    private Long directoryId;
    private WikiDirectory instance;

    // TODO: No more nested set, rewriting this with the same functionality is more difficult...
    //private NestedSetNodeWrapper<WikiDirectory> treeRoot;

    private List<WikiNode> childNodes;
    private Map<WikiNode, Boolean> selectedNodes = new HashMap<WikiNode,Boolean>();
    private Pager pager;

    public Long getDirectoryId() { return directoryId; }
    public void setDirectoryId(Long directoryId) { this.directoryId = directoryId; }

    public List<WikiNode> getChildNodes() { return childNodes; }
    public Map<WikiNode, Boolean> getSelectedNodes() { return selectedNodes; }

    public EntityManager getEntityManager() { return restrictedEntityManager; }

    public WikiNodeDAO getWikiNodeDAO() { return wikiNodeDAO; }

    public WikiDirectory getInstance() {
        if (instance == null) findInstance();
        return instance;
    }

    public void setInstance(WikiDirectory instance) {
        this.instance = instance;
    }

    public Pager getPager() {
        return pager;
    }

    public void setPager(Pager pager) {
        this.pager = pager;
    }

    private void resetPager() {
        pager = new Pager(settings.getPageSize());
    }

    /*
    public NestedSetNodeWrapper<WikiDirectory> getTreeRoot() {
        if (treeRoot == null) loadTree();
        return treeRoot;
    }
    */

    public void showTree() {
        settings.setTreeVisible(true);
    }

    public void hideTree() {
        settings.setTreeVisible(false);
    }

    /*
    // Open a node in the visible UI tree if its identifier is in the current path
    public boolean adviseTreeNodeOpened(UITree tree) {

        // We need to call the undocumented getRowData() and not getTreeNode() because we
        // use the rich:recursiveTreeNodeAdapter...
        if (tree.getRowData() == null) return false; // Safety against RichFaces behavior
        Long currentTreeNodeId = ((NestedSetNodeWrapper<WikiDirectory>) tree.getRowData()).getWrappedNode().getId();

        if (settings.getExpandedTreeNodes().contains(currentTreeNodeId)) {
            log.debug("node is stored as expanded in session: " + currentTreeNodeId);
            return true;
        }

        if (getInstance().getPathIdentifiers().contains(currentTreeNodeId)) {
            log.debug("node is in parent path of current directory, hence expanded: " + currentTreeNodeId);
            return true;
        }
        log.debug("node is not expanded: " + currentTreeNodeId);
        return false;
    }

    // Select the node in the visible UI tree if its identifier is the same as the current directory
    public boolean adviseTreeNodeSelected(UITree tree) {
        if (tree.getRowData() == null) return false; // Safety against RichFaces behavior
        Long currentTreeNodeId = ((NestedSetNodeWrapper<WikiDirectory>) tree.getRowData()).getWrappedNode().getId();
        return getInstance().getId().equals(currentTreeNodeId);
    }

    public void listenTreeNodeExpand(NodeExpandedEvent event) {
        Long currentTreeNodeId =
                ((NestedSetNodeWrapper<WikiDirectory>) ((HtmlTree)event.getSource()).getRowData()).getWrappedNode().getId();

        boolean isExpanded = ((HtmlTree)event.getSource()).isExpanded();
        if (isExpanded) {
            log.debug("expanding tree node: " + currentTreeNodeId);
            settings.getExpandedTreeNodes().add(currentTreeNodeId);
        } else {
            log.debug("collapsing tree node: " + currentTreeNodeId);
            settings.getExpandedTreeNodes().remove(currentTreeNodeId);
        }
    }

    public void listenTreeNodeSelected(NodeSelectedEvent event) {
        Long currentTreeNodeId =
                ((NestedSetNodeWrapper<WikiDirectory>) ((HtmlTree)event.getSource()).getRowData()).getWrappedNode().getId();
        log.debug("selecting tree node: " + currentTreeNodeId);
        selectDirectory(currentTreeNodeId);
    }
    */

    public void findInstance() {
        if (getDirectoryId() == null)
            throw new InvalidWikiRequestException("Missing directoryId parameter");

        instance = wikiNodeDAO.findWikiDirectory(getDirectoryId());
        if (instance == null)
            throw new EntityNotFoundException(getDirectoryId(), WikiDirectory.class);

        afterNodeFound();
    }

    public void afterNodeFound() {
        refreshChildNodes();
    }

    public void selectDirectory(Long nodeId) {
        resetPager();
        setDirectoryId(nodeId);
        instance = null;
        findInstance();
        settings.getExpandedTreeNodes().add(nodeId);
    }

    public void sortBy(String propertyName) {
        resetPager();
        settings.setOrderByProperty(WikiNode.SortableProperty.valueOf(propertyName));
        settings.setOrderDescending(!settings.isOrderDescending());
        refreshChildNodes();
    }

    public void changePageSize() {
        pager.setPage(0);
        refreshChildNodes();
    }

    /*
    @Observer(value = {"Node.removed"}, create = false)
    public void loadTree() {
        WikiDirectory wikiRoot = (WikiDirectory) Component.getInstance("wikiRoot");
        treeRoot = wikiNodeDAO.findWikiDirectoryTree(wikiRoot);
    }
    */

    @Observer(value = {"Node.removed", "Pager.pageChanged"}, create = false)
    public void refreshChildNodes() {

        log.debug("refreshing child nodes of current directory: " + getInstance());
        getPager().setNumOfRecords(wikiNodeDAO.findChildrenCount(getInstance()));
        getPager().setPageSize(settings.getPageSize());

        log.debug("number of children: " + getPager().getNumOfRecords());
        if (getPager().getNumOfRecords() > 0) {
            log.debug("loading children page from: " + getPager().getNextRecord() + " size: " + getPager().getPageSize());
            childNodes =
                    wikiNodeDAO.findChildren(
                            getInstance(),
                            settings.getOrderByProperty(),
                            !settings.isOrderDescending(),
                            getPager().getQueryFirstResult(),
                            getPager().getQueryMaxResults()
                    );
        } else {
            childNodes = Collections.emptyList();
        }
    }

    // TODO: Most of this clipboard stuff is based on the hope that nobody modifies anything while we have it in the clipboard...

    public void clearClipboard() {
        clipboard.clear();
    }

    public void copy() {
        for (Map.Entry<WikiNode, Boolean> entry : selectedNodes.entrySet()) {
            if (entry.getValue()) { // Has to be true for a selected node
                log.debug("copying to clipboard: " + entry.getKey());
                clipboard.add(entry.getKey().getId(), false);
            }
        }
        selectedNodes.clear();
    }

    @Restrict("#{s:hasPermission('Node', 'edit', directoryBrowser.instance)}")
    public void cut() {
        for (Map.Entry<WikiNode, Boolean> entry : selectedNodes.entrySet()) {
            if (entry.getValue()) { // Has to be true for a selected node
                log.debug("cutting to clipboard: " + entry.getKey());
                clipboard.add(entry.getKey().getId(), true);
            }
        }
        selectedNodes.clear();
        refreshChildNodes();
    }

    @Restrict("#{s:hasPermission('Node', 'create', directoryBrowser.instance)}")
    public void paste() {

        if (getInstance().getId().equals(wikiRoot.getId())) return; // Can't paste in wiki root

        // Batch the work
        int batchSize = 2;
        int i = 0;
        List<Long> batchIds = new ArrayList<Long>();
        for (Long clipboardNodeId : clipboard.getItems()) {
            i++;
            batchIds.add(clipboardNodeId);
            if (i % batchSize == 0) {
                List<WikiNode> nodesForPasteBatch = wikiNodeDAO.findWikiNodes(batchIds);
                pasteNodes(nodesForPasteBatch);
                batchIds.clear();
            }
        }
        // Last batch
        if (batchIds.size() != 0) {
            List<WikiNode> nodesForPasteBatch = wikiNodeDAO.findWikiNodes(batchIds);
            pasteNodes(nodesForPasteBatch);
        }

        log.debug("completed executing paste, refreshing...");

        selectedNodes.clear();
        clipboard.clear();
        refreshChildNodes();
    }

    private void pasteNodes(List<WikiNode> nodes) {
        log.debug("executing paste batch");
        for (WikiNode n: nodes) {
            log.debug("pasting clipboard item: " + n);
            String pastedName = n.getName();

            // Check unique name if we are not cutting and pasting into the same area
            if (!(clipboard.isCut(n.getId()) && n.getParent().getAreaNumber().equals(getInstance().getAreaNumber()))) {
                log.debug("pasting node into different area, checking wikiname");

                if (!wikiNodeDAO.isUniqueWikiname(getInstance().getAreaNumber(), WikiUtil.convertToWikiName(pastedName))) {
                    log.debug("wikiname is not unique, renaming");
                    if (pastedName.length() > 245) {
                        statusMessages.addToControlFromResourceBundleOrDefault(
                            "name",
                            WARN,
                            "lacewiki.msg.Clipboard.DuplicatePasteNameFailure",
                            "The name '{0}' was already in use in this area and is too long to be renamed, skipping paste.",
                            pastedName
                        );
                        continue; // Jump to next loop iteration when we can't append a number to the name
                    }

                    // Now try to add "Copy 1", "Copy 2" etc. to the name until it is unique
                    int i = 1;
                    String attemptedName = pastedName + " " + Messages.instance().get("lacewiki.label.Clipboard.CopySuffix") + i;
                    while (!wikiNodeDAO.isUniqueWikiname(getInstance().getAreaNumber(), WikiUtil.convertToWikiName(attemptedName))) {
                        attemptedName = pastedName + " " + Messages.instance().get("lacewiki.label.Clipboard.CopySuffix") + (++i);
                    }
                    pastedName = attemptedName;

                    statusMessages.addToControlFromResourceBundleOrDefault(
                        "name",
                        INFO,
                        "lacewiki.msg.Clipboard.DuplicatePasteName",
                        "The name '{0}' was already in use in this area, renamed item to '{1}'.",
                        n.getName(), pastedName
                    );
                }
            }

            if (clipboard.isCut(n.getId())) {
                log.debug("cut pasting: " + n);

                // Check if the cut item was a default file for its parent
                if ( ((WikiDirectory)n.getParent()).getDefaultFile() != null &&
                    ((WikiDirectory)n.getParent()).getDefaultFile().getId().equals(n.getId())) {
                    log.debug("cutting default file of directory: " + n.getParent());
                    ((WikiDirectory)n.getParent()).setDefaultFile(null);
                }

                n.setName(pastedName);
                n.setWikiname(WikiUtil.convertToWikiName(pastedName));
                n.setParent(getInstance());

                // If we cut and paste into a new area, all children must be updated as well
                if (!getInstance().getAreaNumber().equals(n.getAreaNumber())) {
                    n.setAreaNumber(getInstance().getAreaNumber());

                    // TODO: Ugly and memory intensive, better use a database query but HQL updates are limited with joins
                    if (n.isInstance(WikiDocument.class)) {
                        List<WikiComment> comments = wikiNodeDAO.findWikiComments((WikiDocument)n, true);
                        for (WikiComment comment : comments) {
                            comment.setAreaNumber(n.getAreaNumber());
                        }
                    }
                }

            } else {
                log.debug("copy pasting: " + n);
                WikiNode newNode = n.duplicate(true);
                newNode.setName(pastedName);
                newNode.setWikiname(WikiUtil.convertToWikiName(pastedName));
                newNode.setParent(getInstance());
                newNode.setAreaNumber(getInstance().getAreaNumber());
                UserDAO userDAO = (UserDAO)Component.getInstance(UserDAO.class);
                newNode.setCreatedBy(userDAO.findUser(n.getCreatedBy().getId()));
                if (n.getLastModifiedBy() != null) {
                    newNode.setLastModifiedBy(userDAO.findUser(n.getLastModifiedBy().getId()));
                }
                restrictedEntityManager.persist(newNode);
            }
        }
        log.debug("completed executing of paste batch");
    }

    @Restrict("#{s:hasPermission('Trash', 'empty', trashArea)}")
    public void emptyTrash() {
        WikiDirectory trashArea = (WikiDirectory) Component.getInstance("trashArea");
        if (getInstance() == null || !trashArea.getId().equals(getInstance().getId())) return;

        log.debug("emptying trash");
        List<WikiNode> children = wikiNodeDAO.findChildren(getInstance(), WikiNode.SortableProperty.name, false, 0, Integer.MAX_VALUE);

        // TODO: This should be batched with a database cursor!
        for (WikiNode child : children) {
            log.debug("trashing item: " + child);
            if (child.isInstance(WikiDocument.class)) {
                NodeRemover documentRemover = (NodeRemover)Component.getInstance(DocumentNodeRemover.class);
                documentRemover.removeDependencies(child);
            } else if (child.isInstance(WikiUpload.class)) {
                NodeRemover uploadRemover = (NodeRemover)Component.getInstance(UploadNodeRemover.class);
                uploadRemover.removeDependencies(child);
            }
            restrictedEntityManager.remove(child);
        }
        restrictedEntityManager.flush();

        statusMessages.addFromResourceBundleOrDefault(
                INFO,
                "lacewiki.msg.Trash.Emptied",
                "All items in the trash have been permanently deleted."
        );

        selectedNodes.clear();
        refreshChildNodes();
    }


    // TODO: I'm not too happy with this, maybe we should call the NodeRemovers directly from the XHTML

    // Cache removablity information, speeds up large lists
    Map<Long, Boolean> childNodesRemovability = new HashMap<Long, Boolean>();

    public boolean isRemovable(WikiNode node) {

        if (childNodesRemovability.containsKey(node.getId())) {
            // Return cached result
            return childNodesRemovability.get(node.getId());
        }

        log.debug("checking removablity of node: " + node);

        // Check if the current directory is the trash area, delete doesn't make sense here
        WikiDirectory trashArea = (WikiDirectory)Component.getInstance("trashArea");
        if (trashArea.getId().equals(getInstance().getId()))
            return false;

        // Check permissions TODO: This duplicates the check
        if (!Identity.instance().hasPermission("Node", "edit", node)) {
            log.debug("user doesn't have edit permissions for this node: " + node);
            return false;
        }

        NodeRemover remover;
        if (node.isInstance(WikiDocument.class)) {
            remover = (NodeRemover) Component.getInstance(DocumentNodeRemover.class);
        } else if (node.isInstance(WikiUpload.class)) {
            remover = (NodeRemover) Component.getInstance(UploadNodeRemover.class);
        } else {
            log.warn("no remover found for node type: " + node);
            return false;
        }
        boolean removable = remover.isRemovable(node);
        log.debug("remover said it's removable: " + removable);

        childNodesRemovability.put(node.getId(), removable);

        return removable;
    }

}
TOP

Related Classes of org.jboss.seam.wiki.core.action.DirectoryBrowser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.