/* * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.impl.dataflow; import org.exoplatform.services.jcr.access.AccessControlEntry; import org.exoplatform.services.jcr.access.AccessControlList; import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.dataflow.ItemDataTraversingVisitor; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.dataflow.persistent.PersistedPropertyData; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.datamodel.ValueData; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.SessionDataManager; import org.exoplatform.services.jcr.impl.dataflow.persistent.ReadOnlyChangedSizeHandler; import org.exoplatform.services.jcr.util.IdGenerator; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Stack; import javax.jcr.RepositoryException; /** * The class visits each node, all subnodes and all of them properties. It transfer as parameter of * a method <code>ItemData.visits()</code>. During visiting the class forms the <b>itemAddStates</b> * list of <code>List<ItemState></code> for copying new nodes and their properties and * <b>itemDeletedStates</b> for deleting existing nodes and properties. * * @version $Id$ */ public class ItemDataMoveVisitor extends ItemDataTraversingVisitor { /** * The list of added item states */ protected List<ItemState> deleteStates = new ArrayList<ItemState>(); /** * Destination node name */ private InternalQName destNodeName; /** * The stack. In the top it contains a parent node. */ protected Stack<NodeData> parents; /** * Contains instance of source parent */ protected NodeData srcParent; /** * The list of added item states */ protected List<ItemState> addStates = new ArrayList<ItemState>(); /** * The moved item state. */ protected ItemState moveState ; /** * The variable shows necessity of preservation <code>Identifier</code>, not generate new one, at * transformation of <code>Item</code>. */ protected boolean keepIdentifiers; /** * The NodeTypeManager */ protected NodeTypeDataManager ntManager; protected QPath ancestorToSave; /** * Trigger events for descendants. */ protected Boolean triggerEventsForDescendants; private int maxDescendantNodesAllowed; private int totalVisitedNodes; private int sizeOfAddStatesAfterRootLevel; private int sizeOfDeleteStatesAfterRootLevel; private ItemState pathChangedState; /** * Creates an instance of this class. * * @param parent * - The parent node * @param dstNodeName * Destination node name * @param nodeTypeManager * - The NodeTypeManager * @param srcDataManager * - Source data manager * @param keepIdentifiers * - Is it necessity to keep <code>Identifiers</code> * @param triggerEventsForDescendants * - Trigger events for descendants. * @param maxDescendantNodesAllowed * - The maximum amount of descendant nodes allowed before considering that triggering event * for descendants is too costly, it will be then automatically disabled */ public ItemDataMoveVisitor(NodeData parent, InternalQName dstNodeName, NodeData srcParent, NodeTypeDataManager nodeTypeManager, SessionDataManager srcDataManager, boolean keepIdentifiers, Boolean triggerEventsForDescendants, int maxDescendantNodesAllowed) { super(srcDataManager, triggerEventsForDescendants == null || triggerEventsForDescendants.booleanValue() ? INFINITE_DEPTH : 0); this.keepIdentifiers = keepIdentifiers; this.ntManager = nodeTypeManager; this.destNodeName = dstNodeName; this.parents = new Stack<NodeData>(); this.parents.add(parent); this.srcParent = srcParent; this.triggerEventsForDescendants = triggerEventsForDescendants; this.maxDescendantNodesAllowed = maxDescendantNodesAllowed; } @Override protected void entering(NodeData node, int level) throws RepositoryException { if (ancestorToSave == null) { ancestorToSave = QPath.getCommonAncestorPath(curParent().getQPath(), node.getQPath()); } NodeData destParent = curParent(); int destIndex; // index for path int destOrderNum; // order number InternalQName qname; if (level == 0) { qname = destNodeName; List<NodeData> destChilds = dataManager.getChildNodesData(destParent); List<NodeData> srcChilds; destIndex = 1; // If ordering is supported by the node // type of the parent node of the new location, then the // newly moved node is appended to the end of the child // node list. destOrderNum = 0; for (NodeData child : destChilds) { if (child.getOrderNumber() + 1 > destOrderNum) { destOrderNum = child.getOrderNumber() + 1; } } if (destParent == srcParent) // NOSONAR { // move to same parent srcChilds = destChilds; } else { // move to another parent srcChilds = dataManager.getChildNodesData(srcParent); // find index on destination for (NodeData dchild : destChilds) { if (dchild.getQPath().getName().equals(destNodeName)) { destIndex++; } } } int srcIndex = 1; // Fix SNS on source for (int i = 0; i < srcChilds.size(); i++) { NodeData child = srcChilds.get(i); if (!child.getIdentifier().equals(node.getIdentifier())) { if ((child.getQPath().getName()).getAsString().equals((node.getQPath().getName()).getAsString())) { int persistedVersion = child.getPersistedVersion(); if (srcIndex == node.getQPath().getIndex() && persistedVersion == node.getPersistedVersion() - 1) { // applying update state it is possible to get unique constraint violation for // index JCR_IDX_IWS_PARENT, thats why try to have different persisted version of SNS nodes persistedVersion++; } QPath siblingPath = QPath.makeChildPath(srcParent.getQPath(), child.getQPath().getName(), srcIndex); TransientNodeData sibling = new TransientNodeData(siblingPath, child.getIdentifier(), persistedVersion, child.getPrimaryTypeName(), child.getMixinTypeNames(), child.getOrderNumber(), child.getParentIdentifier(), child.getACL()); addStates.add(new ItemState(sibling, ItemState.UPDATED, true, ancestorToSave, false, true)); srcIndex++; } // find index on destination in case when destination the same as source if (srcChilds == destChilds && (child.getQPath().getName().equals(destNodeName))) // NOSONAR { destIndex++; } } } } else { qname = node.getQPath().getName(); destIndex = node.getQPath().getIndex(); destOrderNum = node.getOrderNumber(); } String id = keepIdentifiers ? node.getIdentifier() : IdGenerator.generate(); QPath qpath = QPath.makeChildPath(destParent.getQPath(), qname, destIndex); AccessControlList acl = destParent.getACL(); boolean isPrivilegeable = ntManager.isNodeType(Constants.EXO_PRIVILEGEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames()); boolean isOwneable = ntManager.isNodeType(Constants.EXO_OWNEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames()); if (isPrivilegeable || isOwneable) { List<AccessControlEntry> permissionEntries = new ArrayList<AccessControlEntry>(); permissionEntries.addAll((isPrivilegeable ? node.getACL() : destParent.getACL()).getPermissionEntries()); String owner = isOwneable ? node.getACL().getOwner() : destParent.getACL().getOwner(); acl = new AccessControlList(owner, permissionEntries); } TransientNodeData newNode = new TransientNodeData(qpath, id, -1, node.getPrimaryTypeName(), node.getMixinTypeNames(), destOrderNum, destParent.getIdentifier(), acl); parents.push(newNode); if (level == 0) { moveState=ItemState.createMovedState(newNode,node.getQPath()); addStates.add(moveState); } // ancestorToSave is a parent node // if level == 0 set internal create as false for validating on save addStates.add(new ItemState(newNode, ItemState.RENAMED, level == 0, ancestorToSave, false, level == 0)); deleteStates.add(new ItemState(node, ItemState.DELETED, level == 0, ancestorToSave, false, false)); if (triggerEventsForDescendants == null) { // In case we did not set an explicit value for the parameter triggerEventsForDescendants if (level == 0) { // We save the size to be able to roll back to this state later if needed sizeOfAddStatesAfterRootLevel = addStates.size(); sizeOfDeleteStatesAfterRootLevel = deleteStates.size(); pathChangedState = new ItemState(newNode, ItemState.PATH_CHANGED, false, ancestorToSave, false, false, node .getQPath()); } if (!isInterrupted() && ++totalVisitedNodes > maxDescendantNodesAllowed) { // Interrupt the visit process this.interrupted = true; // Go back to the previous state for (int i = addStates.size() - 1; i >= sizeOfAddStatesAfterRootLevel; i--) { addStates.remove(i); } for (int i = deleteStates.size() - 1; i >= sizeOfDeleteStatesAfterRootLevel; i--) { deleteStates.remove(i); } addStates.add(0, pathChangedState); } } else if (!triggerEventsForDescendants.booleanValue()) { addStates.add(0, new ItemState(newNode, ItemState.PATH_CHANGED, false, ancestorToSave, false, false, node .getQPath())); } } /** * {@inheritDoc} */ @Override protected void entering(PropertyData property, int level) throws RepositoryException { InternalQName qname = property.getQPath().getName(); List<ValueData> values; if (ntManager.isNodeType(Constants.MIX_REFERENCEABLE, curParent().getPrimaryTypeName(), curParent() .getMixinTypeNames()) && qname.equals(Constants.JCR_UUID)) { values = new ArrayList<ValueData>(1); values.add(new TransientValueData(curParent().getIdentifier())); } else { // we don't copy ValueDatas here as it's move (i.e. VS files will not be relocated) values = property.getValues(); } PropertyData newProperty = new TransientPropertyData(QPath.makeChildPath(curParent().getQPath(), qname), keepIdentifiers ? property.getIdentifier() : IdGenerator.generate(), -1, property.getType(), curParent().getIdentifier(), property.isMultiValued(), values); createStates(property, newProperty); } /** * Creates item states and adds them to changes log. If possible tries * to inject {@link ChangedSizeHandler} to manage data size changes. * * @param prevProperty * {@link PropertyData} currently exists into storage. * @param newProperty * {@link PropertyData} will be saved to the storage */ private void createStates(PropertyData prevProperty, PropertyData newProperty) { ReadOnlyChangedSizeHandler delChangedSizeHandler = null; ReadOnlyChangedSizeHandler addChangedSizeHandler = null; if (prevProperty instanceof PersistedPropertyData) { PersistedPropertyData persistedPrevProp = (PersistedPropertyData)prevProperty; delChangedSizeHandler = new ReadOnlyChangedSizeHandler(0, persistedPrevProp.getPersistedSize()); addChangedSizeHandler = new ReadOnlyChangedSizeHandler(persistedPrevProp.getPersistedSize(), 0); } addStates.add(new ItemState(newProperty, ItemState.RENAMED, false, ancestorToSave, false, false, null, addChangedSizeHandler)); deleteStates.add(new ItemState(prevProperty, ItemState.DELETED, false, ancestorToSave, false, false, null, delChangedSizeHandler)); } public List<ItemState> getAllStates() { List<ItemState> list = getItemDeletedStates(true); list.addAll(getItemAddStates()); return list; } /** * Returns the list of item deleted states */ public List<ItemState> getItemDeletedStates(boolean isInverse) { if (isInverse) { Collections.reverse(deleteStates); } return deleteStates; } /** * Returns the current parent node */ protected NodeData curParent() { return parents.peek(); } @Override protected void leaving(PropertyData property, int level) throws RepositoryException { } @Override protected void leaving(NodeData node, int level) throws RepositoryException { parents.pop(); } /** * Returns the list of item add states */ public List<ItemState> getItemAddStates() { return addStates; } /** * Returns the item move state */ public ItemState getItemMoveState() { return moveState; } }