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