/*
* 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.AccessControlList;
import org.exoplatform.services.jcr.access.AccessManager;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.dataflow.ItemDataConsumer;
import org.exoplatform.services.jcr.dataflow.ItemDataTraversingVisitor;
import org.exoplatform.services.jcr.dataflow.ItemState;
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.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.ConversationState;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.jcr.AccessDeniedException;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
/**
* Created by The eXo Platform SAS 15.12.2006
*
* @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
* @version $Id$
*/
public class ItemDataRemoveVisitor extends ItemDataTraversingVisitor
{
private static Log log = ExoLogger.getLogger("exo.jcr.component.core.ItemDataRemoveVisitor");
protected List<ItemState> itemRemovedStates = new ArrayList<ItemState>();
protected List<ItemState> reversedItemRemovedStates = null;
protected boolean validate;
protected NodeData removedRoot = null;
protected QPath ancestorToSave = null;
private final NodeTypeDataManager nodeTypeManager;
private final AccessManager accessManager;
private final ConversationState userState;
// a deletion without any validation
public ItemDataRemoveVisitor(ItemDataConsumer dataManager, QPath ancestorToSave)
{
this(dataManager, ancestorToSave, null, null, null);
this.validate = false;
}
public ItemDataRemoveVisitor(ItemDataConsumer dataManager, QPath ancestorToSave,
NodeTypeDataManager nodeTypeManager, AccessManager accessManager, ConversationState userState)
{
super(dataManager);
this.nodeTypeManager = nodeTypeManager;
this.accessManager = accessManager;
this.userState = userState;
this.validate = true;
this.ancestorToSave = ancestorToSave;
}
protected void validate(PropertyData property) throws RepositoryException
{
// 1. check AccessDeniedException
validateAccessDenied(property);
// 2. check ConstraintViolationException - PropertyDefinition for
// mandatory/protected flags
validateConstraints(property);
// 3. check VersionException
validateVersion(property);
// 4. check LockException
validateLock(property);
}
protected void validateAccessDenied(PropertyData property) throws RepositoryException
{
NodeData parent = (NodeData)dataManager.getItemData(property.getParentIdentifier());
if (!accessManager.hasPermission(parent.getACL(), PermissionType.READ, userState.getIdentity()))
{
throw new AccessDeniedException("Access denied " + property.getQPath().getAsString() + " for "
+ userState.getIdentity().getUserId() + " (get item parent by id)");
}
}
protected void validateConstraints(PropertyData property) throws RepositoryException
{
}
protected void validateVersion(PropertyData property) throws RepositoryException
{
}
protected void validateLock(PropertyData property) throws RepositoryException
{
}
protected void validate(NodeData node) throws RepositoryException
{
// 1. check AccessDeniedException
validateAccessDenied(node);
// 2. check ReferentialIntegrityException - REFERENCE property target
if (nodeTypeManager.isNodeType(Constants.MIX_REFERENCEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames()))
{
validateReferential(node);
}
// 3. check ConstraintViolationException - NodeDefinition for
// mandatory/protected flags
validateConstraints(node);
// 4. check VersionException
validateVersion(node);
// 5. check LockException
validateLock(node);
}
protected void validateAccessDenied(NodeData node) throws RepositoryException
{
if (!accessManager.hasPermission(node.getACL(), PermissionType.READ, userState.getIdentity()))
{
throw new AccessDeniedException("Access denied " + node.getQPath().getAsString() + " for "
+ userState.getIdentity().getUserId() + " (get item by id)");
}
}
protected void validateReferential(NodeData node) throws RepositoryException
{
List<PropertyData> refs = dataManager.getReferencesData(node.getIdentifier(), true);
// A ReferentialIntegrityException will be thrown on save if this item or an
// item in its subtree
// is currently the target of a REFERENCE property located in this workspace
// but outside
// this item's subtree and the current Session has read access to that
// REFERENCE property.
// An AccessDeniedException will be thrown on save if this item or an item
// in its subtree
// is currently the target of a REFERENCE property located in this workspace
// but outside
// this item's subtree and the current Session does not have read access to
// that REFERENCE property.
for (PropertyData rpd : refs)
{
if (isRemoveDescendant(removedRoot))
{
// on the tree(s), we have to remove REFERENCE property before the node
entering(rpd, currentLevel);
}
else
{
NodeData refParent = (NodeData)dataManager.getItemData(rpd.getParentIdentifier());
if (!accessManager.hasPermission(refParent.getACL(), PermissionType.READ, userState.getIdentity()))
{
throw new AccessDeniedException("Access denied " + rpd.getQPath().getAsString() + " for "
+ userState.getIdentity().getUserId() + " (get reference property parent by id)");
}
throw new ReferentialIntegrityException("This node " + node.getQPath().getAsString()
+ " is currently the target of a REFERENCE property " + rpd.getQPath().getAsString()
+ " located in this workspace. Session id: " + userState.getIdentity().getUserId());
}
}
}
protected boolean isRemoveDescendant(ItemData item) throws RepositoryException
{
return item.getQPath().isDescendantOf(removedRoot.getQPath());
}
protected void validateConstraints(NodeData node) throws RepositoryException
{
}
protected void validateVersion(NodeData node) throws RepositoryException
{
}
protected void validateLock(NodeData node) throws RepositoryException
{
}
@Override
protected void entering(PropertyData property, int level) throws RepositoryException
{
if (log.isDebugEnabled())
{
log.debug("Entering property " + property.getQPath().getAsString());
}
if (validate)
{
validate(property);
}
property = (PropertyData)copyItemDataDelete(property);
ItemState state =
new ItemState(property, ItemState.DELETED, true, ancestorToSave != null ? ancestorToSave
: removedRoot.getQPath());
if (!itemRemovedStates.contains(state))
{
itemRemovedStates.add(state);
}
else if (log.isDebugEnabled())
{
// REFERENCE props usecase, see validateReferential(NodeData)
log.debug("A property " + property.getQPath().getAsString() + " is already listed for remove");
}
}
@Override
protected void entering(NodeData node, int level) throws RepositoryException
{
if (log.isDebugEnabled())
{
log.debug("Entering node " + node.getQPath().getAsString());
}
// this node is not taken in account
if (level == 0)
{
removedRoot = node;
}
if (validate)
{
validate(node);
}
if (!(node instanceof TransientItemData))
{
node = (NodeData)copyItemDataDelete(node);
}
ItemState state =
new ItemState(node, ItemState.DELETED, true, ancestorToSave != null ? ancestorToSave : removedRoot.getQPath());
itemRemovedStates.add(state);
}
@Override
protected void leaving(PropertyData property, int level) throws RepositoryException
{
}
@Override
protected void leaving(NodeData node, int level) throws RepositoryException
{
}
public List<ItemState> getRemovedStates()
{
if (reversedItemRemovedStates == null)
{
Collections.reverse(itemRemovedStates);
reversedItemRemovedStates = itemRemovedStates;
}
return reversedItemRemovedStates;
}
/**
* Copy ItemData for Delete operation.
*
* @param item
* ItemData
* @return TransientItemData
* @throws RepositoryException
* if error occurs
*/
protected TransientItemData copyItemDataDelete(final ItemData item) throws RepositoryException
{
if (item == null)
{
return null;
}
// make a copy
if (item.isNode())
{
final NodeData node = (NodeData)item;
// the node ACL can't be are null as ACL manager does care about this
final AccessControlList acl = node.getACL();
if (acl == null)
{
throw new RepositoryException("Node ACL is null. " + node.getQPath().getAsString() + " "
+ node.getIdentifier());
}
return new TransientNodeData(node.getQPath(), node.getIdentifier(), node.getPersistedVersion(),
node.getPrimaryTypeName(), node.getMixinTypeNames(), node.getOrderNumber(), node.getParentIdentifier(), acl);
}
// else - property
final PropertyData prop = (PropertyData)item;
// make a copy, value wil be null for deleting items
TransientPropertyData newData =
new TransientPropertyData(prop.getQPath(), prop.getIdentifier(), prop.getPersistedVersion(), prop.getType(),
prop.getParentIdentifier(), prop.isMultiValued());
return newData;
}
}