Package org.apache.jackrabbit.core

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

/*
* Copyright 2004-2005 The Apache Software Foundation or its licensors,
*                     as applicable.
*
* Licensed 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 org.apache.commons.collections.iterators.IteratorChain;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
import org.apache.jackrabbit.core.nodetype.NodeDef;
import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
import org.apache.jackrabbit.core.nodetype.PropDef;
import org.apache.jackrabbit.core.nodetype.PropertyDefinitionImpl;
import org.apache.jackrabbit.core.security.AccessManager;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.ItemStateListener;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.PropertyState;
import org.apache.jackrabbit.core.state.SessionItemStateManager;
import org.apache.jackrabbit.core.state.StaleItemStateException;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.core.version.VersionManager;
import org.apache.jackrabbit.name.NoPrefixDeclaredException;
import org.apache.jackrabbit.name.Path;
import org.apache.jackrabbit.name.QName;
import org.apache.jackrabbit.uuid.UUID;
import org.apache.log4j.Logger;

import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ItemVisitor;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.version.VersionException;
import javax.jcr.version.VersionHistory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
* <code>ItemImpl</code> implements the <code>Item</code> interface.
*/
public abstract class ItemImpl implements Item, ItemStateListener {

    private static Logger log = Logger.getLogger(ItemImpl.class);

    protected static final int STATUS_NORMAL = 0;
    protected static final int STATUS_MODIFIED = 1;
    protected static final int STATUS_DESTROYED = 2;
    protected static final int STATUS_INVALIDATED = 3;

    protected int status;

    protected final ItemId id;

    /**
     * <code>Session</code> through which this <code>Item</code> was acquired
     */
    protected final SessionImpl session;

    /**
     * the <code>Repository</code> object
     */
    protected final RepositoryImpl rep;

    /**
     * <code>ItemState</code> associated with this <code>Item</code>
     */
    protected ItemState state;

    /**
     * <code>ItemManager</code> that created this <code>Item</code>
     */
    protected final ItemManager itemMgr;

    /**
     * <code>SessionItemStateManager</code> associated with this <code>Item</code>
     */
    protected final SessionItemStateManager stateMgr;

    /**
     * Listeners (weak references)
     */
    protected final Map listeners =
            Collections.synchronizedMap(new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK));

    /**
     * Package private constructor.
     *
     * @param itemMgr   the <code>ItemManager</code> that created this <code>Item</code>
     * @param session   the <code>Session</code> through which this <code>Item</code> is acquired
     * @param id        id of this <code>Item</code>
     * @param state     state associated with this <code>Item</code>
     * @param listeners listeners on life cylce changes of this <code>ItemImpl</code>
     */
    ItemImpl(ItemManager itemMgr, SessionImpl session, ItemId id, ItemState state,
             ItemLifeCycleListener[] listeners) {
        this.session = session;
        rep = (RepositoryImpl) session.getRepository();
        stateMgr = session.getItemStateManager();
        this.id = id;
        this.itemMgr = itemMgr;
        this.state = state;
        status = STATUS_NORMAL;

        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                addLifeCycleListener(listeners[i]);
            }
        }
        notifyCreated();

        // add this item as listener to events of the underlying state object
        this.state.addListener(this);
    }

    /**
     * Performs a sanity check on this item and the associated session.
     *
     * @throws RepositoryException if this item has been rendered invalid for some reason
     */
    protected void sanityCheck() throws RepositoryException {
        // check session status
        session.sanityCheck();

        // check status of this item for read operation
        if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) {
            throw new InvalidItemStateException(id + ": the item does not exist anymore");
        }
    }

    protected boolean isTransient() {
        return state.isTransient();
    }

    protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException;

    protected abstract void makePersistent() throws InvalidItemStateException;

    /**
     * Marks this instance as 'removed' and notifies its listeners.
     * The resulting state is either 'temporarily invalidated' or
     * 'permanently invalidated', depending on the initial state.
     *
     * @throws RepositoryException if an error occurs
     */
    protected void setRemoved() throws RepositoryException {
        if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) {
            // this instance is already 'invalid', get outta here
            return;
        }

        ItemState transientState = getOrCreateTransientItemState();
        if (transientState.getStatus() == ItemState.STATUS_NEW) {
            // this is a 'new' item, simply dispose the transient state
            // (it is no longer used); this will indirectly (through
            // stateDiscarded listener method) invalidate this instance permanently
            stateMgr.disposeTransientItemState(transientState);
        } else {
            // this is an 'existing' item (i.e. it is backed by persistent
            // state), mark it as 'removed'
            transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED);
            // transfer the transient state to the attic
            stateMgr.moveTransientItemStateToAttic(transientState);

            // set state of this instance to 'invalid'
            status = STATUS_INVALIDATED;
            // notify the listeners that this instance has been
            // temporarily invalidated
            notifyInvalidated();
        }
    }

    /**
     * Returns the item-state associated with this <code>Item</code>.
     *
     * @return state associated with this <code>Item</code>
     */
    ItemState getItemState() {
        return state;
    }

    /**
     * Notify the listeners that this instance has been discarded
     * (i.e. it has been temporarily rendered 'invalid').
     */
    private void notifyCreated() {
        // copy listeners to array to avoid ConcurrentModificationException
        ItemLifeCycleListener[] la = new ItemLifeCycleListener[listeners.size()];
        Iterator iter = listeners.values().iterator();
        int cnt = 0;
        while (iter.hasNext()) {
            la[cnt++] = (ItemLifeCycleListener) iter.next();
        }
        for (int i = 0; i < la.length; i++) {
            if (la[i] != null) {
                la[i].itemCreated(this);
            }
        }
    }

    /**
     * Notify the listeners that this instance has been invalidated
     * (i.e. it has been temporarily rendered 'invalid').
     */
    protected void notifyInvalidated() {
        // copy listeners to array to avoid ConcurrentModificationException
        ItemLifeCycleListener[] la = new ItemLifeCycleListener[listeners.size()];
        Iterator iter = listeners.values().iterator();
        int cnt = 0;
        while (iter.hasNext()) {
            la[cnt++] = (ItemLifeCycleListener) iter.next();
        }
        for (int i = 0; i < la.length; i++) {
            if (la[i] != null) {
                la[i].itemInvalidated(id, this);
            }
        }
    }

    /**
     * Notify the listeners that this instance has been destroyed
     * (i.e. it has been permanently rendered 'invalid').
     */
    protected void notifyDestroyed() {
        // copy listeners to array to avoid ConcurrentModificationException
        ItemLifeCycleListener[] la = new ItemLifeCycleListener[listeners.size()];
        Iterator iter = listeners.values().iterator();
        int cnt = 0;
        while (iter.hasNext()) {
            la[cnt++] = (ItemLifeCycleListener) iter.next();
        }
        for (int i = 0; i < la.length; i++) {
            if (la[i] != null) {
                la[i].itemDestroyed(id, this);
            }
        }
    }

    /**
     * Add an <code>ItemLifeCycleListener</code>
     *
     * @param listener the new listener to be informed on life cycle changes
     */
    void addLifeCycleListener(ItemLifeCycleListener listener) {
        if (!listeners.containsKey(listener)) {
            listeners.put(listener, listener);
        }
    }

    /**
     * Remove an <code>ItemLifeCycleListener</code>
     *
     * @param listener an existing listener
     */
    void removeLifeCycleListener(ItemLifeCycleListener listener) {
        listeners.remove(listener);
    }

    /**
     * Return the id of this <code>Item</code>.
     *
     * @return the id of this <code>Item</code>
     */
    public ItemId getId() {
        return id;
    }

    /**
     * Returns the primary path to this <code>Item</code>.
     *
     * @return the primary path to this <code>Item</code>
     */
    public Path getPrimaryPath() throws RepositoryException {
        return session.getHierarchyManager().getPath(id);
    }

    /**
     * Builds a list of transient (i.e. new or modified) item states that are
     * within the scope of <code>this.{@link #save()}</code>.
     *
     * @return list of transient item states
     * @throws InvalidItemStateException
     * @throws RepositoryException
     */
    private Collection getTransientStates()
            throws InvalidItemStateException, RepositoryException {
        // list of transient states that should be persisted
        ArrayList dirty = new ArrayList();
        ItemState transientState;

        // fail-fast test: check status of this item's state
        if (isTransient()) {
            switch (state.getStatus()) {
                case ItemState.STATUS_EXISTING_MODIFIED:
                    // add this item's state to the list
                    dirty.add(state);
                    break;

                case ItemState.STATUS_NEW:
                    {
                        String msg = safeGetJCRPath()
                                + ": cannot save a new item.";
                        log.debug(msg);
                        throw new RepositoryException(msg);
                    }

                case ItemState.STATUS_STALE_MODIFIED:
                    {
                        String msg = safeGetJCRPath()
                                + ": the item cannot be saved because it has been modified externally.";
                        log.debug(msg);
                        throw new InvalidItemStateException(msg);
                    }

                case ItemState.STATUS_STALE_DESTROYED:
                    {
                        String msg = safeGetJCRPath()
                                + ": the item cannot be saved because it has been deleted externally.";
                        log.debug(msg);
                        throw new InvalidItemStateException(msg);
                    }

                case ItemState.STATUS_UNDEFINED:
                    {
                        String msg = safeGetJCRPath()
                                + ": the item cannot be saved; it seems to have been removed externally.";
                        log.debug(msg);
                        throw new InvalidItemStateException(msg);
                    }

                default:
                    log.debug("unexpected state status (" + state.getStatus() + ")");
                    // ignore
                    break;
            }
        }

        if (isNode()) {
            // build list of 'new' or 'modified' descendants
            Iterator iter = stateMgr.getDescendantTransientItemStates((NodeId) id);
            while (iter.hasNext()) {
                transientState = (ItemState) iter.next();
                // fail-fast test: check status of transient state
                switch (transientState.getStatus()) {
                    case ItemState.STATUS_NEW:
                    case ItemState.STATUS_EXISTING_MODIFIED:
                        // add modified state to the list
                        dirty.add(transientState);
                        break;

                    case ItemState.STATUS_STALE_MODIFIED:
                        {
                            String msg = transientState.getId()
                                    + ": the item cannot be saved because it has been modified externally.";
                            log.debug(msg);
                            throw new InvalidItemStateException(msg);
                        }

                    case ItemState.STATUS_STALE_DESTROYED:
                        {
                            String msg = transientState.getId()
                                    + ": the item cannot be saved because it has been deleted externally.";
                            log.debug(msg);
                            throw new InvalidItemStateException(msg);
                        }

                    case ItemState.STATUS_UNDEFINED:
                        {
                            String msg = safeGetJCRPath()
                                    + ": the item cannot be saved; it seems to have been removed externally.";
                            log.debug(msg);
                            throw new InvalidItemStateException(msg);
                        }

                    default:
                        log.debug("unexpected state status (" + transientState.getStatus() + ")");
                        // ignore
                        break;
                }
            }
        }
        return dirty;
    }

    /**
     * Builds a list of transient descendant item states in the attic
     * (i.e. those marked as 'removed') that are within the scope of
     * <code>this.{@link #save()}</code>.
     *
     * @return list of transient item states
     * @throws InvalidItemStateException
     * @throws RepositoryException
     */
    private Collection getRemovedStates()
            throws InvalidItemStateException, RepositoryException {
        ArrayList removed = new ArrayList();
        ItemState transientState;

        if (isNode()) {
            Iterator iter = stateMgr.getDescendantTransientItemStatesInAttic((NodeId) id);
            while (iter.hasNext()) {
                transientState = (ItemState) iter.next();
                // check if stale
                if (transientState.getStatus() == ItemState.STATUS_STALE_MODIFIED) {
                    String msg = transientState.getId()
                            + ": the item cannot be removed because it has been modified externally.";
                    log.debug(msg);
                    throw new InvalidItemStateException(msg);
                }
                if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) {
                    String msg = transientState.getId()
                            + ": the item cannot be removed because it has already been deleted externally.";
                    log.debug(msg);
                    throw new InvalidItemStateException(msg);
                }
                removed.add(transientState);
            }
        }
        return removed;
    }

    private void validateTransientItems(Iterator dirtyIter, Iterator removedIter)
            throws AccessDeniedException, ConstraintViolationException,
            RepositoryException {
        /**
         * the following validations/checks are performed on transient items:
         *
         * for every transient item:
         * - if it is 'modified' check the WRITE permission
         *
         * for every transient node:
         * - if it is 'new' check that its node type satisfies the
         *   'required node type' constraint specified in its definition
         * - check if 'mandatory' child items exist
         *
         * for every transient property:
         * - check if the property value satisfies the value constraints
         *   specified in the property's definition
         *
         * note that the protected flag is checked in Node.addNode/Node.remove
         * (for adding/removing child entries of a node), in
         * Node.addMixin/removeMixin (for mixin changes on nodes)
         * and in Property.setValue (for properties to be modified).
         */

        AccessManager accessMgr = session.getAccessManager();
        // walk through list of dirty transient items and validate each
        while (dirtyIter.hasNext()) {
            ItemState itemState = (ItemState) dirtyIter.next();

            if (itemState.getStatus() != ItemState.STATUS_NEW) {
                // transient item is not 'new', therefore it has to be 'modified'

                // check WRITE permission
                ItemId id = itemState.getId();
                if (!accessMgr.isGranted(id, AccessManager.WRITE)) {
                    String msg = itemMgr.safeGetJCRPath(id)
                            + ": not allowed to modify item";
                    log.debug(msg);
                    throw new AccessDeniedException(msg);
                }
            }

            if (itemState.isNode()) {
                // the transient item is a node
                NodeState nodeState = (NodeState) itemState;
                ItemId id = nodeState.getId();
                NodeImpl node = (NodeImpl) itemMgr.getItem(id);
                NodeDefinition def = node.getDefinition();
                // primary type
                NodeTypeImpl pnt = (NodeTypeImpl) node.getPrimaryNodeType();
                // effective node type (primary type incl. mixins)
                EffectiveNodeType ent = node.getEffectiveNodeType();
                /**
                 * if the transient node was added (i.e. if it is 'new'),
                 * check its node's node type against the required node type
                 * in its definition
                 */
                if (nodeState.getStatus() == ItemState.STATUS_NEW) {
                    NodeType[] nta = def.getRequiredPrimaryTypes();
                    for (int i = 0; i < nta.length; i++) {
                        NodeTypeImpl ntReq = (NodeTypeImpl) nta[i];
                        if (!(pnt.getQName().equals(ntReq.getQName())
                                || pnt.isDerivedFrom(ntReq.getQName()))) {
                            /**
                             * the transient node's primary node type does not
                             * satisfy the 'required primary types' constraint
                             */
                            String msg = node.safeGetJCRPath()
                                    + " must be of node type " + ntReq.getName();
                            log.debug(msg);
                            throw new ConstraintViolationException(msg);
                        }
                    }
                }

                // mandatory child properties
                PropDef[] pda = ent.getMandatoryPropDefs();
                for (int i = 0; i < pda.length; i++) {
                    PropDef pd = pda[i];
                    if (pd.getDeclaringNodeType().equals(QName.MIX_VERSIONABLE)) {
                        /**
                         * todo FIXME workaround for mix:versionable:
                         * the mandatory properties are initialized at a
                         * later stage and might not exist yet
                         */
                        continue;
                    }
                    if (!nodeState.hasPropertyName(pd.getName())) {
                        String msg = node.safeGetJCRPath()
                                + ": mandatory property " + pd.getName()
                                + " does not exist";
                        log.debug(msg);
                        throw new ConstraintViolationException(msg);
                    }
                }
                // mandatory child nodes
                NodeDef[] cnda = ent.getMandatoryNodeDefs();
                for (int i = 0; i < cnda.length; i++) {
                    NodeDef cnd = cnda[i];
                    if (!nodeState.hasChildNodeEntry(cnd.getName())) {
                        String msg = node.safeGetJCRPath()
                                + ": mandatory child node " + cnd.getName()
                                + " does not exist";
                        log.debug(msg);
                        throw new ConstraintViolationException(msg);
                    }
                }
            } else {
                // the transient item is a property
                PropertyState propState = (PropertyState) itemState;
                ItemId propId = propState.getId();
                PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propId);
                PropertyDefinitionImpl def =
                        (PropertyDefinitionImpl) prop.getDefinition();

                /**
                 * check value constraints
                 * (no need to check value constraints of protected properties
                 * as those are set by the implementation only, i.e. they
                 * cannot be set by the user through the api)
                 */
                if (!def.isProtected()) {
                    String[] constraints = def.getValueConstraints();
                    if (constraints != null) {
                        InternalValue[] values = propState.getValues();
                        try {
                            EffectiveNodeType.checkSetPropertyValueConstraints(
                                    def.unwrap(), values);
                        } catch (RepositoryException e) {
                            // repack exception for providing verboser error message
                            String msg = prop.safeGetJCRPath() + ": " + e.getMessage();
                            log.debug(msg);
                            throw new ConstraintViolationException(msg);
                        }

                        /**
                         * need to manually check REFERENCE value constraints
                         * as this requires a session (target node needs to
                         * be checked)
                         */
                        if (constraints.length > 0
                                && def.getRequiredType() == PropertyType.REFERENCE) {
                            for (int i = 0; i < values.length; i++) {
                                boolean satisfied = false;
                                try {
                                    UUID targetUUID = (UUID) values[i].internalValue();
                                    Node targetNode = session.getNodeByUUID(targetUUID.toString());
                                    /**
                                     * constraints are OR-ed, i.e. at least one
                                     * has to be satisfied
                                     */
                                    for (int j = 0; j < constraints.length; j++) {
                                        /**
                                         * a REFERENCE value constraint specifies
                                         * the name of the required node type of
                                         * the target node
                                         */
                                        String ntName = constraints[j];
                                        if (targetNode.isNodeType(ntName)) {
                                            satisfied = true;
                                            break;
                                        }
                                    }
                                } catch (RepositoryException re) {
                                    String msg = prop.safeGetJCRPath()
                                            + ": failed to check REFERENCE value constraint";
                                    log.debug(msg);
                                    throw new ConstraintViolationException(msg, re);
                                }
                                if (!satisfied) {
                                    String msg = prop.safeGetJCRPath()
                                            + ": does not satisfy the value constraint "
                                            + constraints[0];   // just report the 1st
                                    log.debug(msg);
                                    throw new ConstraintViolationException(msg);
                                }
                            }
                        }
                    }
                }

                /**
                 * no need to check the protected flag as this is checked
                 * in PropertyImpl.setValue(Value)
                 */
            }
        }

        // walk through list of removed transient items and check REMOVE permission
        while (removedIter.hasNext()) {
            ItemState itemState = (ItemState) removedIter.next();
            ItemId id = itemState.getId();
            // check WRITE permission
            if (!accessMgr.isGranted(id, AccessManager.REMOVE)) {
                String msg = itemMgr.safeGetJCRPath(id)
                        + ": not allowed to remove item";
                log.debug(msg);
                throw new AccessDeniedException(msg);
            }
        }
    }

    private void removeTransientItems(Iterator iter) {

        /**
         * walk through list of transient items marked 'removed' and
         * definitively remove each one
         */
        while (iter.hasNext()) {
            ItemState transientState = (ItemState) iter.next();
            ItemState persistentState = transientState.getOverlayedState();
            /**
             * remove persistent state
             *
             * this will indirectly (through stateDestroyed listener method)
             * permanently invalidate all Item instances wrapping it
             */
            stateMgr.destroy(persistentState);
        }
    }

    private void persistTransientItems(Iterator iter)
            throws RepositoryException {

        // walk through list of transient items and persist each one
        while (iter.hasNext()) {
            ItemState itemState = (ItemState) iter.next();
            ItemImpl item = itemMgr.getItem(itemState.getId());
            // persist state of transient item
            item.makePersistent();
        }
    }

    private void restoreTransientItems(Iterator iter)
            throws RepositoryException {

        // walk through list of transient states and reapply transient changes
        while (iter.hasNext()) {
            ItemState itemState = (ItemState) iter.next();
            ItemId id = itemState.getId();
            ItemImpl item;

            if (stateMgr.isItemStateInAttic(id)) {
                // If an item has been removed and then again created, the
                // item is lost after persistTransientItems() and the
                // TransientItemStateManager will bark because of a deleted
                // state in its attic. We therefore have to forge a new item
                // instance ourself.
                if (itemState.isNode()) {
                    item = itemMgr.createNodeInstance((NodeState) itemState);
                } else {
                    item = itemMgr.createPropertyInstance((PropertyState) itemState);
                }
                itemState.setStatus(ItemState.STATUS_NEW);
            } else {
                item = itemMgr.getItem(id);
            }
            if (!item.isTransient()) {
                // reapply transient changes (i.e. undo effect of item.makePersistent())
                if (item.isNode()) {
                    NodeImpl node = (NodeImpl) item;
                    node.restoreTransient((NodeState) itemState);
                } else {
                    PropertyImpl prop = (PropertyImpl) item;
                    prop.restoreTransient((PropertyState) itemState);
                }
            }
        }
    }

    /**
     * Initializes the version history of all new nodes of node type
     * <code>mix:versionable</code>.
     * <p/>
     * Called by {@link #save()}.
     *
     * @param iter
     * @return true if this call generated new transient state; otherwise false
     * @throws RepositoryException
     */
    private boolean initVersionHistories(Iterator iter) throws RepositoryException {
        // walk through list of transient items and search for new versionable nodes
        boolean createdTransientState = false;
        while (iter.hasNext()) {
            ItemState itemState = (ItemState) iter.next();
            if (itemState.isNode()) {
                NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId());
                if (node.isNodeType(QName.MIX_VERSIONABLE)) {
                    if (!node.hasProperty(QName.JCR_VERSIONHISTORY)) {
                        VersionManager vMgr = session.getVersionManager();
                        NodeState nodeState = (NodeState) itemState;
                        /**
                         * check if there's already a version history for that
                         * node; this would e.g. be the case if a versionable
                         * node had been exported, removed and re-imported with
                         * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or
                         * IMPORT_UUID_COLLISION_REPLACE_EXISTING;
                         * otherwise create a new version history
                         */
                        VersionHistory vh = vMgr.getVersionHistory(session, nodeState);
                        if (vh == null) {
                            vh = vMgr.createVersionHistory(session, nodeState);
                        }
                        node.internalSetProperty(QName.JCR_VERSIONHISTORY, InternalValue.create(new UUID(vh.getUUID())));
                        node.internalSetProperty(QName.JCR_BASEVERSION, InternalValue.create(new UUID(vh.getRootVersion().getUUID())));
                        node.internalSetProperty(QName.JCR_ISCHECKEDOUT, InternalValue.create(true));
                        node.internalSetProperty(QName.JCR_PREDECESSORS,
                                new InternalValue[]{InternalValue.create(new UUID(vh.getRootVersion().getUUID()))});
                        createdTransientState = true;
                    }
                }
            }
        }
        return createdTransientState;
    }

    /**
     * Failsafe mapping of internal <code>id</code> to JCR path for use in
     * diagnostic output, error messages etc.
     *
     * @return JCR path or some fallback value
     */
    public String safeGetJCRPath() {
        return itemMgr.safeGetJCRPath(id);
    }

    /**
     * Same as <code>{@link Item#remove()}</code> except for the
     * <code>noChecks</code> parameter.
     *
     * @param noChecks
     * @throws VersionException
     * @throws LockException
     * @throws RepositoryException
     */
    protected void internalRemove(boolean noChecks)
            throws VersionException, LockException,
            ConstraintViolationException, RepositoryException {

        // check state of this instance
        sanityCheck();

        Path.PathElement thisName = getPrimaryPath().getNameElement();

        // check if protected
        if (isNode()) {
            NodeImpl node = (NodeImpl) this;
            // check if this is the root node
            if (node.getDepth() == 0) {
                String msg = safeGetJCRPath() + ": cannot remove root node";
                log.debug(msg);
                throw new RepositoryException(msg);
            }

            NodeDefinition def = node.getDefinition();
            // check protected flag
            if (!noChecks && def.isProtected()) {
                String msg = safeGetJCRPath() + ": cannot remove a protected node";
                log.debug(msg);
                throw new ConstraintViolationException(msg);
            }
        } else {
            PropertyImpl prop = (PropertyImpl) this;
            PropertyDefinition def = prop.getDefinition();
            // check protected flag
            if (!noChecks && def.isProtected()) {
                String msg = safeGetJCRPath() + ": cannot remove a protected property";
                log.debug(msg);
                throw new ConstraintViolationException(msg);
            }
        }

        NodeImpl parentNode = (NodeImpl) getParent();

        // verify that parent node is checked-out
        if (!noChecks && !parentNode.internalIsCheckedOut()) {
            String msg = parentNode.safeGetJCRPath() + ": cannot remove a child of a checked-in node";
            log.debug(msg);
            throw new VersionException(msg);
        }

        // check protected flag of parent node
        if (!noChecks && parentNode.getDefinition().isProtected()) {
            String msg = parentNode.safeGetJCRPath() + ": cannot remove a child of a protected node";
            log.debug(msg);
            throw new ConstraintViolationException(msg);
        }

        // check lock status
        if (!noChecks) {
            parentNode.checkLock();
        }

        // delegate the removal of the child item to the parent node
        if (isNode()) {
            parentNode.removeChildNode(thisName.getName(), thisName.getIndex());
        } else {
            parentNode.removeChildProperty(thisName.getName());
        }
    }

    /**
     * Same as <code>{@link Item#getName()}</code> except that
     * this method returns a <code>QName</code> instead of a
     * <code>String</code>.
     *
     * @return the name of this item as <code>QName</code>
     * @throws RepositoryException if an error occurs.
     */
    public abstract QName getQName() throws RepositoryException;

    //----------------------------------------------------< ItemStateListener >
    /**
     * {@inheritDoc}
     */
    public void stateCreated(ItemState created) {
        status = STATUS_NORMAL;
    }

    /**
     * {@inheritDoc}
     */
    public void stateDestroyed(ItemState destroyed) {
        // underlying state has been permanently destroyed

        // set state of this instance to 'destroyed'
        status = STATUS_DESTROYED;
        // dispose state
        if (state == destroyed) {
            state.removeListener(this);
            state = null;
        }
        /**
         * notify the listeners that this instance has been
         * permanently invalidated
         */
        notifyDestroyed();
    }

    /**
     * {@inheritDoc}
     */
    public void stateModified(ItemState modified) {
        status = STATUS_MODIFIED;
    }

    /**
     * {@inheritDoc}
     */
    public void stateDiscarded(ItemState discarded) {
        /**
         * the state of this item has been discarded, probably as a result
         * of calling Node.revert() or ItemImpl.setRemoved()
         */
        if (isTransient()) {
            switch (state.getStatus()) {
                /**
                 * persistent item that has been transiently removed
                 */
                case ItemState.STATUS_EXISTING_REMOVED:
                    /**
                     * persistent item that has been transiently modified
                     */
                case ItemState.STATUS_EXISTING_MODIFIED:
                    /**
                     * persistent item that has been transiently modified or removed
                     * and the underlying persistent state has been externally
                     * modified since the transient modification/removal.
                     */
                case ItemState.STATUS_STALE_MODIFIED:
                    ItemState persistentState = state.getOverlayedState();
                    /**
                     * the state is a transient wrapper for the underlying
                     * persistent state, therefore restore the
                     * persistent state and resurrect this item instance
                     * if necessary
                     */
                    state.removeListener(this);
                    persistentState.addListener(this);
                    stateMgr.disconnectTransientItemState(state);
                    state = persistentState;
                    state.addListener(this);

                    return;

                    /**
                     * persistent item that has been transiently modified or removed
                     * and the underlying persistent state has been externally
                     * destroyed since the transient modification/removal.
                     */
                case ItemState.STATUS_STALE_DESTROYED:
                    /**
                     * first notify the listeners that this instance has been
                     * permanently invalidated
                     */
                    notifyDestroyed();
                    // now set state of this instance to 'destroyed'
                    status = STATUS_DESTROYED;
                    // finally dispose state
                    state.removeListener(this);
                    state = null;
                    return;

                    /**
                     * new item that has been transiently added
                     */
                case ItemState.STATUS_NEW:
                    /**
                     * first notify the listeners that this instance has been
                     * permanently invalidated
                     */
                    notifyDestroyed();
                    // now set state of this instance to 'destroyed'
                    status = STATUS_DESTROYED;
                    // finally dispose state
                    state.removeListener(this);
                    state = null;
                    return;
            }
        }

        /**
         * first notify the listeners that this instance has been
         * invalidated
         */
        notifyInvalidated();
        // now render this instance 'invalid'
        status = STATUS_INVALIDATED;
    }

    //-----------------------------------------------------------------< Item >
    /**
     * {@inheritDoc}
     */
    public abstract void accept(ItemVisitor visitor)
            throws RepositoryException;

    /**
     * {@inheritDoc}
     */
    public abstract boolean isNode();

    /**
     * {@inheritDoc}
     */
    public abstract String getName() throws RepositoryException;

    /**
     * {@inheritDoc}
     */
    public abstract Node getParent()
            throws ItemNotFoundException, AccessDeniedException, RepositoryException;

    /**
     * {@inheritDoc}
     */
    public boolean isNew() {
        return state.isTransient() && state.getOverlayedState() == null;
    }

    /**
     * checks if this item is new. running outside of transactions, this
     * is the same as {@link #isNew()} but within a transaction an item can
     * be saved but not yet persisted.
     */
    protected boolean isTransactionalNew() {
        return state.getStatus() == ItemState.STATUS_NEW;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isModified() {
        return state.isTransient() && state.getOverlayedState() != null;
    }

    /**
     * {@inheritDoc}
     */
    public void remove()
            throws VersionException, LockException,
            ConstraintViolationException, RepositoryException {
        internalRemove(false);
    }

    /**
     * {@inheritDoc}
     */
    public void save()
            throws AccessDeniedException, ItemExistsException,
            ConstraintViolationException, InvalidItemStateException,
            ReferentialIntegrityException, VersionException, LockException,
            NoSuchNodeTypeException, RepositoryException {
        // check state of this instance
        sanityCheck();

        // synchronize on this session
        synchronized (session) {
            /**
             * build list of transient (i.e. new & modified) states that
             * should be persisted
             */
            Collection dirty = getTransientStates();
            if (dirty.size() == 0) {
                // no transient items, nothing to do here
                return;
            }

            /**
             * build list of transient descendants in the attic
             * (i.e. those marked as 'removed')
             */
            Collection removed = getRemovedStates();

            /**
             * build set of item id's who are within the scope of
             * (i.e. affected by) this save operation
             */
            Set affectedIds = new HashSet(dirty.size() + removed.size());
            for (Iterator it =
                    new IteratorChain(dirty.iterator(), removed.iterator());
                 it.hasNext();) {
                affectedIds.add(((ItemState) it.next()).getId());
            }

            /**
             * make sure that this save operation is totally 'self-contained'
             * and independant; items within the scope of this save operation
             * must not have 'external' dependencies;
             * (e.g. moving a node requires that the target node including both
             * old and new parents are saved)
             */
            for (Iterator it =
                    new IteratorChain(dirty.iterator(), removed.iterator());
                 it.hasNext();) {
                ItemState transientState = (ItemState) it.next();
                if (transientState.isNode()) {
                    NodeState nodeState = (NodeState) transientState;
                    Set dependentUUIDs = new HashSet();
                    if (nodeState.hasOverlayedState()) {
                        String oldParentUUID =
                                nodeState.getOverlayedState().getParentUUID();
                        String newParentUUID = nodeState.getParentUUID();
                        if (oldParentUUID != null) {
                            if (newParentUUID == null) {
                                // node has been removed, add old parent
                                // to dependencies
                                dependentUUIDs.add(oldParentUUID);
                            } else {
                                if (oldParentUUID != null &&
                                        !oldParentUUID.equals(newParentUUID)) {
                                    // node has been moved, add old and new parent
                                    // to dependencies
                                    dependentUUIDs.add(oldParentUUID);
                                    dependentUUIDs.add(newParentUUID);
                                }
                            }
                        }
                    }
                    // removed child node entries
                    for (Iterator cneIt =
                            nodeState.getRemovedChildNodeEntries().iterator();
                         cneIt.hasNext();) {
                        NodeState.ChildNodeEntry cne =
                                (NodeState.ChildNodeEntry) cneIt.next();
                        dependentUUIDs.add(cne.getUUID());
                    }
                    // added child node entries
                    for (Iterator cneIt =
                            nodeState.getAddedChildNodeEntries().iterator();
                         cneIt.hasNext();) {
                        NodeState.ChildNodeEntry cne =
                                (NodeState.ChildNodeEntry) cneIt.next();
                        dependentUUIDs.add(cne.getUUID());
                    }

                    // now walk through dependencies and check whether they
                    // are within the scope of this save operation
                    Iterator depIt = dependentUUIDs.iterator();
                    while (depIt.hasNext()) {
                        NodeId id = new NodeId((String) depIt.next());
                        if (!affectedIds.contains(id)) {
                            // need to save the parent as well
                            String msg = itemMgr.safeGetJCRPath(id)
                                    + " needs to be saved as well.";
                            log.debug(msg);
                            throw new ConstraintViolationException(msg);
                        }
                    }
                }
            }

            /**
             * validate access and node type constraints
             * (this will also validate child removals)
             */
            validateTransientItems(dirty.iterator(), removed.iterator());

            // start the update operation
            try {
                stateMgr.edit();
            } catch (IllegalStateException e) {
                String msg = "Unable to start edit operation";
                log.debug(msg);
                throw new RepositoryException(msg, e);
            }

            boolean succeeded = false;

            try {

                // process transient items marked as 'removed'
                removeTransientItems(removed.iterator());

                // initialize version histories for new nodes (might generate new transient state)
                if (initVersionHistories(dirty.iterator())) {
                    // re-build the list of transient states because the previous call
                    // generated new transient state
                    dirty = getTransientStates();
                }

                // process 'new' or 'modified' transient states
                persistTransientItems(dirty.iterator());

                // dispose the transient states marked 'new' or 'modified'
                // at this point item state data is pushed down one level,
                // node instances are disconnected from the transient
                // item state and connected to the 'overlayed' item state.
                // transient item states must be removed now. otherwise
                // the session item state provider will return an orphaned
                // item state which is not referenced by any node instance.
                for (Iterator it = dirty.iterator(); it.hasNext();) {
                    ItemState transientState = (ItemState) it.next();
                    // dispose the transient state, it is no longer used
                    stateMgr.disposeTransientItemState(transientState);
                }

                // end update operation
                stateMgr.update();
                // update operation succeeded
                succeeded = true;
            } catch (StaleItemStateException e) {
                throw new InvalidItemStateException(e.getMessage());
            } catch (ItemStateException e) {
                String msg = safeGetJCRPath() + ": unable to update item.";
                log.debug(msg);
                throw new RepositoryException(msg, e);
            } finally {
                if (!succeeded) {
                    // update operation failed, cancel all modifications
                    stateMgr.cancel();

                    // JCR-288: if an exception has been thrown during
                    // update() the transient changes have already been
                    // applied by persistTransientItems() and we need to
                    // restore transient state, i.e. undo the effect of
                    // persistTransientItems()
                    restoreTransientItems(dirty.iterator());
                }
            }

            // now it is safe to dispose the transient states:
            // dispose the transient states marked 'removed'.
            // item states in attic are removed after store, because
            // the observation mechanism needs to build paths of removed
            // items in store().
            for (Iterator it = removed.iterator(); it.hasNext();) {
                ItemState transientState = (ItemState) it.next();
                // dispose the transient state, it is no longer used
                stateMgr.disposeTransientItemStateInAttic(transientState);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void refresh(boolean keepChanges)
            throws InvalidItemStateException, RepositoryException {
        // check state of this instance
        sanityCheck();

        if (keepChanges) {
            /** todo FIXME should reset Item#status field to STATUS_NORMAL
             * of all descendent non-transient instances; maybe also
             * have to reset stale ItemState instances */
            return;
        }

        if (isNode()) {
            // check if this is the root node
            if (getDepth() == 0) {
                // optimization
                stateMgr.disposeAllTransientItemStates();
                return;
            }
        }

        // list of transient items that should be discarded
        ArrayList list = new ArrayList();
        ItemState transientState;

        // check status of this item's state
        if (isTransient()) {
            transientState = state;
            switch (transientState.getStatus()) {
                case ItemState.STATUS_STALE_MODIFIED:
                case ItemState.STATUS_STALE_DESTROYED:
                case ItemState.STATUS_EXISTING_MODIFIED:
                    // add this item's state to the list
                    list.add(transientState);
                    break;

                case ItemState.STATUS_NEW:
                    {
                        String msg = safeGetJCRPath() + ": cannot revert a new item.";
                        log.debug(msg);
                        throw new RepositoryException(msg);
                    }

                default:
                    log.debug("unexpected state status (" + transientState.getStatus() + ")");
                    // ignore
                    break;
            }
        }

        if (isNode()) {
            // build list of 'new', 'modified' or 'stale' descendants
            Iterator iter = stateMgr.getDescendantTransientItemStates((NodeId) id);
            while (iter.hasNext()) {
                transientState = (ItemState) iter.next();
                switch (transientState.getStatus()) {
                    case ItemState.STATUS_STALE_MODIFIED:
                    case ItemState.STATUS_STALE_DESTROYED:
                    case ItemState.STATUS_NEW:
                    case ItemState.STATUS_EXISTING_MODIFIED:
                        // add new or modified state to the list
                        list.add(transientState);
                        break;

                    default:
                        log.debug("unexpected state status (" + transientState.getStatus() + ")");
                        // ignore
                        break;
                }
            }
        }

        // process list of 'new', 'modified' or 'stale' transient states
        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            transientState = (ItemState) iter.next();
            // dispose the transient state, it is no longer used;
            // this will indirectly (through stateDiscarded listener method)
            // either restore or permanently invalidate the wrapping Item instances
            stateMgr.disposeTransientItemState(transientState);
        }

        if (isNode()) {
            // discard all transient descendants in the attic (i.e. those marked
            // as 'removed'); this will resurrect the removed items
            iter = stateMgr.getDescendantTransientItemStatesInAttic((NodeId) id);
            while (iter.hasNext()) {
                transientState = (ItemState) iter.next();
                // dispose the transient state; this will indirectly (through
                // stateDiscarded listener method) resurrect the wrapping Item instances
                stateMgr.disposeTransientItemStateInAttic(transientState);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public Item getAncestor(int degree)
            throws ItemNotFoundException, AccessDeniedException, RepositoryException {
        // check state of this instance
        sanityCheck();

        if (degree == 0) {
            return itemMgr.getRootNode();
        }

        try {
            // Path.getAncestor requires relative degree, i.e. we need
            // to convert absolute to relative ancestor degree
            Path path = getPrimaryPath();
            int relDegree = path.getAncestorCount() - degree;
            if (relDegree < 0) {
                throw new ItemNotFoundException();
            }
            Path ancestorPath = path.getAncestor(relDegree);
            return itemMgr.getItem(ancestorPath);
        } catch (PathNotFoundException pnfe) {
            throw new ItemNotFoundException();
        }
    }

    /**
     * {@inheritDoc}
     */
    public String getPath() throws RepositoryException {
        // check state of this instance
        sanityCheck();

        try {
            return getPrimaryPath().toJCRPath(session.getNamespaceResolver());
        } catch (NoPrefixDeclaredException npde) {
            // should never get here...
            String msg = "internal error: encountered unregistered namespace";
            log.debug(msg);
            throw new RepositoryException(msg, npde);
        }
    }

    /**
     * {@inheritDoc}
     */
    public int getDepth() throws RepositoryException {
        // check state of this instance
        sanityCheck();

        if (state.getParentUUID() == null) {
            // shortcut
            return 0;
        }
        return session.getHierarchyManager().getDepth(id);
    }

    /**
     * {@inheritDoc}
     */
    public Session getSession() throws RepositoryException {
        // check state of this instance
        sanityCheck();

        return session;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isSame(Item otherItem) throws RepositoryException {
        // check state of this instance
        sanityCheck();

        if (this == otherItem) {
            return true;
        }
        if (otherItem instanceof ItemImpl) {
            ItemImpl other = (ItemImpl) otherItem;
            return id.equals(other.id);
        }
        return false;
    }
}
TOP

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

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.