/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.core;
import javax.jcr.AccessDeniedException;
import javax.jcr.ItemExistsException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.nodetype.ConstraintViolationException;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
import org.apache.jackrabbit.core.security.AccessManager;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.core.session.SessionContext;
import org.apache.jackrabbit.core.session.SessionWriteOperation;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.conversion.NameException;
import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SessionMoveOperation implements SessionWriteOperation<Object> {
private final Logger log =
LoggerFactory.getLogger(SessionMoveOperation.class);
private final String srcAbsPath;
private final Path srcPath;
private final String destAbsPath;
private final Path destPath;
public SessionMoveOperation(
PathResolver resolver, String srcAbsPath, String destAbsPath)
throws RepositoryException {
this.srcAbsPath = srcAbsPath;
this.srcPath = getAbsolutePath(resolver, srcAbsPath);
this.destAbsPath = destAbsPath;
this.destPath = getAbsolutePath(resolver, destAbsPath);
if (destPath.getIndex() != Path.INDEX_UNDEFINED) {
// subscript in name element
String msg = destAbsPath + ": invalid destination path (subscript in name element is not allowed)";
log.debug(msg);
throw new RepositoryException(msg);
}
if (srcPath.isAncestorOf(destPath)) {
throw new RepositoryException(
"Destination path " + destAbsPath
+ " cannot be descendant of source path " + srcAbsPath
+ " in a move operation.");
}
}
private Path getAbsolutePath(PathResolver resolver, String path)
throws RepositoryException {
try {
Path qpath = resolver.getQPath(path).getNormalizedPath();
if (!qpath.isAbsolute()) {
throw new RepositoryException("Path is not absolute: " + path);
}
return qpath;
} catch (NameException e) {
throw new RepositoryException("Path is invalid: " + path, e);
}
}
private NodeImpl getNode(
SessionContext context, Path path, String absPath)
throws RepositoryException {
try {
return context.getItemManager().getNode(path);
} catch (AccessDeniedException e) {
throw new PathNotFoundException("Path not found: " + absPath);
}
}
public Object perform(SessionContext context) throws RepositoryException {
// Get node instances
NodeImpl targetNode = getNode(context, srcPath, srcAbsPath);
NodeImpl srcParentNode =
getNode(context, srcPath.getAncestor(1), srcAbsPath);
NodeImpl destParentNode =
getNode(context, destPath.getAncestor(1), destAbsPath);
if (context.getHierarchyManager().isShareAncestor(
targetNode.getNodeId(), destParentNode.getNodeId())) {
throw new RepositoryException(
"Move not possible because of a share cycle between "
+ srcAbsPath + " and " + destAbsPath);
}
// check for name collisions
NodeImpl existing = null;
try {
existing = context.getItemManager().getNode(destPath);
// there's already a node with that name:
// check same-name sibling setting of existing node
if (!existing.getDefinition().allowsSameNameSiblings()) {
throw new ItemExistsException(
"Same name siblings are not allowed: " + existing);
}
} catch (AccessDeniedException ade) {
// FIXME by throwing ItemExistsException we're disclosing too much information
throw new ItemExistsException(destAbsPath);
} catch (PathNotFoundException pnfe) {
// no name collision, fall through
}
// verify that the targetNode can be removed
int options = ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION;
context.getItemValidator().checkRemove(targetNode, options, Permission.NONE);
// verify for both source and destination parent nodes that
// - they are checked-out
// - are not protected neither by node type constraints nor by retention/hold
options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK |
ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION;
context.getItemValidator().checkModify(srcParentNode, options, Permission.NONE);
context.getItemValidator().checkModify(destParentNode, options, Permission.NONE);
// check constraints
// get applicable definition of target node at new location
NodeTypeImpl nt = (NodeTypeImpl) targetNode.getPrimaryNodeType();
org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef;
try {
newTargetDef = destParentNode.getApplicableChildNodeDefinition(destPath.getName(), nt.getQName());
} catch (RepositoryException re) {
String msg = destAbsPath + ": no definition found in parent node's node type for new node";
log.debug(msg);
throw new ConstraintViolationException(msg, re);
}
// if there's already a node with that name also check same-name sibling
// setting of new node; just checking same-name sibling setting on
// existing node is not sufficient since same-name sibling nodes don't
// necessarily have identical definitions
if (existing != null && !newTargetDef.allowsSameNameSiblings()) {
throw new ItemExistsException(
"Same name siblings not allowed: " + existing);
}
NodeId targetId = targetNode.getNodeId();
// check permissions
AccessManager acMgr = context.getAccessManager();
if (!(acMgr.isGranted(srcPath, Permission.REMOVE_NODE) &&
acMgr.isGranted(destPath, Permission.ADD_NODE | Permission.NODE_TYPE_MNGMT))) {
String msg = "Not allowed to move node " + srcAbsPath + " to " + destAbsPath;
log.debug(msg);
throw new AccessDeniedException(msg);
}
if (srcParentNode.isSame(destParentNode)) {
// change definition of target
targetNode.onRedefine(newTargetDef.unwrap());
// do rename
destParentNode.renameChildNode(targetId, destPath.getName(), false);
} else {
// check shareable case
if (targetNode.getNodeState().isShareable()) {
String msg = "Moving a shareable node is not supported.";
log.debug(msg);
throw new UnsupportedRepositoryOperationException(msg);
}
// change definition of target
targetNode.onRedefine(newTargetDef.unwrap());
// Get the transient states
NodeState srcParentState =
(NodeState) srcParentNode.getOrCreateTransientItemState();
NodeState targetState =
(NodeState) targetNode.getOrCreateTransientItemState();
NodeState destParentState =
(NodeState) destParentNode.getOrCreateTransientItemState();
// do move:
// 1. remove child node entry from old parent
if (srcParentState.removeChildNodeEntry(targetId)) {
// 2. re-parent target node
targetState.setParentId(destParentNode.getNodeId());
// 3. add child node entry to new parent
destParentState.addChildNodeEntry(destPath.getName(), targetId);
}
}
return this;
}
//--------------------------------------------------------------< Object >
/**
* Returns a string representation of this operation.
*/
public String toString() {
return "session.move(" + srcAbsPath + ", " + destAbsPath + ")";
}
}