/*
* 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;
}
}