Package org.apache.jackrabbit.core

Source Code of org.apache.jackrabbit.core.ItemValidator

/*
* 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.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.ItemDefinition;
import javax.jcr.version.VersionException;

import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.lock.LockManager;
import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
import org.apache.jackrabbit.core.nodetype.NodeDef;
import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
import org.apache.jackrabbit.core.nodetype.PropDef;
import org.apache.jackrabbit.core.retention.RetentionRegistry;
import org.apache.jackrabbit.core.security.AccessManager;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.PropertyState;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility class for validating an item against constraints
* specified by its definition.
*/
public class ItemValidator {

    /**
     * check access permissions
     */
    public static final int CHECK_ACCESS = 1;

    /**
     * option to check lock status
     */
    public static final int CHECK_LOCK = 2;
    /**
     * option to check checked-out status
     */
    public static final int CHECK_VERSIONING = 4;

    /**
     * check for referential integrity upon removal
     */
    public static final int CHECK_REFERENCES = 8;

    /**
     * option to check if the item is protected by it's nt definition
     */
    public static final int CHECK_CONSTRAINTS = 16;

    /**
     * option to check for pending changes on the session
     */
    public static final int CHECK_PENDING_CHANGES = 32;

    /**
     * option to check for pending changes on the specified node
     */
    public static final int CHECK_PENDING_CHANGES_ON_NODE = 64;

    /**
     * option to check for effective holds
     */
    public static final int CHECK_HOLD = 128;

    /**
     * option to check for effective retention policies
     */
    public static final int CHECK_RETENTION = 256;
   
    /**
     * Logger instance for this class
     */
    private static Logger log = LoggerFactory.getLogger(ItemValidator.class);

    /**
     * node type registry
     */
    protected final NodeTypeRegistry ntReg;

    /**
     * hierarchy manager used for generating error msg's
     * that contain human readable paths
     *
     * @see #safeGetJCRPath(ItemId)
     */
    protected final HierarchyManager hierMgr;

    /**
     * Path resolver for outputting user-friendly error messages.
     */
    protected final PathResolver resolver;

    /**
     *
     */
    protected final LockManager lockMgr;

    protected final AccessManager accessMgr;

    protected final RetentionRegistry retentionReg;

    /**
     * Creates a new <code>ItemValidator</code> instance.
     *
     * @param ntReg      node type registry
     * @param hierMgr    hierarchy manager
     * @param session    session
     */
    public ItemValidator(NodeTypeRegistry ntReg,
                         HierarchyManager hierMgr,
                         SessionImpl session) throws RepositoryException {
        this(ntReg, hierMgr, session, session.getLockManager(), session.getAccessManager(), session.getRetentionRegistry());
    }

    /**
     * Creates a new <code>ItemValidator</code> instance.
     *
     * @param ntReg      node type registry
     * @param hierMgr    hierarchy manager
     * @param resolver   resolver
     * @param lockMgr    lockMgr
     * @param accessMgr  accessMgr
     * @param retentionReg
     */
    public ItemValidator(NodeTypeRegistry ntReg,
                         HierarchyManager hierMgr,
                         PathResolver resolver,
                         LockManager lockMgr,
                         AccessManager accessMgr,
                         RetentionRegistry retentionReg) {
        this.ntReg = ntReg;
        this.hierMgr = hierMgr;
        this.resolver = resolver;
        this.lockMgr = lockMgr;
        this.accessMgr = accessMgr;
        this.retentionReg = retentionReg;
    }

    /**
     * Checks whether the given node state satisfies the constraints specified
     * by its primary and mixin node types. The following validations/checks are
     * performed:
     * <ul>
     * <li>check if its node type satisfies the 'required node types' constraint
     * specified in its definition</li>
     * <li>check if all 'mandatory' child items exist</li>
     * <li>for every property: check if the property value satisfies the
     * value constraints specified in the property's definition</li>
     * </ul>
     *
     * @param nodeState state of node to be validated
     * @throws ConstraintViolationException if any of the validations fail
     * @throws RepositoryException          if another error occurs
     */
    public void validate(NodeState nodeState)
            throws ConstraintViolationException, RepositoryException {
        // effective primary node type
        EffectiveNodeType entPrimary =
                ntReg.getEffectiveNodeType(nodeState.getNodeTypeName());
        // effective node type (primary type incl. mixins)
        EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState);
        NodeDef def = ntReg.getNodeDef(nodeState.getDefinitionId());

        // check if primary type satisfies the 'required node types' constraint
        Name[] requiredPrimaryTypes = def.getRequiredPrimaryTypes();
        for (int i = 0; i < requiredPrimaryTypes.length; i++) {
            if (!entPrimary.includesNodeType(requiredPrimaryTypes[i])) {
                String msg = safeGetJCRPath(nodeState.getNodeId())
                        + ": missing required primary type "
                        + requiredPrimaryTypes[i];
                log.debug(msg);
                throw new ConstraintViolationException(msg);
            }
        }
        // mandatory properties
        PropDef[] pda = entPrimaryAndMixins.getMandatoryPropDefs();
        for (int i = 0; i < pda.length; i++) {
            PropDef pd = pda[i];
            if (!nodeState.hasPropertyName(pd.getName())) {
                String msg = safeGetJCRPath(nodeState.getNodeId())
                        + ": mandatory property " + pd.getName()
                        + " does not exist";
                log.debug(msg);
                throw new ConstraintViolationException(msg);
            }
        }
        // mandatory child nodes
        NodeDef[] cnda = entPrimaryAndMixins.getMandatoryNodeDefs();
        for (int i = 0; i < cnda.length; i++) {
            NodeDef cnd = cnda[i];
            if (!nodeState.hasChildNodeEntry(cnd.getName())) {
                String msg = safeGetJCRPath(nodeState.getNodeId())
                        + ": mandatory child node " + cnd.getName()
                        + " does not exist";
                log.debug(msg);
                throw new ConstraintViolationException(msg);
            }
        }
    }

    /**
     * Checks whether the given property state satisfies the constraints
     * specified by its definition. The following validations/checks are
     * performed:
     * <ul>
     * <li>check if the type of the property values does comply with the
     * requiredType specified in the property's definition</li>
     * <li>check if the property values satisfy the value constraints
     * specified in the property's definition</li>
     * </ul>
     *
     * @param propState state of property to be validated
     * @throws ConstraintViolationException if any of the validations fail
     * @throws RepositoryException          if another error occurs
     */
    public void validate(PropertyState propState)
            throws ConstraintViolationException, RepositoryException {
        PropDef def = ntReg.getPropDef(propState.getDefinitionId());
        InternalValue[] values = propState.getValues();
        int type = PropertyType.UNDEFINED;
        for (int i = 0; i < values.length; i++) {
            if (type == PropertyType.UNDEFINED) {
                type = values[i].getType();
            } else if (type != values[i].getType()) {
                throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId())
                        + ": inconsistent value types");
            }
            if (def.getRequiredType() != PropertyType.UNDEFINED
                    && def.getRequiredType() != type) {
                throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId())
                        + ": requiredType constraint is not satisfied");
            }
        }
        EffectiveNodeType.checkSetPropertyValueConstraints(def, values);
    }

    public void checkModify(ItemImpl item, int options, int permissions) throws RepositoryException {
        checkCondition(item, options, permissions, false);
    }

    public void checkRemove(ItemImpl item, int options, int permissions) throws RepositoryException {
        checkCondition(item, options, permissions, true);
    }

    private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException {
        if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) {
            if (item.getSession().hasPendingChanges()) {
                String msg = "Unable to perform operation. Session has pending changes.";
                log.debug(msg);
                throw new InvalidItemStateException(msg);
            }
        }
        if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) {
            if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) {
                String msg = "Unable to perform operation. Session has pending changes.";
                log.debug(msg);
                throw new InvalidItemStateException(msg);
            }
        }
        if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) {
            if (isProtected(item)) {
                String msg = "Unable to perform operation. Node is protected.";
                log.debug(msg);
                throw new ConstraintViolationException(msg);
            }
        }
        if ((options & CHECK_VERSIONING) == CHECK_VERSIONING) {
            NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent();
            if (!node.internalIsCheckedOut()) {
                String msg = "Unable to perform operation. Node is checked-in.";
                log.debug(msg);
                throw new VersionException(msg);
            }
        }
        if ((options & CHECK_LOCK) == CHECK_LOCK) {
            checkLock(item);
        }

        if (permissions > Permission.NONE) {
            Path path = item.getPrimaryPath();
            accessMgr.checkPermission(path, permissions);
        }
        if ((options & CHECK_HOLD) == CHECK_HOLD) {
            if (hasHold(item, isRemoval)) {
                throw new RepositoryException("Unable to perform operation. Node is affected by a hold.");
            }
        }
        if ((options & CHECK_RETENTION) == CHECK_RETENTION) {
            if (hasRetention(item, isRemoval)) {
                throw new RepositoryException("Unable to perform operation. Node is affected by a retention.");
            }
        }
    }

    public boolean canModify(ItemImpl item, int options, int permissions) throws RepositoryException {
        return hasCondition(item, options, permissions, false);
    }

    private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException {
        if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) {
            if (item.getSession().hasPendingChanges()) {
                return false;
            }
        }
        if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) {
            if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) {
                return false;
            }
        }
        if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) {
            if (isProtected(item)) {
                return false;
            }
        }
        if ((options & CHECK_VERSIONING) == CHECK_VERSIONING) {
            NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent();
            if (!node.internalIsCheckedOut()) {
                return false;
            }
        }
        if ((options & CHECK_LOCK) == CHECK_LOCK) {
            try {
                checkLock(item);
            } catch (LockException e) {
                return false;
            }
        }
        if (permissions > Permission.NONE) {
            Path path = item.getPrimaryPath();
            if (!accessMgr.isGranted(path, permissions)) {
                return false;
            }
        }
        if ((options & CHECK_HOLD) == CHECK_HOLD) {
            if (hasHold(item, isRemoval)) {
                return false;
            }
        }
        if ((options & CHECK_RETENTION) == CHECK_RETENTION) {
            if (hasRetention(item, isRemoval)) {
                return false;
            }
        }
        return true;
    }

    private void checkLock(ItemImpl item) throws LockException, RepositoryException {
        if (item.isNew()) {
            // a new item needs no check
            return;
        }
        NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent();
        lockMgr.checkLock(node);
    }

    private boolean isProtected(ItemImpl item) throws RepositoryException {
        ItemDefinition def;
        if (item.isNode()) {
            def = ((Node) item).getDefinition();
        } else {
            def = ((Property) item).getDefinition();
        }
        return def.isProtected();
    }

    private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException {
        if (item.isNew()) {
            return false;
        }
        Path path = item.getPrimaryPath();
        if (!item.isNode()) {
            path = path.getAncestor(1);
        }
        boolean checkParent = (item.isNode() && isRemoval);
        return retentionReg.hasEffectiveHold(path, checkParent);
    }

    private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException {
        if (item.isNew()) {
            return false;
        }
        Path path = item.getPrimaryPath();
        if (!item.isNode()) {
            path = path.getAncestor(1);
        }
        boolean checkParent = (item.isNode() && isRemoval);
        return retentionReg.hasEffectiveRetention(path, checkParent);
    }


   
    //-------------------------------------------------< misc. helper methods >
    /**
     * Helper method that builds the effective (i.e. merged and resolved)
     * node type representation of the specified node's primary and mixin
     * node types.
     *
     * @param state
     * @return the effective node type
     * @throws RepositoryException
     */
    public EffectiveNodeType getEffectiveNodeType(NodeState state)
            throws RepositoryException {
        try {
            return ntReg.getEffectiveNodeType(
                    state.getNodeTypeName(), state.getMixinTypeNames());
        } catch (NodeTypeConflictException ntce) {
            String msg = "internal error: failed to build effective node type for node "
                    + safeGetJCRPath(state.getNodeId());
            log.debug(msg);
            throw new RepositoryException(msg, ntce);
        }
    }

    /**
     * Helper method that finds the applicable definition for a child node with
     * the given name and node type in the parent node's node type and
     * mixin types.
     *
     * @param name
     * @param nodeTypeName
     * @param parentState
     * @return a <code>NodeDef</code>
     * @throws ConstraintViolationException if no applicable child node definition
     *                                      could be found
     * @throws RepositoryException          if another error occurs
     */
    public NodeDef findApplicableNodeDefinition(Name name,
                                                Name nodeTypeName,
                                                NodeState parentState)
            throws RepositoryException, ConstraintViolationException {
        EffectiveNodeType entParent = getEffectiveNodeType(parentState);
        return entParent.getApplicableChildNodeDef(name, nodeTypeName, ntReg);
    }

    /**
     * Helper method that finds the applicable definition for a property with
     * the given name, type and multiValued characteristic in the parent node's
     * node type and mixin types. If there more than one applicable definitions
     * then the following rules are applied:
     * <ul>
     * <li>named definitions are preferred to residual definitions</li>
     * <li>definitions with specific required type are preferred to definitions
     * with required type UNDEFINED</li>
     * </ul>
     *
     * @param name
     * @param type
     * @param multiValued
     * @param parentState
     * @return a <code>PropDef</code>
     * @throws ConstraintViolationException if no applicable property definition
     *                                      could be found
     * @throws RepositoryException          if another error occurs
     */
    public PropDef findApplicablePropertyDefinition(Name name,
                                                    int type,
                                                    boolean multiValued,
                                                    NodeState parentState)
            throws RepositoryException, ConstraintViolationException {
        EffectiveNodeType entParent = getEffectiveNodeType(parentState);
        return entParent.getApplicablePropertyDef(name, type, multiValued);
    }

    /**
     * Helper method that finds the applicable definition for a property with
     * the given name, type in the parent node's node type and mixin types.
     * Other than <code>{@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)}</code>
     * this method does not take the multiValued flag into account in the
     * selection algorithm. If there more than one applicable definitions then
     * the following rules are applied:
     * <ul>
     * <li>named definitions are preferred to residual definitions</li>
     * <li>definitions with specific required type are preferred to definitions
     * with required type UNDEFINED</li>
     * <li>single-value definitions are preferred to multiple-value definitions</li>
     * </ul>
     *
     * @param name
     * @param type
     * @param parentState
     * @return a <code>PropDef</code>
     * @throws ConstraintViolationException if no applicable property definition
     *                                      could be found
     * @throws RepositoryException          if another error occurs
     */
    public PropDef findApplicablePropertyDefinition(Name name,
                                                    int type,
                                                    NodeState parentState)
            throws RepositoryException, ConstraintViolationException {
        EffectiveNodeType entParent = getEffectiveNodeType(parentState);
        return entParent.getApplicablePropertyDef(name, type);
    }

    /**
     * Failsafe conversion of internal <code>Path</code> to JCR path for use in
     * error messages etc.
     *
     * @param path path to convert
     * @return JCR path
     */
    public String safeGetJCRPath(Path path) {
        try {
            return resolver.getJCRPath(path);
        } catch (NamespaceException e) {
            log.error("failed to convert {} to a JCR path", path);
            // return string representation of internal path as a fallback
            return path.toString();
        }
    }

    /**
     * Failsafe translation of internal <code>ItemId</code> to JCR path for use
     * in error messages etc.
     *
     * @param id id to translate
     * @return JCR path
     */
    public String safeGetJCRPath(ItemId id) {
        try {
            return safeGetJCRPath(hierMgr.getPath(id));
        } catch (ItemNotFoundException e) {
            // return string representation of id as a fallback
            return id.toString();
        } catch (RepositoryException e) {
            log.error(id + ": failed to build path");
            // return string representation of id as a fallback
            return id.toString();
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.core.ItemValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.