Java tutorial
/*************************************************************** * This file is part of the [fleXive](R) framework. * * Copyright (c) 1999-2014 * UCS - unique computing solutions gmbh (http://www.ucs.at) * All rights reserved * * The [fleXive](R) project is free software; you can redistribute * it and/or modify it under the terms of the GNU Lesser General Public * License version 2.1 or higher as published by the Free Software Foundation. * * The GNU Lesser General Public License can be found at * http://www.gnu.org/licenses/lgpl.html. * A copy is found in the textfile LGPL.txt and important notices to the * license from the author are found in LICENSE.txt distributed with * these libraries. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For further information about UCS - unique computing solutions gmbh, * please see the company website: http://www.ucs.at * * For further information about [fleXive](R), please see the * project website: http://www.flexive.org * * * This copyright notice MUST APPEAR in all copies of the file! ***************************************************************/ package com.flexive.cmis.spi; import com.flexive.shared.EJBLookup; import com.flexive.shared.FxSharedUtils; import com.flexive.shared.configuration.SystemParameters; import com.flexive.shared.content.FxContent; import com.flexive.shared.content.FxPK; import com.flexive.shared.exceptions.FxApplicationException; import com.flexive.shared.interfaces.TreeEngine; import com.flexive.shared.security.LifeCycleInfo; import com.flexive.shared.structure.FxEnvironment; import com.flexive.shared.structure.FxPropertyAssignment; import com.flexive.shared.structure.FxType; import com.flexive.shared.tree.FxTreeMode; import com.flexive.shared.tree.FxTreeNode; import com.flexive.shared.tree.FxTreeNodeEdit; import com.flexive.shared.tree.FxTreeRemoveOp; import com.flexive.shared.value.FxString; import com.google.common.collect.Lists; import org.apache.chemistry.*; import org.apache.chemistry.impl.simple.SimpleTree; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.xml.namespace.QName; import java.io.IOException; import java.io.Serializable; import java.util.*; import static com.flexive.shared.CacheAdmin.getEnvironment; import static com.flexive.shared.EJBLookup.getTreeEngine; import static com.google.common.collect.Lists.newArrayList; /** * A folder based on a FxTreeNode and the backing FxContent instance, which must be derived from flexive's * FOLDER type. * * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at) * @version $Rev$ */ public class FlexiveFolder extends FlexiveObjectEntry implements Folder { private static final Log LOG = LogFactory.getLog(FlexiveFolder.class); private static final String ROOT_FOLDER_NAME = ""; // from chemistry testcases private final long nodeId; private final FxTreeMode treeMode; private FxTreeNodeEdit _cachedNode; private FxContent _cachedContent; // cached content instance, use getContent() private List<CMISObject> toAdd; // objects to be filed in the next save operation private List<CMISObject> toRemove; // objects to be unfiled in the next save operation /** * Create a new folder representation of a tree node. * * @param context the connection context * @param node the tree node */ FlexiveFolder(FlexiveConnection.Context context, FxTreeNode node) { super(context); FxSharedUtils.checkParameterNull(node, "node"); this.nodeId = node.getId(); this.treeMode = node.getMode(); this._cachedNode = node.asEditable(); } /** * Create a new folder based on a node ID. The node will not be loaded immediately, but only * when node information is required for fulfilling a request. * * @param context the connection context * @param nodeId the tree node ID */ FlexiveFolder(FlexiveConnection.Context context, long nodeId) { this(context, FxTreeMode.Edit, nodeId); } /** * Create a new folder based on a node ID. The node will not be loaded immediately, but only * when node information is required for fulfilling a request. * * @param context the connection context * @param treeMode the tree mode * @param nodeId the tree node ID */ FlexiveFolder(FlexiveConnection.Context context, FxTreeMode treeMode, long nodeId) { super(context); if (nodeId == -1) { throw new IllegalArgumentException("Folder not found."); } this.nodeId = nodeId; this.treeMode = treeMode; } public FxTreeNodeEdit getNode() { if (_cachedNode == null) { try { _cachedNode = getTreeEngine().getNode(getTreeMode(), getNodeId()).asEditable(); } catch (FxApplicationException e) { throw e.asRuntimeException(); } } return _cachedNode; } /** * Indicate that the node should be refreshed for the next operation. */ protected void refreshNode() { _cachedNode = null; } protected FxTreeMode getTreeMode() { return treeMode; } protected long getNodeId() { return nodeId; } @Override protected long getFxTypeId() { return getNode().getReferenceTypeId(); } @Override protected LifeCycleInfo getLifeCycleInfo() { return getNode().getReferenceLifeCycleInfo(); } @Override protected void addCustomProperties(Map<String, Property> properties) { addVirtualProperty(properties, VirtualProperties.PARENT_ID, String.valueOf(getNode().getParentNodeId())); addVirtualProperty(properties, VirtualProperties.OBJECT_ID, String.valueOf(getNode().getId())); addVirtualProperty(properties, VirtualProperties.NAME, getName()); addVirtualProperty(properties, VirtualProperties.PATH, getNode().getPath()); } @Override public Serializable getValue(String name) { // override some common attributes that do not require a content access if (VirtualProperties.NAME.getId().equalsIgnoreCase(name)) { return getName(); } else { return super.getValue(name); } } @Override protected synchronized FxContent getContent() { if (_cachedContent == null) { try { _cachedContent = EJBLookup.getContentEngine().load(getNode().getReference()); } catch (FxApplicationException e) { throw e.asRuntimeException(); } } return _cachedContent; } @Override public String getId() { return String.valueOf(getNodeId()); } public synchronized void add(CMISObject object) { if (toAdd == null) { toAdd = Lists.newArrayList(); } toAdd.add(object); if (object instanceof FlexiveFolder && ((FlexiveFolder) object).getNode().getParentNodeId() == -1) { // set parent in newly created node // TODO: this doesn't work when this is a new folder, since the ID hasn't yet been set. ((FlexiveFolder) object).getNode().setParentNodeId(getNodeId()); } } public synchronized void remove(CMISObject object) { if (toRemove == null) { toRemove = Lists.newArrayList(); } toRemove.add(object); } public Collection<ObjectId> deleteTree(Unfiling unfiling) { try { if (unfiling == null) { unfiling = Unfiling.DELETE; // according to javadoc } switch (unfiling) { case DELETE: EJBLookup.getTreeEngine().remove(getNode(), FxTreeRemoveOp.Remove, true); break; case UNFILE: EJBLookup.getTreeEngine().remove(getNode(), FxTreeRemoveOp.Unfile, true); break; case DELETE_SINGLE_FILED: EJBLookup.getTreeEngine().remove(getNode(), FxTreeRemoveOp.RemoveSingleFiled, true); break; default: throw new IllegalArgumentException("Unkown argument: " + unfiling); } return new ArrayList<ObjectId>(0); } catch (FxApplicationException e) { throw e.asRuntimeException(); } } public List<Folder> getAncestors() { try { final List<Folder> result = newArrayList(); for (long pathNodeId : EJBLookup.getTreeEngine().getIdChain(getTreeMode(), getNodeId())) { if (pathNodeId != getNodeId()) { result.add(new FlexiveFolder(context, pathNodeId)); } } return result; } catch (FxApplicationException e) { throw e.asRuntimeException(); } } public List<CMISObject> getChildren() { return getChildren(null); } public List<CMISObject> getChildren(BaseType type) { return getChildren(type, 1); } public List<CMISObject> getChildren(BaseType type, int depth) { try { final FxTreeNode node = depth > 1 || getNode().getChildren().isEmpty() ? getTreeEngine().getTree(getNode().getMode(), getNodeId(), depth) : getNode(); final FxEnvironment environment = getEnvironment(); // create a set of all types derived from the folder root type final Set<Long> folderTypeIds = SPIUtils.getFolderTypeIds(); final List<CMISObject> result = newArrayList(); if (type == null) { for (FxTreeNode child : node) { addChild(result, folderTypeIds, child); } } else { switch (type) { case DOCUMENT: for (FxTreeNode child : node) { if (!folderTypeIds.contains(child.getReferenceTypeId())) { addChild(result, folderTypeIds, child); } } break; case FOLDER: for (FxTreeNode child : node) { if (folderTypeIds.contains(child.getReferenceTypeId())) { addChild(result, folderTypeIds, child); } } break; case RELATIONSHIP: for (FxTreeNode child : node) { if (environment.getType(child.getReferenceTypeId()).isRelation()) { addChild(result, folderTypeIds, child); } } break; case POLICY: throw new IllegalArgumentException("Policies cannot be filed in the tree."); } } return result; } catch (FxApplicationException e) { throw e.asRuntimeException(); } } protected Tree<ObjectEntry> getFolderTree(int depth) { return getTree(depth, false, true); } protected Tree<ObjectEntry> getDescendantTree(int depth) { return getTree(depth, true, true); } private Tree<ObjectEntry> getTree(int depth, boolean includeDocuments, boolean includeFolders) { try { return convertNodeTree(EJBLookup.getTreeEngine().getTree(FxTreeMode.Edit, nodeId, depth), includeDocuments, includeFolders); } catch (FxApplicationException e) { throw e.asRuntimeException(); } } private Tree<ObjectEntry> convertNodeTree(FxTreeNode treeNode, boolean includeDocuments, boolean includeFolders) { final Set<Long> folderTypeIds = SPIUtils.getFolderTypeIds(); if (SPIUtils.treatDocumentAsFolder(folderTypeIds, context, treeNode)) { // process direct folder children final List<Tree<ObjectEntry>> children = Lists.newArrayList(); for (FxTreeNode child : treeNode.getChildren()) { // check if child should be included if (includeDocuments && !folderTypeIds.contains(child.getReferenceTypeId()) || includeFolders && folderTypeIds.contains(child.getReferenceTypeId())) { // include subtree for child node final Tree<ObjectEntry> childTree = convertNodeTree(child, includeDocuments, includeFolders); if (childTree != null) { children.add(childTree); } } } // create tree return new SimpleTree<ObjectEntry>(new FlexiveFolder(context, treeNode), children); } else if (includeDocuments) { return new SimpleTree<ObjectEntry>(new FlexiveDocument(context, treeNode, treeNode.getParentNodeId()), Lists.<Tree<ObjectEntry>>newArrayListWithCapacity(0)); } else { return null; } } private void addChild(List<CMISObject> result, Set<Long> folderTypeIds, FxTreeNode child) { if (child.getId() != getNodeId()) { result.add(SPIUtils.treatDocumentAsFolder(folderTypeIds, context, child) ? new FlexiveFolder(context, child) : new FlexiveDocument(context, child, getNode())); } } public Document newDocument(String typeId) { return new FlexiveNewDocument(context, typeId, this); } public Folder newFolder(String typeId) { return new FlexiveNewFolder(context, typeId, this); } public void move(Folder targetFolder, Folder sourceFolder) { try { // check if the name is available in the target folder checkUniqueName(getNodeWithChildren(((FlexiveFolder) targetFolder).getNode()), getName()); // perform move getTreeEngine().move(getTreeMode(), getNodeId(), SPIUtils.getNodeId(targetFolder.getId()), -1); // perform update if (_cachedNode != null) { _cachedNode.setParentNodeId(SPIUtils.getNodeId(targetFolder.getId())); } } catch (FxApplicationException e) { throw e.asRuntimeException(); } } public void delete() { if (!getChildren().isEmpty()) { throw new ConstraintViolationException("Folder not empty."); } try { getTreeEngine().remove(getTreeMode(), getNodeId(), FxTreeRemoveOp.RemoveSingleFiled, true); } catch (FxApplicationException e) { throw e.asRuntimeException(); } } public void unfile() { throw new UnsupportedOperationException(); } public FlexiveFolder getParent() { return (getNode().getParentNodeId() > 0) ? new FlexiveFolder(context, getTreeMode(), getNode().getParentNodeId()) : null; } public Collection<Folder> getParents() { try { final List<Folder> result = newArrayList(); for (long id : getTreeEngine().getIdChain(getTreeMode(), getNodeId())) { if (id != getNodeId()) { result.add(new FlexiveFolder(context, getTreeMode(), id)); } } return result; } catch (FxApplicationException e) { throw e.asRuntimeException(); } } public List<Relationship> getRelationships(RelationshipDirection direction, String typeId, boolean includeSubRelationshipTypes) { throw new UnsupportedOperationException(); } public void applyPolicy(Policy policy) { throw new UnsupportedOperationException(); } public void removePolicy(Policy policy) { throw new UnsupportedOperationException(); } public Collection<Policy> getPolicies() { throw new UnsupportedOperationException(); } public Type getType() { return new FlexiveType(getEnvironment().getType(isRootNode() ? FxType.ROOT_ID // tests assume that the root node uses the root type : getNode().getReferenceTypeId())); } @Override public String getTypeId() { return getType().getId(); } @Override public String getBaseTypeId() { return BaseType.FOLDER.getId(); } public Property getProperty(String name) { return getProperties().get(name); } public synchronized void save() { try { getNode().setId(getTreeEngine().save(getNode())); getContent().save(); processAdd(); processRemove(); } catch (FxApplicationException e) { throw e.asRuntimeException(); } } protected void processRemove() throws FxApplicationException { if (toRemove == null) { return; } for (CMISObject object : toRemove) { final FxTreeNode removeNode = getTreeEngine().findChild(getTreeMode(), getNodeId(), FxPK.fromString(object.getId())); getTreeEngine().remove(getTreeMode(), removeNode.getId(), FxTreeRemoveOp.RemoveSingleFiled, false); } toRemove.clear(); } protected void processAdd() throws FxApplicationException { if (toAdd == null) { return; } final FxTreeNode node = getNodeWithChildren(getNode()); for (CMISObject object : toAdd) { checkUniqueName(node, object.getName()); getTreeEngine().save(FxTreeNodeEdit.createNew(StringUtils.defaultString(object.getName(), "")) .setParentNodeId(getNodeId()).setReference(SPIUtils.getDocumentId(object.getId()))); } toAdd.clear(); } @Override public void setName(String name) { if (!getNode().isNew() && getParent() != null) { checkUniqueName(getNodeWithChildren(getParent().getNode()), name); } // set as node name getNode().setName(name); getNode().setLabel(new FxString(false, name)); // also store in all caption properties of the folder instance (usually there is only one, CAPTION) final List<FxPropertyAssignment> captionAssignments; try { captionAssignments = getFxType().getAssignmentsForProperty( EJBLookup.getConfigurationEngine().get(SystemParameters.TREE_CAPTION_PROPERTY)); } catch (FxApplicationException e) { throw e.asRuntimeException(); } if (!captionAssignments.isEmpty()) { for (FxPropertyAssignment assignment : captionAssignments) { getContent().setValue("/" + assignment.getAlias(), name); } } } @Override public String getName() { return isRootNode() ? ROOT_FOLDER_NAME : getNode().getLabel().toString().trim(); } public ContentStream getContentStream(String contentStreamId) throws IOException { return null; // no content streams for folders } protected boolean isRootNode() { return getNodeId() == FxTreeNode.ROOT_NODE; } @Override public Set<QName> getAllowableActions() { final Set<QName> actions = super.getAllowableActions(); // currently any user can add objects to a folder (as long as he can create the objects) actions.add(AllowableAction.CAN_ADD_OBJECT_TO_FOLDER); actions.add(AllowableAction.CAN_CREATE_DOCUMENT); actions.add(AllowableAction.CAN_CREATE_FOLDER); return actions; } public String getPathSegment() { return getName(); } /** * Proprietary copy method. * * @param targetFolder the target folder * @param includeContents whether the folder content should be copied too * @return the new folder instance */ public FlexiveFolder copyTo(FlexiveFolder targetFolder, String newName, boolean includeContents) { try { if (includeContents) { final TreeEngine treeEngine = EJBLookup.getTreeEngine(); final long newFolderId = treeEngine.copy(getTreeMode(), getNodeId(), targetFolder.getNodeId(), -1, true); if (newName != null && !newName.equals(getName())) { // set new name on copied node final FxTreeNodeEdit newNode = treeEngine.getNode(getTreeMode(), newFolderId).asEditable(); newNode.setName(newName); newNode.setLabel(new FxString(true, newName)); treeEngine.save(newNode); } return new FlexiveFolder(context, newFolderId); } else { final FlexiveFolder newFolder = (FlexiveFolder) targetFolder.newFolder(getTypeId()); newFolder.setName(newName); newFolder.save(); return new FlexiveFolder(context, newFolder.getNodeId()); } } catch (FxApplicationException e) { throw e.asRuntimeException(); } } private void copyNodes(long targetNodeId, List<FxTreeNode> nodes) { // TODO: should be performed by tree engine (FX-702) for (FxTreeNode node : nodes) { try { final FxContent refCopy = EJBLookup.getContentEngine().load(node.getReference()) .copyAsNewInstance(); final FxPK newRefPK = EJBLookup.getContentEngine().save(refCopy); final long newNodeId = getTreeEngine().save(FxTreeNodeEdit.createNew(node.getName()) .setParentNodeId(targetNodeId).setReference(newRefPK)); if (node.getDirectChildCount() > 0) { // copy children recursively copyNodes(newNodeId, node.getChildren()); } } catch (FxApplicationException e) { if (LOG.isWarnEnabled()) { LOG.warn("Failed to copy node " + node.getId() + ": " + e.getMessage(), e); } } } } @Override public String toString() { return "FlexiveFolder[id=" + (getNode().isNew() ? -1 : getNodeId()) + ", name='" + getNode().getName() + "']"; } }