/* * 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.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.ItemData; 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.impl.Constants; import org.exoplatform.services.jcr.impl.core.ItemImpl; import org.exoplatform.services.jcr.impl.core.SessionDataManager; import org.exoplatform.services.jcr.impl.dataflow.session.SessionChangesLog; import org.exoplatform.services.jcr.impl.storage.JCRItemExistsException; import java.util.ArrayList; import java.util.Collections; import java.util.List; 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 clone new nodes and their properties and * <b>ItemDeletedExistingStates</b> list for remove existing nodes if <code>removeExisting</code> is * true. * * @version $Id$ */ public class ItemDataCloneVisitor extends DefaultItemDataCopyVisitor { /** * The list of deleted existing item states */ protected List<ItemState> itemDeletedExistingStates = new ArrayList<ItemState>(); private boolean removeExisting; private boolean deletedExistingPropery = false; private final SessionChangesLog changes; /** * 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 dstDataManager * - Destination data manager * @param removeExisting * - If <code>removeExisting</code> is true and an existing node in this workspace (the * destination workspace) has the same <code>UUID</code> as a node being cloned from * srcWorkspace, then the incoming node takes precedence, and the existing node (and its * subtree) is removed. If <code>removeExisting</code> is false then a <code>UUID</code> * collision causes this method to throw a <b>ItemExistsException</b> and no changes are * made. */ public ItemDataCloneVisitor(NodeData parent, InternalQName dstNodeName, NodeTypeDataManager nodeTypeManager, SessionDataManager srcDataManager, SessionDataManager dstDataManager, boolean removeExisting, SessionChangesLog changes) { super(parent, dstNodeName, nodeTypeManager, srcDataManager, dstDataManager, false); this.removeExisting = removeExisting; this.changes = changes; } /** * Returns the list of item delete existing states */ public List<ItemState> getItemDeletedExistingStates(boolean isInverse) { if (isInverse) { Collections.reverse(itemDeletedExistingStates); } return itemDeletedExistingStates; } protected int calculateNewNodeOrderNumber() throws RepositoryException { NodeData parent = curParent(); List<NodeData> existedChilds = getMargedChildNodesData(parent); int orderNum = 0; if (existedChilds.size() > 0) { orderNum = existedChilds.get(existedChilds.size() - 1).getOrderNumber() + 1; } return orderNum; } @Override protected QPath calculateNewNodePath(NodeData node, int level) throws RepositoryException { NodeData parent = curParent(); InternalQName qname = null; List<NodeData> existedChilds = getMargedChildNodesData(parent); int newIndex = 1; if (level == 0) { qname = destNodeName; // Calculate SNS index for dest root for (NodeData child : existedChilds) { if (child.getQPath().getName().equals(qname)) { newIndex++; // next sibling index } } } else { qname = node.getQPath().getName(); newIndex = node.getQPath().getIndex(); } return QPath.makeChildPath(parent.getQPath(), qname, newIndex); } @Override protected void entering(NodeData node, int level) throws RepositoryException { boolean isMixReferenceable = ntManager.isNodeType(Constants.MIX_REFERENCEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames()); deletedExistingPropery = false; if (isMixReferenceable) { String identifier = node.getIdentifier(); ItemImpl relItem = dstDataManager.getItemByIdentifier(identifier, false); ItemState changesItemState = null; if (changes != null) { changesItemState = changes.getItemState(identifier); } if (relItem != null && !(changesItemState != null && changesItemState.isDeleted())) { if (removeExisting) { deletedExistingPropery = true; itemDeletedExistingStates.add(new ItemState(relItem.getData(), ItemState.DELETED, true, dstDataManager .getItemByIdentifier(relItem.getParentIdentifier(), false).getInternalPath(), level != 0)); } else { throw new JCRItemExistsException("Item exists id = " + identifier + " name " + relItem.getName(), identifier); } } keepIdentifiers = true; } super.entering(node, level); keepIdentifiers = false; } @Override protected void entering(PropertyData property, int level) throws RepositoryException { if (deletedExistingPropery && removeExisting) { // if parent of this property in destination must be deleted, property // must be deleted too. if (itemInItemStateList(itemDeletedExistingStates, property.getParentIdentifier(), ItemState.DELETED)) { // search destination propery ItemData dstParentNodeData = dstDataManager.getItemByIdentifier(property.getParentIdentifier(), false).getData(); List<PropertyData> dstChildProperties = dstDataManager.getChildPropertiesData((NodeData)dstParentNodeData); PropertyData dstProperty = null; for (PropertyData propertyData : dstChildProperties) { if (propertyData.getQPath().getName().equals(property.getQPath().getName())) { dstProperty = propertyData; break; } } if (dstProperty != null) { itemDeletedExistingStates.add(new ItemState(dstProperty, ItemState.DELETED, true, dstDataManager .getItemByIdentifier(dstProperty.getParentIdentifier(), false).getInternalPath(), level != 0)); } else { throw new RepositoryException("Destination propery " + property.getQPath().getAsString() + " not found. "); } } } super.entering(property, level); }; /** * Return true if the itemstate for item with <code>itemId</code> UUId exist in * <code>List<ItemState></code> list. * * @param list * @param itemId * @param state * @return */ private boolean itemInItemStateList(List<ItemState> list, String itemId, int state) { boolean retval = false; for (ItemState itemState : list) { if (itemState.getState() == state && itemState.getData().getIdentifier().equals(itemId)) { retval = true; break; } } return retval; } private boolean isItemDeleted(ItemData item) { if (itemInItemStateList(itemDeletedExistingStates, item.getIdentifier(), ItemState.DELETED)) return true; ItemState changesItemState = null; if (changes != null) { changesItemState = changes.getItemState(item.getIdentifier()); if (changesItemState != null && changesItemState.isDeleted()) return true; } return false; } private List<NodeData> getMargedChildNodesData(NodeData parent) throws RepositoryException { List<NodeData> result = new ArrayList<NodeData>(); List<NodeData> existedChilds = dstDataManager.getChildNodesData(parent); for (NodeData nodeData : existedChilds) { if (!isItemDeleted(nodeData)) { result.add(nodeData); } } return result; } }