All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate Maven / Gradle / Ivy

/*
 * 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.oak.jcr.delegate;

import static org.apache.jackrabbit.guava.common.base.MoreObjects.toStringHelper;
import static org.apache.jackrabbit.guava.common.collect.Iterables.addAll;
import static org.apache.jackrabbit.guava.common.collect.Iterables.contains;
import static org.apache.jackrabbit.guava.common.collect.Iterators.filter;
import static org.apache.jackrabbit.guava.common.collect.Iterators.transform;
import static org.apache.jackrabbit.guava.common.collect.Lists.newArrayList;
import static org.apache.jackrabbit.guava.common.collect.Sets.newLinkedHashSet;
import static org.apache.jackrabbit.JcrConstants.JCR_ISMIXIN;
import static org.apache.jackrabbit.JcrConstants.JCR_LOCKISDEEP;
import static org.apache.jackrabbit.JcrConstants.JCR_LOCKOWNER;
import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
import static org.apache.jackrabbit.JcrConstants.JCR_MULTIPLE;
import static org.apache.jackrabbit.JcrConstants.JCR_NODETYPENAME;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.JcrConstants.JCR_PROTECTED;
import static org.apache.jackrabbit.JcrConstants.JCR_REQUIREDTYPE;
import static org.apache.jackrabbit.JcrConstants.JCR_SAMENAMESIBLINGS;
import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
import static org.apache.jackrabbit.JcrConstants.MIX_LOCKABLE;
import static org.apache.jackrabbit.JcrConstants.NT_BASE;
import static org.apache.jackrabbit.oak.api.Type.BOOLEAN;
import static org.apache.jackrabbit.oak.api.Type.NAMES;
import static org.apache.jackrabbit.oak.api.Type.UNDEFINED;
import static org.apache.jackrabbit.oak.api.Type.UNDEFINEDS;
import static org.apache.jackrabbit.oak.commons.PathUtils.dropIndexFromName;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.JCR_IS_ABSTRACT;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.NODE_TYPES_PATH;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_HAS_PROTECTED_RESIDUAL_CHILD_NODES;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_HAS_PROTECTED_RESIDUAL_PROPERTIES;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_NAMED_CHILD_NODE_DEFINITIONS;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_NAMED_PROPERTY_DEFINITIONS;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_PROTECTED_CHILD_NODES;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_PROTECTED_PROPERTIES;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_RESIDUAL_CHILD_NODE_DEFINITIONS;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_RESIDUAL_PROPERTY_DEFINITIONS;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_SUPERTYPES;
import static org.apache.jackrabbit.oak.plugins.tree.TreeUtil.getBoolean;
import static org.apache.jackrabbit.oak.plugins.tree.TreeUtil.getNames;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.security.AccessControlException;

import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Tree.Status;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.jcr.lock.LockDeprecation;
import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager;
import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
import org.apache.jackrabbit.value.ValueHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * {@code NodeDelegate} serve as internal representations of {@code Node}s.
 * Most methods of this class throw an {@code InvalidItemStateException}
 * exception if the instance is stale. An instance is stale if the underlying
 * items does not exist anymore.
 */
public class NodeDelegate extends ItemDelegate {

    /** The underlying {@link org.apache.jackrabbit.oak.api.Tree} of this node. */
    private final Tree tree;

    public NodeDelegate(SessionDelegate sessionDelegate, Tree tree) {
        super(sessionDelegate);
        this.tree = tree;
    }

    @Override
    @NotNull
    public String getName() {
        return tree.getName();
    }

    @Override
    @NotNull
    public String getPath() {
        return tree.getPath();
    }

    @Override
    @Nullable
    public NodeDelegate getParent() {
        return tree.isRoot() || !tree.getParent().exists()
            ? null
            : new NodeDelegate(sessionDelegate, tree.getParent());
    }

    @Override
    public boolean exists() {
        return tree.exists();
    }

    @Override
    @Nullable
    public Status getStatus() {
        return tree.getStatus();
    }

    @NotNull
    public String getIdentifier() throws InvalidItemStateException {
        return IdentifierManager.getIdentifier(getTree());
    }

    @Override
    public boolean isProtected() throws InvalidItemStateException {
        Tree tree = getTree();
        if (tree.isRoot()) {
            return false;
        }

        Tree parent = tree.getParent();
        String nameWithIndex = tree.getName();
        String name = dropIndexFromName(nameWithIndex);
        boolean sns = !name.equals(nameWithIndex);
        Tree typeRoot = sessionDelegate.getRoot().getTree(NODE_TYPES_PATH);
        List types = TreeUtil.getEffectiveType(parent, typeRoot);

        boolean protectedResidual = false;
        for (Tree type : types) {
            if (contains(TreeUtil.getNames(type, REP_PROTECTED_CHILD_NODES), name)) {
                return true;
            } else if (!protectedResidual) {
                protectedResidual = TreeUtil.getBoolean(
                        type, REP_HAS_PROTECTED_RESIDUAL_CHILD_NODES);
            }
        }

        // Special case: There are one or more protected *residual*
        // child node definitions. Iterate through them to check whether
        // there's a matching, protected one.
        if (protectedResidual) {
            Set typeNames = new HashSet<>();
            for (Tree type : TreeUtil.getEffectiveType(tree, typeRoot)) {
                typeNames.add(TreeUtil.getName(type, JCR_NODETYPENAME));
                addAll(typeNames, TreeUtil.getNames(type, REP_SUPERTYPES));
            }

            for (Tree type : types) {
                Tree definitions =
                        type.getChild(REP_RESIDUAL_CHILD_NODE_DEFINITIONS);
                for (String typeName : typeNames) {
                    Tree definition = definitions.getChild(typeName);
                    if ((!sns || TreeUtil.getBoolean(definition, JCR_SAMENAMESIBLINGS))
                            && TreeUtil.getBoolean(definition, JCR_PROTECTED)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    boolean isProtected(String propertyName, Type propertyType) throws InvalidItemStateException {
        Tree tree = getTree();
        Tree typeRoot = sessionDelegate.getRoot().getTree(NODE_TYPES_PATH);
        List types = TreeUtil.getEffectiveType(tree, typeRoot);

        boolean protectedResidual = false;
        for (Tree type : types) {
            if (contains(TreeUtil.getNames(type, REP_PROTECTED_PROPERTIES), propertyName)) {
                return true;
            } else if (!protectedResidual) {
                protectedResidual = TreeUtil.getBoolean(
                        type, REP_HAS_PROTECTED_RESIDUAL_PROPERTIES);
            }
        }

        // Special case: There are one or more protected *residual*
        // property definitions. Iterate through them to check whether
        // there's a matching, protected one.
        if (protectedResidual) {
            Tree definition = findMatchingResidualPropertyDefinition(types, propertyType);
            if (definition != null && TreeUtil.getBoolean(definition, JCR_PROTECTED)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Determine whether this is the root node
     *
     * @return {@code true} iff this is the root node
     */
    public boolean isRoot() throws InvalidItemStateException {
        return getTree().isRoot();
    }

    /**
     * Get the number of properties of the node
     *
     * @return number of properties of the node
     */
    public long getPropertyCount() throws InvalidItemStateException {
        return getTree().getPropertyCount();
    }

    /**
     * Get a property
     *
     * @param relPath oak path
     * @return property at the path given by {@code relPath} or {@code null} if
     *         no such property exists
     */
    @Nullable
    public PropertyDelegate getPropertyOrNull(String relPath)
            throws RepositoryException {
        Tree parent = tree;
        String name = relPath;

        int slash = relPath.lastIndexOf('/');
        if (slash != -1) {
            parent = getTree(relPath.substring(0, slash));
            name = relPath.substring(slash + 1);
        }

        if (parent != null) {
            PropertyDelegate property =
                    new PropertyDelegate(sessionDelegate, parent, name);
            if (property.exists()) {
                return property;
            }
        }
        return null;
    }

    /**
     * Get a property. In contrast to {@link #getPropertyOrNull(String)} this
     * method never returns {@code null}. In the case where no property exists
     * at the given path, the returned property delegate throws an
     * {@code InvalidItemStateException} on access. See See OAK-395.
     *
     * @param relPath oak path
     * @return property at the path given by {@code relPath}.
     */
    @NotNull
    public PropertyDelegate getProperty(String relPath) throws RepositoryException {
        Tree parent = tree;
        String name = relPath;

        int slash = relPath.lastIndexOf('/');
        if (slash != -1) {
            parent = getTree(relPath.substring(0, slash));
            name = relPath.substring(slash + 1);
        }

        return new PropertyDelegate(sessionDelegate, parent, name);
    }

    /**
     * Get the properties of the node
     *
     * @return properties of the node
     */
    @NotNull
    public Iterator getProperties() throws InvalidItemStateException {
        return transform(getTree().getProperties().iterator(),
                propertyState -> new PropertyDelegate(sessionDelegate, tree, propertyState.getName()));
    }

    /**
     * Get the number of child nodes
     * 

* If an implementation does know the exact value, it returns it (even if * the value is higher than max). If the implementation does not know the * exact value, and the child node count is higher than max, it may return * Long.MAX_VALUE. The cost of the operation is at most O(max). * * @param max the maximum value * @return number of child nodes of the node */ public long getChildCount(long max) throws InvalidItemStateException { return getTree().getChildrenCount(max); } /** * Get child node * * @param relPath oak path * @return node at the path given by {@code relPath} or {@code null} if * no such node exists */ @Nullable public NodeDelegate getChild(String relPath) throws RepositoryException { if (relPath.isEmpty()) { return this; } Tree tree = getTree(relPath); return tree == null || !tree.exists() ? null : new NodeDelegate(sessionDelegate, tree); } /** * Returns an iterator for traversing all the children of this node. * If the node is orderable then the iterator will return child nodes in the * specified order. Otherwise the ordering of the iterator is undefined. * * @return child nodes of the node */ @NotNull public Iterator getChildren() throws InvalidItemStateException { Iterator iterator = getTree().getChildren().iterator(); return transform( filter(iterator, tree -> tree.exists()), tree -> new NodeDelegate(sessionDelegate, tree)); } public void orderBefore(String source, String target) throws ItemNotFoundException, InvalidItemStateException { Tree tree = getTree(); if (!tree.getChild(source).exists()) { throw new ItemNotFoundException("Not a child: " + source); } else if (target != null && !tree.getChild(target).exists()) { throw new ItemNotFoundException("Not a child: " + target); } else { tree.getChild(source).orderBefore(target); } } public boolean canAddMixin(String typeName) throws RepositoryException { if (!LockDeprecation.isLockingSupported() && JcrConstants.MIX_LOCKABLE.equals(typeName)) { return false; } Tree type = sessionDelegate.getRoot().getTree(NODE_TYPES_PATH).getChild(typeName); if (type.exists()) { return !TreeUtil.getBoolean(type, JCR_IS_ABSTRACT) && TreeUtil.getBoolean(type, JCR_ISMIXIN); } else { throw new NoSuchNodeTypeException( "Node type " + typeName + " does not exist"); } } public void addMixin(Function> existingMixins, String typeName) throws RepositoryException { TreeUtil.addMixin(getTree(), existingMixins, typeName, sessionDelegate.getRoot().getTree(NODE_TYPES_PATH), getUserID()); } public void removeMixin(String typeName) throws RepositoryException { Tree tree = getTree(); Set mixins = newLinkedHashSet(getNames(tree, JCR_MIXINTYPES)); if (!mixins.remove(typeName)) { throw new NoSuchNodeTypeException("Mixin " + typeName +" not contained in " + getPath()); } updateMixins(mixins, Collections.singleton(typeName)); } public void setMixins(Set mixinNames) throws RepositoryException { Set existingMixins = newLinkedHashSet(getNames(tree, JCR_MIXINTYPES)); if (existingMixins.isEmpty()) { updateMixins(mixinNames, Collections.emptySet()); } else { Set toRemove = newLinkedHashSet(); for (String name : existingMixins) { if (!mixinNames.remove(name)) { toRemove.add(name); } } updateMixins(mixinNames, toRemove); } } public void updateMixins(Set addMixinNames, Set removedOakMixinNames) throws RepositoryException { // 1. set all new mixin types including validation for (String oakMixinName : addMixinNames) { addMixin(TreeUtil::getMixinTypeNames, oakMixinName); } if (!removedOakMixinNames.isEmpty()) { // 2. retrieve the updated set of mixin types, remove the mixins that should no longer be present Set mixinNames = newLinkedHashSet(getNames(getTree(), JCR_MIXINTYPES)); if (mixinNames.removeAll(removedOakMixinNames)) { // FIXME: add mixins to add again as the removal may change the effect of type inheritance as evaluated during #addMixin mixinNames.addAll(addMixinNames); tree.setProperty(JCR_MIXINTYPES, mixinNames, NAMES); } // 3. clean up set of properties and child nodes such that all child items // have a valid item definition according to the effective node type present // after having updated the mixin property. this includes removing all // protected properties and child nodes associated with the removed mixin // type(s), as there's no way for the client to do that. Other items // defined in this mixin type might also need to be removed if there // is no longer a matching item definition available. Tree typeRoot = sessionDelegate.getRoot().getTree(NODE_TYPES_PATH); List removed = new ArrayList(); for (String name : removedOakMixinNames) { removed.add(typeRoot.getChild(name)); } List remaining = getNodeTypes(tree, typeRoot); for (PropertyState property : tree.getProperties()) { String name = property.getName(); Type type = property.getType(); Tree oldDefinition = findMatchingPropertyDefinition(removed, name, type, true); if (oldDefinition != null) { Tree newDefinition = findMatchingPropertyDefinition(remaining, name, type, true); if (newDefinition == null || (getBoolean(oldDefinition, JCR_PROTECTED) && !getBoolean(newDefinition, JCR_PROTECTED))) { tree.removeProperty(name); } } } for (Tree child : tree.getChildren()) { String name = child.getName(); Set typeNames = newLinkedHashSet(); for (Tree type : getNodeTypes(child, typeRoot)) { typeNames.add(TreeUtil.getName(type, JCR_NODETYPENAME)); addAll(typeNames, getNames(type, REP_SUPERTYPES)); } Tree oldDefinition = findMatchingChildNodeDefinition(removed, name, typeNames); if (oldDefinition != null) { Tree newDefinition = findMatchingChildNodeDefinition(remaining, name, typeNames); if (newDefinition == null || (getBoolean(oldDefinition, JCR_PROTECTED) && !getBoolean(newDefinition, JCR_PROTECTED))) { child.remove(); } } } } } /** * Set a property * * @return the set property */ @NotNull public PropertyDelegate setProperty( PropertyState propertyState, boolean exactTypeMatch, boolean setProtected) throws RepositoryException { Tree tree = getTree(); String name = propertyState.getName(); Type type = propertyState.getType(); PropertyState old = tree.getProperty(name); if (old != null && old.isArray() && !propertyState.isArray()) { throw new ValueFormatException( "Can not assign a single value to multi-valued property: " + propertyState); } if (old != null && !old.isArray() && propertyState.isArray()) { throw new ValueFormatException( "Can not assign multiple values to single valued property: " + propertyState); } Tree definition = findMatchingPropertyDefinition( getNodeTypes(tree), name, type, exactTypeMatch); if (definition == null) { throw new ConstraintViolationException( "No matching property definition: " + propertyState); } else if (!setProtected && TreeUtil.getBoolean(definition, JCR_PROTECTED)) { throw new ConstraintViolationException( "Property is protected: " + propertyState); } Type requiredType = Type.fromString(TreeUtil.getString(definition, JCR_REQUIREDTYPE)); if (requiredType != Type.UNDEFINED) { if (TreeUtil.getBoolean(definition, JCR_MULTIPLE)) { requiredType = requiredType.getArrayType(); } ValueHelper.checkSupportedConversion(propertyState.getType().tag(), requiredType.tag()); propertyState = PropertyStates.convert(propertyState, requiredType); } tree.setProperty(propertyState); return new PropertyDelegate(sessionDelegate, tree, name); } /** * Returns the type nodes of the primary and mixin types of the given node. * * @param tree node * @return primary and mixin type nodes */ private List getNodeTypes(Tree tree) { Root root = sessionDelegate.getRoot(); return getNodeTypes(tree, root.getTree(NODE_TYPES_PATH)); } private List getNodeTypes(Tree tree, Tree typeRoot) { // Find applicable node types List types = newArrayList(); String primaryName = TreeUtil.getName(tree, JCR_PRIMARYTYPE); if (primaryName == null) { primaryName = NT_BASE; } types.add(typeRoot.getChild(primaryName)); for (String mixinName : TreeUtil.getNames(tree, JCR_MIXINTYPES)) { types.add(typeRoot.getChild(mixinName)); } return types; } private boolean isNodeType(String typeName) { return isNodeType(tree, typeName, sessionDelegate.getRoot()); } private boolean isNodeType(Tree tree, String typeName, Root root) { return TreeUtil.isNodeType(tree, typeName, root.getTree(NODE_TYPES_PATH)); } private Tree findMatchingPropertyDefinition( List types, String propertyName, Type propertyType, boolean exactTypeMatch) { // Escape the property name for looking up a matching definition String escapedName; if (JCR_PRIMARYTYPE.equals(propertyName)) { escapedName = NodeTypeConstants.REP_PRIMARY_TYPE; } else if (JCR_MIXINTYPES.equals(propertyName)) { escapedName = NodeTypeConstants.REP_MIXIN_TYPES; } else if (JCR_UUID.equals(propertyName)) { escapedName = NodeTypeConstants.REP_UUID; } else { escapedName = propertyName; } String definedType = propertyType.toString(); String undefinedType = UNDEFINED.toString(); if (propertyType.isArray()) { undefinedType = UNDEFINEDS.toString(); } // First look for a matching named property definition Tree fuzzyMatch = null; for (Tree type : types) { Tree definitions = type .getChild(REP_NAMED_PROPERTY_DEFINITIONS) .getChild(escapedName); Tree definition = definitions.getChild(definedType); if (definition.exists()) { return definition; } definition = definitions.getChild(undefinedType); if (definition.exists()) { return definition; } for (Tree def : definitions.getChildren()) { if (propertyType.isArray() == TreeUtil.getBoolean(def, JCR_MULTIPLE)) { if (getBoolean(def, JCR_PROTECTED)) { return null; // no fuzzy matches for protected items } else if (!exactTypeMatch && fuzzyMatch == null) { fuzzyMatch = def; } } } } // Then look through any residual property definitions return findMatchingResidualPropertyDefinition(fuzzyMatch, types, propertyType.isArray(), definedType, undefinedType, exactTypeMatch); } private Tree findMatchingResidualPropertyDefinition(List types, Type propertyType) { String definedType = propertyType.toString(); String undefinedType = UNDEFINED.toString(); if (propertyType.isArray()) { undefinedType = UNDEFINEDS.toString(); } return findMatchingResidualPropertyDefinition(null, types, propertyType.isArray(), definedType, undefinedType, true); } private Tree findMatchingResidualPropertyDefinition(Tree fuzzyMatch, List types, boolean isMultiValue, String definedType, String undefinedType, boolean exactTypeMatch) { for (Tree type : types) { Tree definitions = type.getChild(REP_RESIDUAL_PROPERTY_DEFINITIONS); Tree definition = definitions.getChild(definedType); if (definition.exists()) { return definition; } definition = definitions.getChild(undefinedType); if (definition.exists()) { return definition; } if (!exactTypeMatch && fuzzyMatch == null) { for (Tree def : definitions.getChildren()) { if (isMultiValue == TreeUtil.getBoolean(def, JCR_MULTIPLE)) { fuzzyMatch = def; break; } } } } return fuzzyMatch; } private Tree findMatchingChildNodeDefinition( List types, String childNodeName, Set typeNames) { // First look for a matching named property definition for (Tree type : types) { Tree definitions = type .getChild(REP_NAMED_CHILD_NODE_DEFINITIONS) .getChild(childNodeName); for (String typeName : typeNames) { Tree definition = definitions.getChild(typeName); if (definition.exists()) { return definition; } } } // Then look through any residual property definitions for (Tree type : types) { Tree definitions = type .getChild(REP_RESIDUAL_CHILD_NODE_DEFINITIONS); for (String typeName : typeNames) { Tree definition = definitions.getChild(typeName); if (definition.exists()) { return definition; } } } return null; } /** * Add a child node * * @param name Oak name of the new child node * @param typeName Oak name of the type of the new child node, * or {@code null} if a default type should be used * @return the added node or {@code null} if such a node already exists */ @Nullable public NodeDelegate addChild(String name, String typeName) throws RepositoryException { Tree tree = getTree(); if (tree.hasChild(name)) { return null; } Tree typeRoot = sessionDelegate.getRoot().getTree(NODE_TYPES_PATH); Tree child = TreeUtil.addChild(tree, name, typeName, typeRoot, getUserID()); return new NodeDelegate(sessionDelegate, child); } /** * Remove this node. This operation never succeeds for the root node. * * @return {@code true} if the node was removed; {@code false} otherwise. */ @Override public boolean remove() throws InvalidItemStateException { return getTree().remove(); } /** * Enables or disabled orderable children on the underlying tree. * * @param enable whether to enable or disable orderable children. */ public void setOrderableChildren(boolean enable) throws InvalidItemStateException { getTree().setOrderableChildren(enable); } /** * Checks whether this node is locked, either directly or through * a deep lock on an ancestor. * * @return whether this node is locked */ // FIXME: access to locking status should not depend on access rights (OAK-4234) public boolean isLocked() { return getLock() != null; } @Nullable public NodeDelegate getLock() { Tree lock = findLock(tree, false); if (lock != null) { NodeDelegate delegate = new NodeDelegate(sessionDelegate, lock); if (delegate.isNodeType(MIX_LOCKABLE)) { return delegate; } else if (lock.isRoot()) { return null; } else { lock = findLock(lock.getParent(), true); } } return null; } @Nullable private Tree findLock(@NotNull Tree tree, boolean deep) { if (holdsLock(tree, deep)) { return tree; } else if (tree.isRoot()) { return null; } else { return findLock(tree.getParent(), true); } } private boolean holdsLock(Tree tree, boolean deep) { // FIXME: access to locking status should not depend on access rights (OAK-4234) PropertyState property = tree.getProperty(JCR_LOCKISDEEP); return property != null && property.getType() == Type.BOOLEAN && (!deep || property.getValue(BOOLEAN)); } @Nullable private Tree findDescendantLock(@NotNull Tree tree) { for (Tree child : tree.getChildren()) { if (holdsLock(child, false)) { return child; } else { Tree desc = findDescendantLock(child); if (desc != null) { return desc; } } } return null; } /** * Checks whether this node holds a lock. * * @param deep if {@code true}, only check for deep locks * @return whether this node holds a lock */ public boolean holdsLock(boolean deep) { return holdsLock(tree, deep) && isNodeType(MIX_LOCKABLE); } public String getLockOwner() { PropertyState property = tree.getProperty(JCR_LOCKOWNER); if (property != null && property.getType() == Type.STRING) { return property.getValue(Type.STRING); } else { return null; } } public boolean isLockOwner(String user) { return user != null && user.equals(getLockOwner()); } public void lock(boolean isDeep) throws RepositoryException { String path = getPath(); Root root = sessionDelegate.getContentSession().getLatestRoot(); Tree tree = root.getTree(path); if (!tree.exists()) { throw new ItemNotFoundException("Node " + path + " does not exist"); } else if (!isNodeType(tree, MIX_LOCKABLE, root)) { throw new LockException("Node " + path + " is not lockable"); } else if (tree.hasProperty(JCR_LOCKISDEEP)) { throw new LockException("Node " + path + " is already locked"); } // look for locked ancestor Tree inheritedLock = findLock(tree, true); if (inheritedLock != null) { throw new LockException("Node already indirectly locked by " + inheritedLock.getPath()); } // scan for locked descendant if (isDeep) { Tree descendantLock = findDescendantLock(tree); if (descendantLock != null) { throw new LockException("Lock conflicts with lock hold by " + descendantLock.getPath()); } } try { String owner = sessionDelegate.getAuthInfo().getUserID(); if (owner == null) { owner = ""; } tree.setProperty(JCR_LOCKISDEEP, isDeep); tree.setProperty(JCR_LOCKOWNER, owner); sessionDelegate.commit(root); } catch (CommitFailedException e) { if (e.isAccessViolation()) { throw new AccessControlException( "Access denied to lock node " + path, e); } else { throw new RepositoryException( "Unable to lock node " + path, e); } } } public void unlock() throws RepositoryException { String path = getPath(); Root root = sessionDelegate.getContentSession().getLatestRoot(); Tree tree = root.getTree(path); if (!tree.exists()) { throw new ItemNotFoundException("Node " + path + " does not exist"); } else if (!isNodeType(tree, MIX_LOCKABLE, root)) { throw new LockException("Node " + path + " is not lockable"); } else if (!tree.hasProperty(JCR_LOCKISDEEP)) { throw new LockException("Node " + path + " is not locked"); } try { tree.removeProperty(JCR_LOCKISDEEP); tree.removeProperty(JCR_LOCKOWNER); sessionDelegate.commit(root); } catch (CommitFailedException e) { if (e.isAccessViolation()) { throw new AccessControlException( "Access denied to unlock node " + path, e); } else { throw new RepositoryException( "Unable to unlock node " + path, e); } } } @Override public String toString() { return toStringHelper(this).add("tree", tree).toString(); } //------------------------------------------------------------< internal >--- @NotNull // FIXME this should be package private. OAK-672 public Tree getTree() throws InvalidItemStateException { if (!tree.exists()) { throw new InvalidItemStateException("Item is stale " + tree.getPath()); } return tree; } // -----------------------------------------------------------< private >--- private Tree getTree(String relPath) throws RepositoryException { if (PathUtils.isAbsolute(relPath)) { throw new RepositoryException("Not a relative path: " + relPath); } return TreeUtil.getTree(tree, relPath); } private String getUserID() { return sessionDelegate.getAuthInfo().getUserID(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy