/* * 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.annotations.In; import org.jboss.seam.annotations.security.Restrict; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.core.Events; import org.jboss.seam.core.Conversation; import org.jboss.seam.framework.EntityHome; import org.jboss.seam.security.AuthorizationException; import org.jboss.seam.security.Identity; import org.jboss.seam.wiki.core.dao.UserDAO; import org.jboss.seam.wiki.core.dao.WikiNodeDAO; import org.jboss.seam.wiki.core.model.*; import org.jboss.seam.wiki.core.action.prefs.WikiPreferences; import org.jboss.seam.wiki.core.exception.InvalidWikiRequestException; import org.jboss.seam.wiki.util.WikiUtil; import org.jboss.seam.wiki.preferences.Preferences; import org.jboss.seam.international.StatusMessages; import static org.jboss.seam.international.StatusMessage.Severity.WARN; import static org.jboss.seam.international.StatusMessage.Severity.INFO; import java.util.Date; import java.util.List; /** * Superclass for all creating and editing documents, directories, files, etc. * * @author Christian Bauer */ public abstract class NodeHome<N extends WikiNode, P extends WikiNode> extends EntityHome<N> { // TODO: This is a performance optimization, our EM is always already joined (SMPC) protected void joinTransaction() {} /* -------------------------- Context Wiring ------------------------------ */ @In private WikiNodeDAO wikiNodeDAO; @In private UserDAO userDAO; @In private WikiDirectory wikiRoot; @In protected User currentUser; @In protected List<Role.AccessLevel> accessLevelsList; public WikiNodeDAO getWikiNodeDAO() { return wikiNodeDAO; } public UserDAO getUserDAO() { return userDAO; } public WikiDirectory getWikiRoot() { return wikiRoot; } public User getCurrentUser() { return currentUser; } public List<Role.AccessLevel> getAccessLevelsList() { return accessLevelsList; } /* -------------------------- Request Wiring ------------------------------ */ private Long parentNodeId; public Long getParentNodeId() { return parentNodeId; } public void setParentNodeId(Long parentNodeId) { this.parentNodeId = parentNodeId; } private P parentNode; public P getParentNode() { return parentNode; } public void setParentNode(P parentNode) { this.parentNode = parentNode; } public void setNodeId(Long o) { super.setId(o); } public Long getNodeId() { return (Long)super.getId(); } /* -------------------------- Additional States ------------------------------ */ private boolean editor = false; public boolean isEditor() { return editor; } public void initEditor(boolean visibleWorkspace) { getLog().debug("initializing editor workspace"); this.editor = true; if (visibleWorkspace) { // Set workspace description of the current conversation String desc = getEditorWorkspaceDescription(getNodeId() == null); WikiPreferences prefs = Preferences.instance().get(WikiPreferences.class); if (desc != null && desc.length() > prefs.getWorkspaceSwitcherDescriptionLength()) { desc = desc.substring(0, prefs.getWorkspaceSwitcherDescriptionLength().intValue()) + "..."; } Conversation.instance().setDescription(desc); } } public void initEditor() { initEditor(true); } /* -------------------------- Basic Overrides ------------------------------ */ @Override protected String getPersistenceContextName() { return "restrictedEntityManager"; } @Override public N find() { getLog().debug("finding an existing instance with id: " + getId()); N foundNode = findInstance(); if (foundNode == null) { handleNotFound(); return null; } getLog().debug("found instance: " + foundNode); return isEditor() ? beforeNodeEditFound(afterNodeFound(foundNode)) : afterNodeFound(foundNode); } @Override protected N createInstance() { getLog().debug("creating a new instance"); N newNode = super.createInstance(); getLog().debug("created new instance: " + newNode); return isEditor() ? beforeNodeEditNew(afterNodeCreated(newNode)) : afterNodeCreated(newNode); } /* -------------------------- Basic Subclass Callbacks ------------------------------ */ public N afterNodeCreated(N node) { if (parentNodeId == null) throw new InvalidWikiRequestException("Missing parentNodeId parameter"); outjectCurrentLocation(node); return node; } public N beforeNodeEditNew(N node) { getLog().debug("loading parent node with id: " + parentNodeId); parentNode = findParentNode(parentNodeId); if (parentNode == null) throw new InvalidWikiRequestException("Could not find parent node with id: " + parentNodeId); getLog().debug("initalized with parent node: " + parentNode); // Check write access level of the parent node, if the user wants to create a new node if (!isPersistAllowed(node, parentNode)) throw new AuthorizationException("You don't have permission for this operation"); // Default to same access permissions as parent node node.setWriteAccessLevel(parentNode.getWriteAccessLevel()); node.setReadAccessLevel(parentNode.getReadAccessLevel()); writeAccessLevel = getAccessLevelsList().get( accessLevelsList.indexOf( new Role.AccessLevel(parentNode.getWriteAccessLevel()) ) ); readAccessLevel = getAccessLevelsList().get( accessLevelsList.indexOf( new Role.AccessLevel(parentNode.getReadAccessLevel()) ) ); return node; } public N afterNodeFound(N node) { getLog().debug("using parent of instance: " + node.getParent()); if (node.getParent() != null) { // Wiki Root doesn't have a parent parentNode = (P)node.getParent(); parentNodeId = parentNode.getId(); } outjectCurrentLocation(node); return node; } public N beforeNodeEditFound(N node) { // Check write access level of the node the user wants to edit if (!isUpdateAllowed(node, null)) throw new AuthorizationException("You don't have permission for this operation"); writeAccessLevel = getAccessLevelsList().get( accessLevelsList.indexOf( new Role.AccessLevel(node.getWriteAccessLevel()) ) ); readAccessLevel = getAccessLevelsList().get( accessLevelsList.indexOf( new Role.AccessLevel(node.getReadAccessLevel()) ) ); return node; } /* -------------------------- Custom CUD ------------------------------ */ @Override public String persist() { checkPersistPermissions(); if (!validateComponents(getPersistValidations())) return null; if (!preparePersist()) return null; getLog().trace("linking new node with its parent node: " + getParentNode()); getInstance().setParent(getParentNode()); // Creation metadata setCreatedMetadata(); // Wiki name conversion setWikiName(); // Set its area number (if subclass didn't already set it) if (getInstance().getAreaNumber() == null) getInstance().setAreaNumber(getInstance().getParent().getAreaNumber()); // Validate if (!isValidModel()) return null; if (!beforePersist()) return null; getLog().debug("persisting node: " + getInstance()); String outcome = super.persist(); if (outcome != null) { Events.instance().raiseEvent("PreferenceEditor.flushAll"); Events.instance().raiseEvent("Node.persisted", getInstance()); } // Now set the message identifier, if nobody else did if (getInstance().getMessageId() == null && requiresMessageId()) { getInstance().setMessageId( // Use the identifier and the creation time, both quite unique and immutable WikiUtil.calculateMessageId( getInstance().getId(), String.valueOf(getInstance().getCreatedOn().getTime())) ); // Need to flush again, to execute UPDATE getEntityManager().flush(); } return outcome; } @Override public String update() { checkUpdatePermissions(); if (!validateComponents(getUpdateValidations())) return null; if (!prepareUpdate()) return null; // Modification metadata setLastModifiedMetadata(); // Wiki name conversion setWikiName(); // Validate if (!isValidModel()) return null; if (!beforeUpdate()) return null; getLog().debug("updating node: " + getInstance()); String outcome = super.update(); if (outcome != null) { Events.instance().raiseEvent("PreferenceEditor.flushAll"); Events.instance().raiseEvent("Node.updated", getInstance()); } return outcome; } public boolean isRemovable() { getLog().debug("checking removability of current instance"); return isManaged() && getNodeRemover() != null && getNodeRemover().isRemovable(getInstance()); } @Override public String remove() { if (!isRemovable()) return null; checkRemovePermissions(); getLog().debug("removing node: " + getInstance()); getNodeRemover().removeDependencies(getInstance()); String outcome = super.remove(); if (outcome != null) { Events.instance().raiseEvent("Node.removed", getInstance()); } return outcome; } public String remove(Long nodeId) { getLog().debug("requested node remove with id: " + nodeId); setNodeId(nodeId); initEditor(false); String outcome = remove(); if (outcome != null) { Events.instance().raiseEvent("Node.removed", getInstance()); } return outcome; } public String trash() { if (!isRemovable()) return null; checkRemovePermissions(); getLog().debug("trashing node : " + getInstance()); getNodeRemover().trash(getInstance()); setLastModifiedMetadata(); getEntityManager().flush(); trashedMessage(); Events.instance().raiseEvent("Node.removed", getInstance()); return "removed"; } /* -------------------------- Internal (Subclass) Methods ------------------------------ */ public abstract Class<N> getEntityClass(); protected abstract N findInstance(); protected abstract P findParentNode(Long parentNodeId); protected void outjectCurrentLocation(WikiNode node) { if (isPageRootController()) { // Outjects current node or parent directory, e.g. for breadcrumb rendering Contexts.getPageContext().set("currentLocation", node); } } protected void setWikiName() { getLog().trace("setting wiki name of node"); getInstance().setWikiname(WikiUtil.convertToWikiName(getInstance().getName())); } protected void setCreatedMetadata() { getLog().trace("setting created metadata"); getInstance().setCreatedBy(getCurrentUser()); getInstance().setCreatedOn(new Date()); } protected void setLastModifiedMetadata() { getLog().trace("setting last modified metadata"); getInstance().setLastModifiedBy(getCurrentUser()); getInstance().setLastModifiedOn(new Date()); } protected boolean isValidModel() { getLog().trace("validating model"); if (getParentNode() == null) return true; // Special case, editing the wiki root // Unique wiki name if (getWikiNodeDAO().isUniqueWikiname(getParentNode().getAreaNumber(), getInstance())) { return true; } else { StatusMessages.instance().addToControlFromResourceBundleOrDefault( "name", WARN, "lacewiki.entity.DuplicateName", "This name is already used, please change it" ); return false; } } protected void checkPersistPermissions() { getLog().debug("checking persist permissions"); if (!isPersistAllowed(getInstance(), getParentNode())) throw new AuthorizationException("You don't have permission for this operation"); } protected void checkUpdatePermissions() { getLog().debug("checking update permissions"); if (!isUpdateAllowed(getInstance(), getParentNode())) throw new AuthorizationException("You don't have permission for this operation"); } protected void checkRemovePermissions() { getLog().debug("checking remove permissions"); if (!isRemoveAllowed(getInstance(), getParentNode())) throw new AuthorizationException("You don't have permission for this operation"); } protected void trashedMessage() { StatusMessages.instance().addFromResourceBundleOrDefault( INFO, "lacewiki.msg.Node.Trashed", "'{0}' has been moved into the trash.", getInstance().getName() ); } public boolean isPersistAllowed(N node, P parent) { return Identity.instance().hasPermission("Node", "create", parent); } public boolean isUpdateAllowed(N node, P parent) { return Identity.instance().hasPermission("Node", "edit", node); } public boolean isRemoveAllowed(N node, P parent) { return Identity.instance().hasPermission("Node", "edit", node); } protected boolean validateComponents(Validatable validatableComponents[]) { if (validatableComponents == null) return true; boolean allValid = true; for (Validatable validatableComponent : validatableComponents) { validatableComponent.validate(); allValid = validatableComponent.isValid(); } return allValid; } protected Validatable[] getUpdateValidations() { return null; } protected Validatable[] getPersistValidations() { return null; } protected boolean requiresMessageId() { return true; } /* -------------------------- Optional Subclass Callbacks ------------------------------ */ protected boolean isPageRootController() { return true; } /** * Called before the superclass does its preparation; * @return boolean continue processing */ protected boolean preparePersist() { return true; } /** * Called after superclass did its preparation right before the actual persist() * @return boolean continue processing */ protected boolean beforePersist() { return true; } /** * Called before the superclass does its preparation; * @return boolean continue processing */ protected boolean prepareUpdate() { return true; } /** * Called after superclass did its preparation right before the actual update() * @return boolean continue processing */ protected boolean beforeUpdate() { return true; } /** * Called when a node is removed, obtains remover for execution of dependency deletion * before the node is finally removed. * @return NodeRemover instance */ protected abstract NodeRemover getNodeRemover(); /** * Description (i18n) of workspace switcher item. * * @param create true if editor is initialized to create an item, false if it's used to update an item. * @return String description of workspace switcher item or <tt>null</tt> if no workspace switcher item should be shown. */ protected abstract String getEditorWorkspaceDescription(boolean create); /* -------------------------- Public Features ------------------------------ */ @Restrict("#{s:hasPermission('User', 'isAdmin', currentUser)}") public void selectOwner(Long creatorId) { User newCreator = userDAO.findUser(creatorId); getInstance().setCreatedBy(newCreator); } private Role.AccessLevel writeAccessLevel; private Role.AccessLevel readAccessLevel; public Role.AccessLevel getWriteAccessLevel() { return writeAccessLevel; } public void setWriteAccessLevel(Role.AccessLevel writeAccessLevel) { if (!Identity.instance().hasPermission("Node", "changeAccessLevel", getInstance()) ) { throw new AuthorizationException("You don't have permission for this operation"); } this.writeAccessLevel = writeAccessLevel; getInstance().setWriteAccessLevel( writeAccessLevel != null ? writeAccessLevel.getAccessLevel() : Role.ADMINROLE_ACCESSLEVEL ); } public Role.AccessLevel getReadAccessLevel() { return readAccessLevel; } public void setReadAccessLevel(Role.AccessLevel readAccessLevel) { if (!Identity.instance().hasPermission("Node", "changeAccessLevel", getInstance()) ) { throw new AuthorizationException("You don't have permission for this operation"); } this.readAccessLevel = readAccessLevel; getInstance().setReadAccessLevel( readAccessLevel != null ? readAccessLevel.getAccessLevel() : Role.ADMINROLE_ACCESSLEVEL ); } }