You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2006/07/12 15:33:27 UTC

svn commit: r421270 [16/23] - in /jackrabbit/trunk/contrib/spi: ./ commons/ commons/src/ commons/src/main/ commons/src/main/java/ commons/src/main/java/org/ commons/src/main/java/org/apache/ commons/src/main/java/org/apache/jackrabbit/ commons/src/main...

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,1671 @@
+/*
+ * 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.jcr2spi.state;
+
+import org.apache.commons.collections.iterators.IteratorChain;
+import org.apache.jackrabbit.jcr2spi.CachingHierarchyManager;
+import org.apache.jackrabbit.jcr2spi.HierarchyManager;
+import org.apache.jackrabbit.jcr2spi.ZombieHierarchyManager;
+import org.apache.jackrabbit.jcr2spi.util.ReferenceChangeTracker;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeConflictException;
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.jcr2spi.operation.OperationVisitor;
+import org.apache.jackrabbit.jcr2spi.operation.AddNode;
+import org.apache.jackrabbit.jcr2spi.operation.AddProperty;
+import org.apache.jackrabbit.jcr2spi.operation.Clone;
+import org.apache.jackrabbit.jcr2spi.operation.Copy;
+import org.apache.jackrabbit.jcr2spi.operation.Move;
+import org.apache.jackrabbit.jcr2spi.operation.Remove;
+import org.apache.jackrabbit.jcr2spi.operation.SetMixin;
+import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
+import org.apache.jackrabbit.jcr2spi.operation.ReorderNodes;
+import org.apache.jackrabbit.jcr2spi.operation.Checkout;
+import org.apache.jackrabbit.jcr2spi.operation.Checkin;
+import org.apache.jackrabbit.jcr2spi.operation.Update;
+import org.apache.jackrabbit.jcr2spi.operation.Restore;
+import org.apache.jackrabbit.jcr2spi.operation.ResolveMergeConflict;
+import org.apache.jackrabbit.jcr2spi.operation.Merge;
+import org.apache.jackrabbit.jcr2spi.operation.LockOperation;
+import org.apache.jackrabbit.jcr2spi.operation.LockRefresh;
+import org.apache.jackrabbit.jcr2spi.operation.LockRelease;
+import org.apache.jackrabbit.jcr2spi.operation.AddLabel;
+import org.apache.jackrabbit.jcr2spi.operation.RemoveLabel;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.uuid.UUID;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.IdFactory;
+import org.apache.jackrabbit.value.QValue;
+import org.apache.jackrabbit.value.ValueHelper;
+import org.apache.jackrabbit.value.ValueFormat;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.RepositoryException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFormatException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.PropertyType;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Session;
+import javax.jcr.MergeException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.version.VersionException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.lock.LockException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.io.InputStream;
+
+/**
+ * <code>SessionItemStateManager</code> ...
+ */
+public class SessionItemStateManager implements UpdatableItemStateManager, OperationVisitor {
+
+    private static Logger log = LoggerFactory.getLogger(SessionItemStateManager.class);
+
+    /**
+     * Id of the root node.
+     */
+    // TODO: TO-BE-FIXED. With SPI_ItemId rootId must not be stored separately
+    private final NodeId rootId;
+
+    /**
+     * State manager that allows updates
+     */
+    private final UpdatableItemStateManager workspaceItemStateMgr;
+
+    /**
+     * State manager for the transient items
+     */
+    // DIFF JACKRABBIT: private final TransientItemStateManager transientStateMgr;
+    private final TransientChangeLog transientStateMgr;
+
+    /**
+     * Hierarchy manager
+     */
+    // DIFF JACKRABBIT: private CachingHierarchyManager hierMgr;
+    private final CachingHierarchyManager hierMgr;
+    private final NamespaceResolver nsResolver;
+
+    private final IdFactory idFactory;
+    private final ValueFactory valueFactory;
+    private final ItemStateValidator validator;
+
+    /**
+     * Creates a new <code>SessionItemStateManager</code> instance.
+     *
+     * @param rootId
+     * @param workspaceItemStateMgr
+     * @param nsResolver
+     */
+    public SessionItemStateManager(NodeId rootId,
+                                   UpdatableItemStateManager workspaceItemStateMgr,
+                                   IdFactory idFactory,
+                                   ValueFactory valueFactory,
+                                   ItemStateValidator validator,
+                                   NamespaceResolver nsResolver) {
+        // DIFF JACKRABBIT: added rootId
+        this.rootId = rootId;
+        this.workspaceItemStateMgr = workspaceItemStateMgr;
+        // DIFF JACKRABBIT: this.transientStateMgr = new TransientItemStateManager();
+        this.transientStateMgr = new TransientChangeLog(idFactory);
+        // DIFF JR: validator added
+        this.validator = validator;
+        // DIFF JR: idFactory added
+        this.idFactory = idFactory;
+        // DIFF JR: valueFactory added
+        this.valueFactory = valueFactory;
+
+        // create hierarchy manager that uses both transient and persistent state
+        hierMgr = new CachingHierarchyManager(rootId, this, nsResolver);
+        this.nsResolver = nsResolver;
+    }
+
+    /**
+     * 
+     * @return
+     */
+    public HierarchyManager getHierarchyManager() {
+        return hierMgr;
+    }
+
+
+    //---------------------------------------------------< ItemStateManager >---
+    /**
+     * {@inheritDoc}
+     */
+    public ItemState getItemState(ItemId id)
+            throws NoSuchItemStateException, ItemStateException {
+
+        // first check if the specified item has been transiently removed
+        if (transientStateMgr.getAttic().hasItemState(id)) {
+            /**
+             * check if there's new transient state for the specified item
+             * (e.g. if a property with name 'x' has been removed and a new
+             * property with same name has been created);
+             * this will throw a NoSuchItemStateException if there's no new
+             * transient state
+             */
+            return transientStateMgr.getItemState(id);
+        }
+
+        // check if there's transient state for the specified item
+        if (transientStateMgr.hasItemState(id)) {
+            return transientStateMgr.getItemState(id);
+        }
+
+        // check if there's persistent state for the specified item
+        if (workspaceItemStateMgr.hasItemState(id)) {
+            return workspaceItemStateMgr.getItemState(id);
+        }
+
+        throw new NoSuchItemStateException(id.toString());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasItemState(ItemId id) {
+        // first check if the specified item has been transiently removed
+        if (transientStateMgr.getAttic().hasItemState(id)) {
+            /**
+             * check if there's new transient state for the specified item
+             * (e.g. if a property with name 'x' has been removed and a new
+             * property with same name has been created);
+             */
+            return transientStateMgr.hasItemState(id);
+        }
+        // check if there's transient state for the specified item
+        if (transientStateMgr.hasItemState(id)) {
+            return true;
+        }
+        // check if there's persistent state for the specified item
+        return workspaceItemStateMgr.hasItemState(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeReferences getNodeReferences(NodeId id)
+            throws NoSuchItemStateException, ItemStateException {
+
+        return workspaceItemStateMgr.getNodeReferences(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNodeReferences(NodeId id) {
+        return workspaceItemStateMgr.hasNodeReferences(id);
+    }
+
+    //------------------------------------------< UpdatableItemStateManager >---
+    /**
+     * {@inheritDoc}
+     */
+    public void execute(Operation operation) throws RepositoryException {
+        operation.accept(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void execute(ChangeLog changes) throws RepositoryException {
+        throw new UnsupportedOperationException("Not implemented for SessionItemStateManager");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void dispose() {
+        // discard all transient changes
+        transientStateMgr.disposeAllItemStates();
+        // dispose our (i.e. 'local') state manager
+        workspaceItemStateMgr.dispose();
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * @return <code>true</code> if this manager has any transient state;
+     *         <code>false</code> otherwise.
+     */
+    public boolean hasPendingChanges() {
+        // DIFF JACKRABBIT: return transientStateMgr.hasAnyItemStates();
+        return transientStateMgr.getEntriesCount() > 0;
+    }
+
+    /**
+     * This will save <code>state</code> and all descendants items of
+     * <code>state</code> that are transiently modified in a single step. If
+     * this operation fails, no item will have been saved.
+     *
+     * @param state the root state of the update operation
+     */
+    public void save(ItemState state) throws ReferentialIntegrityException,
+        RepositoryException, StaleItemStateException, ItemStateException {
+        // shortcut, if no modifications are present
+        if (!hasPendingChanges()) {
+            return;
+        }
+
+        // collect the changes to be saved
+        ChangeLog changeLog = getChangeLog(state);
+        if (!changeLog.isEmpty()) {
+            // only pass changelog if there are transient modifications available
+            // for the specified item and its decendants.
+            workspaceItemStateMgr.execute(changeLog);
+        }
+
+        // dispose the transient states marked 'new' or 'modified'
+        Iterator it = new IteratorChain(changeLog.addedStates(), changeLog.modifiedStates());
+        while (it.hasNext()) {
+            ItemState transientState = (ItemState) it.next();
+            // notify uncovering of transient state
+            transientState.notifyStateUncovering();
+            // dispose the transient state, it is no longer used
+            transientStateMgr.disposeItemState(transientState);
+        }
+
+        // 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 update().
+        it = changeLog.deletedStates();
+        while (it.hasNext()) {
+            ItemState transientState = (ItemState) it.next();
+            // dispose the transient state, it is no longer used
+            transientStateMgr.disposeItemStateInAttic(transientState);
+        }
+
+        // remove operations just processed
+        transientStateMgr.disposeOperations(changeLog.getOperations());
+    }
+
+    /**
+     * This will undo all changes made to <code>state</code> and descendant
+     * items of <code>state</code> inside this item state manager.
+     *
+     * @param state the root state of the cancel operation.
+     * @throws ItemStateException if undoing changes made to <code>state</code>
+     *                            and descendant items is not a closed set of
+     *                            changes. That is, at least another item needs
+     *                            to be canceled as well in another sub-tree.
+     */
+    public void undo(ItemState state) throws ItemStateException {
+        if (rootId.equals(state.getId())) {
+            // optimization for root
+            transientStateMgr.disposeAllItemStates();
+            return;
+        }
+
+        // list of transient items that should be discarded
+        ChangeLog changeLog = new TransientChangeLog(idFactory);
+
+        // check status of current item's state
+        if (state.isTransient()) {
+            switch (state.getStatus()) {
+                case ItemState.STATUS_STALE_MODIFIED:
+                case ItemState.STATUS_STALE_DESTROYED:
+                case ItemState.STATUS_EXISTING_MODIFIED:
+                    // add this item's state to the list
+                    changeLog.modified(state);
+                    break;
+                default:
+                    log.debug("unexpected state status (" + state.getStatus() + ")");
+                    // ignore
+                    break;
+            }
+        }
+
+        if (state.isNode()) {
+            NodeId nodeId = ((NodeState)state).getNodeId();
+            // build list of 'new', 'modified' or 'stale' descendants
+            Iterator iter = getDescendantTransientItemStates(nodeId);
+            while (iter.hasNext()) {
+                ItemState childState = (ItemState) iter.next();
+                switch (childState.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
+                        changeLog.modified(childState);
+                        break;
+
+                    default:
+                        log.debug("unexpected state status (" + childState.getStatus() + ")");
+                        // ignore
+                        break;
+                }
+            }
+
+            // build list of deleted states
+            Iterator atticIter = getDescendantTransientItemStatesInAttic(nodeId);
+            while (atticIter.hasNext()) {
+                ItemState transientState = (ItemState) atticIter.next();
+                changeLog.deleted(transientState);
+            }
+        }
+
+        /**
+         * build set of item id's which are within the scope of
+         * (i.e. affected by) this cancel operation
+         */
+        Set affectedIds = new HashSet();
+        Iterator it = new IteratorChain(changeLog.modifiedStates(), changeLog.deletedStates());
+        while (it.hasNext()) {
+            affectedIds.add(((ItemState) it.next()).getId());
+        }
+        collectOperations(affectedIds, changeLog);
+
+        // process list of 'new', 'modified' or 'stale' transient states
+        Iterator transIter = changeLog.modifiedStates();
+        while (transIter.hasNext()) {
+            // 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
+            ItemState transientState = (ItemState) transIter.next();
+            switch (transientState.getStatus()) {
+                case ItemState.STATUS_STALE_MODIFIED:
+                case ItemState.STATUS_STALE_DESTROYED:
+                case ItemState.STATUS_EXISTING_MODIFIED:
+                    transientState.notifyStateUncovering();
+            }
+            transientStateMgr.disposeItemState(transientState);
+        }
+        // process list of deleted states
+        Iterator remIter = changeLog.deletedStates();
+        while (remIter.hasNext()) {
+            ItemState rmState = (ItemState) remIter.next();
+            // dispose the transient state; this will indirectly (through
+            // stateDiscarded listener method) resurrect the wrapping Item instances
+            transientStateMgr.disposeItemStateInAttic(rmState);
+        }
+
+        // remove all canceled operations
+        Iterator opIter = changeLog.getOperations();
+        while (opIter.hasNext()) {
+            transientStateMgr.removeOperation((Operation) opIter.next());
+        }
+    }
+
+    /**
+     * Adjust references at the end of a successful {@link Session#importXML(String, InputStream, int) XML import}.
+     *
+     * @param refTracker
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     */
+    public void adjustReferences(ReferenceChangeTracker refTracker) throws ConstraintViolationException, RepositoryException {
+        Iterator it = refTracker.getReferences();
+        while (it.hasNext()) {
+            PropertyState propState = (PropertyState) it.next();
+            // DIFF JR: remove check (already asserted on processReference)
+            boolean modified = false;
+            QValue[] values = propState.getValues();
+            QValue[] newVals = new QValue[values.length];
+            for (int i = 0; i < values.length; i++) {
+                QValue val = values[i];
+                QValue adjusted = refTracker.getMappedReference(val);
+                if (adjusted != null) {
+                    newVals[i] = adjusted;
+                    modified = true;
+                } else {
+                    // reference doesn't need adjusting, just copy old value
+                    newVals[i] = val;
+                }
+            }
+            if (modified) {
+                setPropertyStateValue(propState, newVals, PropertyType.REFERENCE);
+            }
+        }
+        // make sure all entries are removed
+        refTracker.clear();
+    }
+    //-------------------------------------------< Transient state handling >---
+    /**
+     * Returns an iterator over those transient item state instances that are
+     * direct or indirect descendents of the item state with the given
+     * <code>parentId</code>. The transient item state instance with the given
+     * <code>parentId</code> itself (if there is such) will not be included.
+     * <p/>
+     * The instances are returned in depth-first tree traversal order.
+     *
+     * @param parentId the id of the common parent of the transient item state
+     *                 instances to be returned.
+     * @return an iterator over descendant transient item state instances
+     */
+    private Iterator getDescendantTransientItemStates(NodeId parentId) {
+        // DIFF JACKRABBIT: if (!transientStateMgr.hasAnyItemStates()) {
+        if (transientStateMgr.getEntriesCount() == 0) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+
+        // build ordered collection of descendant transient states
+        // sorted by decreasing relative depth
+
+        // use an array of lists to group the descendants by relative depth;
+        // the depth is used as array index
+        List[] la = new List[10];
+        try {
+            Iterator iter = transientStateMgr.getEntries();
+            while (iter.hasNext()) {
+                ItemState state = (ItemState) iter.next();
+                // determine relative depth: > 0 means it's a descendant
+                int depth;
+                try {
+                    depth = hierMgr.getRelativeDepth(parentId, state.getId());
+                } catch (ItemNotFoundException infe) {
+                    /**
+                     * one of the parents of the specified item has been
+                     * removed externally; as we don't know its path,
+                     * we can't determine if it is a descendant;
+                     * InvalidItemStateException should only be thrown if
+                     * a descendant is affected;
+                     * => throw InvalidItemStateException for now
+                     * todo FIXME
+                     */
+                    // unable to determine relative depth, assume that the item
+                    // (or any of its ancestors) has been removed externally
+                    String msg = state.getId()
+                            + ": the item seems to have been removed externally.";
+                    log.debug(msg);
+                    throw new InvalidItemStateException(msg);
+                }
+
+                if (depth < 1) {
+                    // not a descendant
+                    continue;
+                }
+
+                // ensure capacity
+                if (depth > la.length) {
+                    List old[] = la;
+                    la = new List[depth + 10];
+                    System.arraycopy(old, 0, la, 0, old.length);
+                }
+
+                List list = la[depth - 1];
+                if (list == null) {
+                    list = new ArrayList();
+                    la[depth - 1] = list;
+                }
+                list.add(state);
+            }
+        } catch (RepositoryException re) {
+            log.warn("inconsistent hierarchy state", re);
+        }
+        // create an iterator over the collected descendants
+        // in decreasing depth order
+        IteratorChain resultIter = new IteratorChain();
+        for (int i = la.length - 1; i >= 0; i--) {
+            List list = la[i];
+            if (list != null) {
+                resultIter.addIterator(list.iterator());
+            }
+        }
+        /**
+         * if the resulting iterator chain is empty return
+         * EMPTY_LIST.iterator() instead because older versions
+         * of IteratorChain (pre Commons Collections 3.1)
+         * would throw UnsupportedOperationException in this
+         * situation
+         */
+        if (resultIter.getIterators().isEmpty()) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+        return resultIter;
+    }
+
+    /**
+     * Same as <code>{@link #getDescendantTransientItemStates(NodeId)}</code>
+     * except that item state instances in the attic are returned.
+     *
+     * @param parentId the id of the common parent of the transient item state
+     *                 instances to be returned.
+     * @return an iterator over descendant transient item state instances in the attic
+     */
+    private Iterator getDescendantTransientItemStatesInAttic(NodeId parentId) {
+        // DIFF JACKRABBIT: if (!transientStateMgr.hasAnyItemStatesInAttic()) {
+        if (transientStateMgr.getEntriesInAtticCount() == 0) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+
+        // build ordered collection of descendant transient states in attic
+        // sorted by decreasing relative depth
+
+        // use a special attic-aware hierarchy manager
+        ZombieHierarchyManager zombieHierMgr =
+                new ZombieHierarchyManager(hierMgr.getRootNodeId(),
+                        this,
+                        transientStateMgr.getAttic(),
+                        hierMgr.getNamespaceResolver());
+
+        // use an array of lists to group the descendants by relative depth;
+        // the depth is used as array index
+        List[] la = new List[10];
+        try {
+            Iterator iter = transientStateMgr.getEntriesInAttic();
+            while (iter.hasNext()) {
+                ItemState state = (ItemState) iter.next();
+                // determine relative depth: > 0 means it's a descendant
+                int depth = zombieHierMgr.getRelativeDepth(parentId, state.getId());
+                if (depth < 1) {
+                    // not a descendant
+                    continue;
+                }
+
+                // ensure capacity
+                if (depth > la.length) {
+                    List old[] = la;
+                    la = new List[depth + 10];
+                    System.arraycopy(old, 0, la, 0, old.length);
+                }
+
+                List list = la[depth - 1];
+                if (list == null) {
+                    list = new ArrayList();
+                    la[depth - 1] = list;
+                }
+                list.add(state);
+            }
+        } catch (RepositoryException re) {
+            log.warn("inconsistent hierarchy state", re);
+        }
+        // create an iterator over the collected descendants
+        // in decreasing depth order
+        IteratorChain resultIter = new IteratorChain();
+        for (int i = la.length - 1; i >= 0; i--) {
+            List list = la[i];
+            if (list != null) {
+                resultIter.addIterator(list.iterator());
+            }
+        }
+        /**
+         * if the resulting iterator chain is empty return
+         * EMPTY_LIST.iterator() instead because older versions
+         * of IteratorChain (pre Commons Collections 3.1)
+         * would throw UnsupportedOperationException in this
+         * situation
+         */
+        if (resultIter.getIterators().isEmpty()) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+        return resultIter;
+    }
+
+    /**
+     *
+     * @param itemState
+     * @return
+     * @throws StaleItemStateException
+     * @throws ItemStateException
+     */
+    private ChangeLog getChangeLog(ItemState itemState) throws StaleItemStateException, ItemStateException {
+        ChangeLog changeLog = new TransientChangeLog(idFactory);
+        if (rootId.equals(itemState.getId())) {
+            // get all item states
+            for (Iterator it = transientStateMgr.addedStates(); it.hasNext(); ) {
+                changeLog.added((ItemState) it.next());
+            }
+            for (Iterator it = transientStateMgr.modifiedStates(); it.hasNext(); ) {
+                changeLog.modified((ItemState) it.next());
+            }
+            for (Iterator it = transientStateMgr.deletedStates(); it.hasNext(); ) {
+                changeLog.deleted((ItemState) it.next());
+            }
+            for (Iterator it = transientStateMgr.getOperations(); it.hasNext(); ) {
+                changeLog.addOperation((Operation) it.next());
+            }
+        } else {
+            // build changelog for affected and decendant states only
+            collectTransientStates(itemState, changeLog);
+            collectRemovedStates(itemState, changeLog);
+
+            /**
+             * build set of item id's which are within the scope of
+             * (i.e. affected by) this save operation
+             */
+            Iterator it = new IteratorChain(changeLog.modifiedStates(), changeLog.deletedStates());
+            Set affectedIds = new HashSet();
+            while (it.hasNext()) {
+                affectedIds.add(((ItemState) it.next()).getId());
+            }
+
+            checkIsSelfContained(affectedIds, changeLog);
+            collectOperations(affectedIds, changeLog);
+        }
+        return changeLog;
+    }
+
+    /**
+     * DIFF JACKRABBIT: copied and adapted from ItemImpl.getRemovedStates()
+     * <p/>
+     * Builds a list of transient descendant item states in the attic
+     * (i.e. those marked as 'removed') that are within the scope of
+     * <code>root</code>.
+     *
+     * @throws StaleItemStateException
+     */
+    private void collectRemovedStates(ItemState root, ChangeLog changeLog)
+        throws StaleItemStateException {
+        ItemState transientState;
+        if (root.isNode()) {
+            Iterator iter = getDescendantTransientItemStatesInAttic((NodeId) root.getId());
+            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 StaleItemStateException(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 StaleItemStateException(msg);
+                }
+                changeLog.deleted(transientState);
+            }
+        }
+    }
+
+    /**
+     * DIFF JACKRABBIT: copied and adapted from ItemImpl.getTransientStates()
+     * <p/>
+     * Builds a list of transient (i.e. new or modified) item states that are
+     * within the scope of <code>state</code>.
+     *
+     * @throws StaleItemStateException
+     * @throws ItemStateException
+     */
+    private void collectTransientStates(ItemState state, ChangeLog changeLog)
+            throws StaleItemStateException, ItemStateException {
+        // list of transient states that should be persisted
+        ItemState transientState;
+
+        // fail-fast test: check status of this item's state
+        if (state.isTransient()) {
+            switch (state.getStatus()) {
+                case ItemState.STATUS_EXISTING_MODIFIED:
+                    // add this item's state to the list
+                    changeLog.modified(state);
+                    break;
+
+                case ItemState.STATUS_NEW:
+                    {
+                        String msg = hierMgr.safeGetJCRPath(state.getId()) + ": cannot save a new item.";
+                        log.debug(msg);
+                        throw new ItemStateException(msg);
+                    }
+
+                case ItemState.STATUS_STALE_MODIFIED:
+                    {
+                        String msg = hierMgr.safeGetJCRPath(state.getId()) + ": the item cannot be saved because it has been modified externally.";
+                        log.debug(msg);
+                        throw new StaleItemStateException(msg);
+                    }
+
+                case ItemState.STATUS_STALE_DESTROYED:
+                    {
+                        String msg = hierMgr.safeGetJCRPath(state.getId()) + ": the item cannot be saved because it has been deleted externally.";
+                        log.debug(msg);
+                        throw new StaleItemStateException(msg);
+                    }
+
+                case ItemState.STATUS_UNDEFINED:
+                    {
+                        String msg = hierMgr.safeGetJCRPath(state.getId()) + ": the item cannot be saved; it seems to have been removed externally.";
+                        log.debug(msg);
+                        throw new StaleItemStateException(msg);
+                    }
+
+                default:
+                    log.debug("unexpected state status (" + state.getStatus() + ")");
+                    // ignore
+                    break;
+            }
+        }
+
+        if (state.isNode()) {
+            // build list of 'new' or 'modified' descendants
+            Iterator iter = getDescendantTransientItemStates((NodeId) state.getId());
+            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
+                        changeLog.modified(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 StaleItemStateException(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 StaleItemStateException(msg);
+                        }
+
+                    case ItemState.STATUS_UNDEFINED:
+                        {
+                            String msg = transientState.getId() + ": the item cannot be saved; it seems to have been removed externally.";
+                            log.debug(msg);
+                            throw new StaleItemStateException(msg);
+                        }
+
+                    default:
+                        log.debug("unexpected state status (" + transientState.getStatus() + ")");
+                        // ignore
+                        break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Retuns a list of operations that are in the scope the the change set
+     * defined by the affected <code>itemIds</code>.
+     *
+     * @param affectedIds
+     * @param changeLog
+     */
+    private void collectOperations(Set affectedIds, ChangeLog changeLog) {
+        Iterator opsIter = transientStateMgr.getOperations();
+        while (opsIter.hasNext()) {
+            Operation op = (Operation) opsIter.next();
+            Iterator ids = op.getAffectedItemIds().iterator();
+            while (ids.hasNext()) {
+                ItemId id = (ItemId) ids.next();
+                if (affectedIds.contains(id)) {
+                    changeLog.addOperation(op);
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Make sure that this save operation is totally 'self-contained'
+     * and independant; items within the scope of this update operation
+     * must not have 'external' dependencies;
+     * (e.g. moving a node requires that the target node including both
+     * old and new parents are saved)
+     *
+     * @param affectedIds
+     * @param changeLog
+     */
+    private void checkIsSelfContained(Set affectedIds, ChangeLog changeLog) throws ItemStateException {
+        Iterator it = new IteratorChain(changeLog.modifiedStates(), changeLog.deletedStates());
+        while (it.hasNext()) {
+            ItemState transientState = (ItemState) it.next();
+            if (transientState.isNode()) {
+                NodeState nodeState = (NodeState) transientState;
+                Set dependentIDs = new HashSet();
+                if (nodeState.hasOverlayedState()) {
+                    NodeId oldParentId = nodeState.getOverlayedState().getParentId();
+                    NodeId newParentId = nodeState.getParentId();
+                    if (oldParentId != null) {
+                        if (newParentId == null) {
+                            // node has been removed, add old parent
+                            // to dependencies
+                            dependentIDs.add(oldParentId);
+                        } else {
+                            if (!oldParentId.equals(newParentId)) {
+                                // node has been moved, add old and new parent
+                                // to dependencies
+                                dependentIDs.add(oldParentId);
+                                dependentIDs.add(newParentId);
+                            }
+                        }
+                    }
+                }
+                // removed child node entries
+                Iterator cneIt = nodeState.getRemovedChildNodeEntries().iterator();
+                while (cneIt.hasNext()) {
+                    NodeState.ChildNodeEntry cne = (NodeState.ChildNodeEntry) cneIt.next();
+                    dependentIDs.add(cne.getId());
+                }
+                // added child node entries
+                cneIt = nodeState.getAddedChildNodeEntries().iterator();
+                while (cneIt.hasNext()) {
+                    NodeState.ChildNodeEntry cne = (NodeState.ChildNodeEntry) cneIt.next();
+                    dependentIDs.add(cne.getId());
+                }
+
+                // now walk through dependencies and check whether they
+                // are within the scope of this save operation
+                Iterator depIt = dependentIDs.iterator();
+                while (depIt.hasNext()) {
+                    NodeId id = (NodeId) depIt.next();
+                    if (!affectedIds.contains(id)) {
+                        // need to save the parent as well
+                        String msg = hierMgr.safeGetJCRPath(id)
+                            + " needs to be saved as well.";
+                        log.debug(msg);
+                        throw new ItemStateException(msg);
+                    }
+                }
+            }
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * @inheritDoc
+     */
+    public void visit(AddNode operation) throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        int options = ItemStateValidator.CHECK_LOCK | ItemStateValidator.CHECK_COLLISION
+            | ItemStateValidator.CHECK_VERSIONING | ItemStateValidator.CHECK_CONSTRAINTS;
+
+        NodeState parent = validator.getNodeState(operation.getParentId());
+        QNodeDefinition def = validator.getApplicableNodeDefinition(operation.getNodeName(), operation.getNodeTypeName(), parent);
+        addNodeState(parent, operation.getNodeName(), operation.getNodeTypeName(), operation.getUuid(), def, options);
+
+        transientStateMgr.addOperation(operation);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(AddProperty operation) throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        NodeState parent = validator.getNodeState(operation.getParentId());
+        QName propertyName = operation.getPropertyName();
+        QPropertyDefinition pDef = validator.getApplicablePropertyDefinition(propertyName, operation.getPropertyType(), operation.isMultiValued(), parent);
+        int targetType = pDef.getRequiredType();
+        if (targetType == PropertyType.UNDEFINED) {
+            targetType = operation.getPropertyType();
+            if (targetType == PropertyType.UNDEFINED) {
+                targetType = PropertyType.STRING;
+            }
+        }
+        int options = ItemStateValidator.CHECK_LOCK
+            | ItemStateValidator.CHECK_COLLISION
+            | ItemStateValidator.CHECK_VERSIONING
+            | ItemStateValidator.CHECK_CONSTRAINTS;
+        addPropertyState(parent, propertyName, targetType, operation.getValues(), pDef, options);
+
+        transientStateMgr.addOperation(operation);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(Clone operation) throws NoSuchWorkspaceException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(Copy operation) throws NoSuchWorkspaceException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(Move operation) throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+
+        // retrieve states and assert they are modifiable
+        NodeState srcState = create(validator.getNodeState(operation.getNodeId()));
+        NodeState srcParent = create(validator.getNodeState(operation.getSourceParentId()));
+
+        NodeState destParent = create(validator.getNodeState(operation.getDestinationParentId()));
+
+        // state validation: move-Source can be removed from old/added to new parent
+        validator.checkRemoveItem(srcState,
+            ItemStateValidator.CHECK_ACCESS
+            | ItemStateValidator.CHECK_LOCK
+            | ItemStateValidator.CHECK_VERSIONING
+            | ItemStateValidator.CHECK_CONSTRAINTS);
+        validator.checkAddNode(destParent, operation.getDestinationName(),
+            srcState.getNodeTypeName(),
+            ItemStateValidator.CHECK_ACCESS
+            | ItemStateValidator.CHECK_LOCK
+            | ItemStateValidator.CHECK_VERSIONING
+            | ItemStateValidator.CHECK_CONSTRAINTS);
+        // retrieve applicable definition at the new place
+        // TODO: improve. definition has already retrieve within the checkAddNode...
+        QNodeDefinition newDefinition = validator.getApplicableNodeDefinition(operation.getDestinationName(), srcState.getNodeTypeName(), destParent);
+
+        // perform the move (modifying states)
+        // TODO: TO-BE-FIXED. Move with SPI id       
+        boolean renameOnly = srcParent.getNodeId().equals(destParent.getNodeId());
+        NodeState.ChildNodeEntry cne = srcParent.getChildNodeEntry(srcState.getNodeId());
+        QName srcName = cne.getName();
+        int srcIndex = cne.getIndex();
+        if (renameOnly) {
+            // change child node entry
+            destParent.renameChildNodeEntry(srcName, srcIndex, operation.getDestinationName());
+        } else {
+            // remove child node entry from old parent
+            srcParent.removeChildNodeEntry(srcName, srcIndex);
+            // re-parent target node
+            srcState.setParentId(destParent.getNodeId());
+            // add child node entry to new parent
+            destParent.addChildNodeEntry(operation.getDestinationName(), srcState.getNodeId());
+        }
+
+        // change definition of target node
+        srcState.setDefinition(newDefinition);
+
+        // store states
+        store(srcState);
+        if (renameOnly) {
+            store(srcParent);
+        } else {
+            store(srcParent);
+            store(destParent);
+        }
+
+        // remember operation
+        transientStateMgr.addOperation(operation);
+    }
+
+    public void visit(Update operation) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(Remove operation) throws ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        try {
+            ItemState state = getItemState(operation.getRemoveId());
+            int options = ItemStateValidator.CHECK_LOCK
+                | ItemStateValidator.CHECK_VERSIONING
+                | ItemStateValidator.CHECK_CONSTRAINTS;
+            removeItemState(state, options);
+            // remember operation
+            transientStateMgr.addOperation(operation);
+        } catch (NoSuchItemStateException e) {
+            throw new PathNotFoundException(e);
+        } catch (ItemStateException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(SetMixin operation) throws ConstraintViolationException, AccessDeniedException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        // remember if an existing mixin is being removed.
+        boolean anyRemoved;
+
+        QName[] mixinNames = operation.getMixinNames();
+        NodeState nState = create(validator.getNodeState(operation.getNodeId()));
+
+        // mixin-names to be execute on the nodestate (and corresponding property state)
+        if (mixinNames != null && mixinNames.length > 0) {
+            // find out if any of the existing mixins is removed
+            List originalMixins = Arrays.asList(nState.getMixinTypeNames());
+            originalMixins.removeAll(Arrays.asList(mixinNames));
+            anyRemoved = originalMixins.size() > 0;
+
+            // update nodestate
+            nState.setMixinTypeNames(mixinNames);
+
+            // update/create corresponding property state
+            if (nState.hasPropertyName(QName.JCR_MIXINTYPES)) {
+                // execute value of existing property
+                PropertyState pState = validator.getPropertyState(nState.getNodeId(), QName.JCR_MIXINTYPES);
+                setPropertyStateValue(pState, QValue.create(mixinNames), PropertyType.NAME);
+            } else {
+                // create new jcr:mixinTypes property
+                EffectiveNodeType ent = validator.getEffectiveNodeType(nState);
+                QPropertyDefinition pd = ent.getApplicablePropertyDefinition(QName.JCR_MIXINTYPES, PropertyType.NAME, true);
+                QValue[] mixinValue = QValue.create(nState.getMixinTypeNames());
+                int options = 0; // nothing to check
+                addPropertyState(nState, pd.getQName(), pd.getRequiredType(), mixinValue, pd, options);
+            }
+        } else {
+            anyRemoved = nState.getMixinTypeNames().length > 0;
+            // remove all mixins
+            nState.setMixinTypeNames(null);
+
+            // remove the jcr:mixinTypes property state if already present
+            if (nState.hasPropertyName(QName.JCR_MIXINTYPES)) {
+                PropertyState pState = validator.getPropertyState(nState.getNodeId(), QName.JCR_MIXINTYPES);
+                int options = 0; // no checks required
+                removeItemState(pState, options);
+            } else {
+                // alternative: make sure changes on nodeState are reflected in state manager.
+                store(nState);
+            }
+        }
+
+        // make sure, the modification of the mixin set did not left child-item
+        // states defined by the removed mixin type(s)
+        // TODO: the following block should be delegated to 'server' - side.
+        if (anyRemoved) {
+            EffectiveNodeType ent = validator.getEffectiveNodeType(nState);
+            // use temp set to avoid ConcurrentModificationException
+            Iterator childProps = new HashSet(nState.getPropertyNames()).iterator();
+            while (childProps.hasNext()) {
+                PropertyState childState = validator.getPropertyState(nState.getNodeId(), (QName) childProps.next());
+                QName declNtName = childState.getDefinition().getDeclaringNodeType();
+                // check if property has been defined by mixin type (or one of its supertypes)
+                if (!ent.includesNodeType(declNtName)) {
+                    // the remaining effective node type doesn't include the
+                    // node type that declared this property, it is thus safe
+                    // to remove it
+                    int options = 0; // no checks required
+                    removeItemState(childState, options);
+                }
+            }
+            // use temp array to avoid ConcurrentModificationException
+            Iterator childNodes = new ArrayList(nState.getChildNodeEntries()).iterator();
+            while (childNodes.hasNext()) {
+                NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) childNodes.next();
+                NodeState childState = validator.getNodeState(entry.getId());
+                // check if node has been defined by mixin type (or one of its supertypes)
+                QName declNtName = childState.getDefinition().getDeclaringNodeType();
+                if (!ent.includesNodeType(declNtName)) {
+                    // the remaining effective node type doesn't include the
+                    // node type that declared this child node, it is thus safe
+                    // to remove it.
+                    int options = 0; // NOTE: referencial intergrity checked upon save.
+                    removeItemState(childState, options);
+                }
+            }
+        }
+
+        transientStateMgr.addOperation(operation);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(SetPropertyValue operation) throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        PropertyState pState = validator.getPropertyState(operation.getPropertyId());
+        setPropertyStateValue(pState, operation.getValues(), operation.getPropertyType());
+        transientStateMgr.addOperation(operation);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void visit(ReorderNodes operation) throws ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        // make sure the parent state is modifiable
+        NodeState parent = create(validator.getNodeState(operation.getParentId()));
+
+        NodeId srcId = operation.getInsertNodeId();
+        NodeId beforeId = operation.getBeforeNodeId();
+
+        // TODO: TO-BE-FIXED. Reorder with SPI-Id -> instable ids
+        ArrayList list = new ArrayList(parent.getChildNodeEntries());
+        int srcInd = -1, destInd = -1;
+        for (int i = 0; i < list.size(); i++) {
+            NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) list.get(i);
+            if (srcInd == -1) {
+                if (entry.getId().equals(srcId)) {
+                    srcInd = i;
+                }
+            }
+            if (destInd == -1 && beforeId != null) {
+                if (entry.getId().equals(beforeId)) {
+                    destInd = i;
+                    if (srcInd != -1) {
+                        break;
+                    }
+                }
+            } else {
+                if (srcInd != -1) {
+                    break;
+                }
+            }
+        }
+
+        // check if resulting order would be different to current order
+        if (destInd == -1) {
+            if (srcInd == list.size() - 1) {
+                // no change, we're done
+                return;
+            }
+        } else {
+            if ((destInd - srcInd) == Path.INDEX_DEFAULT) {
+                // no change, we're done
+                return;
+            }
+        }
+        // reorder list
+        if (destInd == -1) {
+            list.add(list.remove(srcInd));
+        } else {
+            if (srcInd < destInd) {
+                list.add(destInd, list.get(srcInd));
+                list.remove(srcInd);
+            } else {
+                list.add(destInd, list.remove(srcInd));
+            }
+        }
+
+        // modify the the parent node state ...
+        parent.setChildNodeEntries(list);
+        // ... and mark it as modified on the stateMgr.
+        store(parent);
+
+        // remember the operation
+        transientStateMgr.addOperation(operation);
+    }
+
+    public void visit(Checkout operation) throws RepositoryException, UnsupportedRepositoryOperationException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(Checkin operation) throws UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(Restore operation) throws VersionException, PathNotFoundException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(Merge operation) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(ResolveMergeConflict operation) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(LockOperation operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(LockRefresh operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(LockRelease operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(AddLabel operation) throws VersionException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    public void visit(RemoveLabel operation) throws VersionException, RepositoryException {
+        workspaceItemStateMgr.execute(operation);
+    }
+
+    //--------------------------------------------< Internal State Handling >---
+    /**
+     *
+     * @param parent
+     * @param propertyName
+     * @param propertyType
+     * @param values
+     * @param pDef
+     * @param options int used to validate the given params. Note, that the options
+     * differ depending if the 'addProperty' is called regularly or to create
+     * auto-created (or protected) properties.
+     * @throws LockException
+     * @throws ConstraintViolationException
+     * @throws AccessDeniedException
+     * @throws ItemExistsException
+     * @throws NoSuchNodeTypeException
+     * @throws UnsupportedRepositoryOperationException
+     * @throws VersionException
+     * @throws RepositoryException
+     */
+    private void addPropertyState(NodeState parent, QName propertyName,
+                                  int propertyType, QValue[] values,
+                                  QPropertyDefinition pDef, int options)
+        throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+
+        validator.checkAddProperty(parent, propertyName, pDef, options);
+
+        // make sure the arguements are consistent and do not violate the
+        // given property definition.
+        validator.validate(propertyType, values, pDef);
+
+        // assert transient parent state
+        NodeState parentState = create(parent);
+        // create property state
+        PropertyState propState = createNew(propertyName, parentState.getNodeId());
+        propState.setDefinition(pDef);
+
+        // NOTE: callers must make sure, the property type is not 'undefined'
+        propState.setType(propertyType);
+        propState.setMultiValued(pDef.isMultiple());
+        propState.setValues(values);
+
+        // now add new property entry to parent
+        parentState.addPropertyName(propertyName);
+        // store parent
+        store(parentState);
+        // store property
+        store(propState);
+    }
+
+    private void addNodeState(NodeState parent, QName nodeName, QName nodeTypeName, String uuid, QNodeDefinition definition, int options) throws RepositoryException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
+
+        // TODO: improve...
+        // check if add node is possible. note, that the options differ if
+        // the 'addNode' is called from inside a regular add-node to create
+        // autocreated child nodes that my are 'protected' by their def.
+        validator.checkAddNode(parent, nodeName, nodeTypeName, options);
+
+        try {
+            validator.getEffectiveNodeType(new QName[]{nodeTypeName});
+        } catch (NodeTypeConflictException e) {
+            throw new RepositoryException("node type conflict: " + e.getMessage());
+        }
+        if (nodeTypeName == null) {
+            // no primary node type specified,
+            // try default primary type from definition
+            nodeTypeName = definition.getDefaultPrimaryType();
+            if (nodeTypeName == null) {
+                String msg = "an applicable node type could not be determined for " + nodeName;
+                log.debug(msg);
+                throw new ConstraintViolationException(msg);
+            }
+        }
+
+        // assert parent state is transient
+        NodeState parentState = create(parent);
+        // ev. create new id
+        NodeId newId = (uuid == null) ? idFactory.createNodeId(UUID.randomUUID().toString()) : idFactory.createNodeId(uuid);
+        NodeState nodeState = createNew(newId, nodeTypeName, parentState.getNodeId());
+        nodeState.setDefinition(definition);
+
+        // now add new child node entry to parent
+        parentState.addChildNodeEntry(nodeName, newId);
+
+        EffectiveNodeType ent = validator.getEffectiveNodeType(nodeState);
+        // add 'auto-create' properties defined in node type
+        QPropertyDefinition[] pda = ent.getAutoCreatePropDefs();
+        for (int i = 0; i < pda.length; i++) {
+            QPropertyDefinition pd = pda[i];
+            QValue[] autoValue = computeSystemGeneratedPropertyValues(nodeState, pd);
+            int propOptions = 0; // nothing to check
+            // execute 'addProperty' without adding operation.
+            addPropertyState(nodeState, pd.getQName(), pd.getRequiredType(), autoValue, pd, propOptions);
+        }
+
+        // recursively add 'auto-create' child nodes defined in node type
+        QNodeDefinition[] nda = ent.getAutoCreateNodeDefs();
+        for (int i = 0; i < nda.length; i++) {
+            QNodeDefinition nd = nda[i];
+            // execute 'addNode' without validation and adding operation.
+            int opt = ItemStateValidator.CHECK_LOCK | ItemStateValidator.CHECK_COLLISION;
+            addNodeState(nodeState, nd.getQName(), nd.getDefaultPrimaryType(), null, nd, opt);
+        }
+
+        // store node
+        store(nodeState);
+        // store parent
+        store(parentState);
+    }
+
+    // TODO: TO-BE-FIXED. removal of same-name-sibling node must include reordering
+    private void removeItemState(ItemState itemState, int options) throws RepositoryException {
+        // DIFF JR: check for both, node- and propertyState
+        validator.checkRemoveItem(itemState, options);
+
+        // recursively remove the complete tree including the given node state.
+        boolean success = false;
+        try {
+            // assert parent is transient state
+            NodeState parent = create(validator.getNodeState(itemState.getParentId()));
+            if (itemState.isNode()) {
+                removeNodeState(parent, (NodeState)itemState);
+            } else {
+                removePropertyState(parent, (PropertyState)itemState);
+            }
+            // store parent
+            // DIFF JR: only store parent if removal is successful... check if correct.
+            store(parent);
+            success = true;
+        } finally {
+            if (!success) {
+                // TODO: undo state modifications
+            }
+        }
+    }
+
+    /**
+     * Unlinks the specified node state from its parent and recursively
+     * removes it including its properties and child nodes.
+     * <p/>
+     * Note that no checks (access rights etc.) are performed on the specified
+     * target node state. Those checks have to be performed beforehand by the
+     * caller. However, the (recursive) removal of target node's child nodes are
+     * subject to the following checks: access rights, locking, versioning.
+     *
+     * @param target
+     */
+    private void removeNodeState(NodeState parent, NodeState target) throws ItemNotFoundException, RepositoryException {
+        NodeState modifiableTarget = create(target);
+        // remove child node entry from parent
+        parent.removeChildNodeEntry(modifiableTarget.getNodeId());
+        // remove target
+        recursiveRemoveNodeState(modifiableTarget);
+    }
+
+    /**
+     *
+     * @param parent
+     * @param target
+     */
+    private void removePropertyState(NodeState parent, PropertyState target) {
+        PropertyState modifiableTarget = create(target);
+        // remove property entry
+        parent.removePropertyName(modifiableTarget.getName());
+        // destroy property state
+        destroy(modifiableTarget);
+    }
+
+    /**
+     * Recursively removes the given node state including its child states.
+     * <p/>
+     * The removal of child nodes is subject to the following checks:
+     * access rights, locking & versioning status. Referential integrity
+     * (references) is checked on commit.
+     * <p/>
+     * Note that the child node entry refering to <code>targetState</code> is
+     * <b><i>not</i></b> automatically removed from <code>targetState</code>'s
+     * parent.
+     *
+     * // TODO fix description
+     *
+     * @param targetState
+     */
+    private void recursiveRemoveNodeState(NodeState targetState) throws RepositoryException  {
+        if (targetState.hasChildNodeEntries()) {
+            // remove child nodes
+            // use temp array to avoid ConcurrentModificationException
+            Iterator tmpIter = new ArrayList(targetState.getChildNodeEntries()).iterator();
+            // remove from tail to avoid problems with same-name siblings
+            while (tmpIter.hasNext()) {
+                NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) tmpIter.next();
+                try {
+                    NodeState child = validator.getNodeState(entry.getId());
+                    // remove child node
+                    // DIFF JR: don't recheck permission for child states
+                    // DIFF JR: jr first calls recursive-method then removes c-n-entry
+                    removeNodeState(targetState, child);
+                } catch (ItemNotFoundException e) {
+                    // ignore
+                    // TODO: check if correct
+                }
+            }
+        }
+
+        // remove properties
+        Iterator tmpIter = new HashSet(targetState.getPropertyNames()).iterator();
+        while (tmpIter.hasNext()) {
+            QName propName = (QName) tmpIter.next();
+            try {
+                PropertyState child = validator.getPropertyState(targetState.getNodeId(), propName);
+                removePropertyState(targetState, child);
+            } catch (ItemNotFoundException e) {
+                // ignore
+                // TODO: check if correct
+            }
+        }
+
+        // now actually do unlink target state
+        targetState.setParentId(null);
+        // destroy target state
+        // DIFF JR: destroy targetState (not overlayed state)
+        destroy(targetState);
+    }
+
+    /**
+     *
+     * @param propState
+     * @param iva
+     * @param valueType
+     * @throws ValueFormatException
+     * @throws LockException
+     * @throws ConstraintViolationException
+     * @throws AccessDeniedException
+     * @throws ItemExistsException
+     * @throws UnsupportedRepositoryOperationException
+     * @throws VersionException
+     * @throws RepositoryException
+     */
+    private void setPropertyStateValue(PropertyState propState, QValue[] iva, int valueType) throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        // assert that the property can be modified.
+        // TODO improve
+        int options = ItemStateValidator.CHECK_LOCK //| ItemStateValidator.CHECK_COLLISION
+            | ItemStateValidator.CHECK_VERSIONING | ItemStateValidator.CHECK_CONSTRAINTS;
+        validator.checkSetProperty(propState, options);
+
+        // make sure property is valid according to its definition
+        validator.validate(valueType, iva, propState.getDefinition());
+
+        // make sure the state is modifiable
+        PropertyState propertyState = create(propState);
+
+        // free old values as necessary
+        QValue[] oldValues = propertyState.getValues();
+        if (oldValues != null) {
+            for (int i = 0; i < oldValues.length; i++) {
+                QValue old = oldValues[i];
+                if (old != null) {
+                    // make sure temporarily allocated data is discarded
+                    // before overwriting it (see QValue#discard())
+                    old.discard();
+                }
+            }
+        }
+
+        propertyState.setValues(iva);
+        propertyState.setType(valueType);
+
+        // store property
+        store(propertyState);
+    }
+
+    /**
+     * Creates a {@link NodeState} instance representing new,
+     * i.e. not yet existing state. Call {@link #store}
+     * on the returned object to make it persistent.
+     *
+     * @param id           the id of the node
+     * @param nodeTypeName qualified node type name
+     * @param parentId     parent node's id
+     * @return a node state
+     * @throws IllegalStateException if the manager is not in edit mode.
+     */
+    private NodeState createNew(NodeId id, QName nodeTypeName, NodeId parentId)
+            throws IllegalStateException {
+        // DIFF JACKRABBIT: return workspaceItemStateMgr.createNew(id, nodeTypeName, parentId);
+        return transientStateMgr.createNodeState(id, nodeTypeName, parentId);
+    }
+
+    /**
+     * Creates a modifiable {@link NodeState} instances representing an existing
+     * <code>state</code>. Call {@link #store(ItemState)} on the returned
+     * object to make it persistent.
+     * <p/>
+     * If <code>state</code> is a transient state, it is immediately returned.
+     * Otherwise, after a transient state has been created that overlays
+     * <code>state</code> {@link TransientItemStateListener} registered on
+     * <code>state</code> are notified about the overlay via the the callback
+     * {@link TransientItemStateListener#stateOverlaid(ItemState)}.
+     *
+     * @param state the node as retrieved with {@link #getItemState(ItemId)}
+     * @return a modifiable {@link NodeState}.
+     */
+    private NodeState create(NodeState state) {
+        if (state.isTransient()) {
+            // already transient state
+            return state;
+        }
+        NodeState transientState = transientStateMgr.createNodeState(state);
+        state.notifyStateOverlaid(transientState);
+        return transientState;
+    }
+
+    /**
+     * Creates a {@link PropertyState} instance representing new,
+     * i.e. not yet existing state. Call {@link #store}
+     * on the returned object to make it persistent.
+     *
+     * @param propName   qualified property name
+     * @param parentId   parent node Id
+     * @return a property state
+     */
+    private PropertyState createNew(QName propName, NodeId parentId) {
+        // DIFF JACKRABBIT: return workspaceItemStateMgr.createNew(propName, parentId);
+        return transientStateMgr.createPropertyState(parentId, propName);
+    }
+
+    /**
+     * Creates a modifiable {@link PropertyState} instances representing an existing
+     * <code>state</code>. Call {@link #store(ItemState)} on the returned
+     * object to make it persistent.
+     * <p/>
+     * If <code>state</code> is a transient state, it is immediately returned.
+     * Otherwise, after a transient state has been created that overlays
+     * <code>state</code> {@link TransientItemStateListener} registered on
+     * <code>state</code> are notified about the overlay via the the callback
+     * {@link TransientItemStateListener#stateOverlaid(ItemState)}.
+     *
+     * @param state the node as retrieved with {@link #getItemState(ItemId)}
+     * @return a modifiable {@link PropertyState}.
+     */
+    private PropertyState create(PropertyState state) {
+        if (state.isTransient()) {
+            // already transient state
+            return state;
+        }
+        PropertyState transientState = transientStateMgr.createPropertyState(state);
+        state.notifyStateOverlaid(transientState);
+        return transientState;
+    }
+
+    /**
+     * Store the given item state, which may be a new one previously created by
+     * calling <code>createNew</code> or a modified existing state.
+     *
+     * @param state item state that should be stored
+     */
+    private void store(ItemState state) throws IllegalStateException {
+        // DIFF JACKRABBIT: workspaceItemStateMgr.store(state);
+        if (state.getStatus() == ItemState.STATUS_EXISTING_MODIFIED) {
+            transientStateMgr.modified(state);
+        } else if (state.getStatus() == ItemState.STATUS_NEW) {
+            transientStateMgr.added(state);
+        } else {
+            // todo throw InvalidItemStateException?
+            throw new IllegalStateException("invalid state: " + state.getStatus());
+        }
+    }
+
+    /**
+     * Destroy an item state.
+     *
+     * @param state item state that should be destroyed
+     */
+    private void destroy(ItemState state) {
+        // DIFF JACKRABBIT: persistentStateMgr.destroy(state);
+        transientStateMgr.deleted(state);
+        // todo correct?
+        state.notifyStateDiscarded();
+    }
+
+
+    /**
+     * Computes the values of well-known system (i.e. protected) properties
+     * as well as auto-created properties which define default value(s)
+     *
+     * @param parent
+     * @param def
+     * @return the computed values
+     */
+    private QValue[] computeSystemGeneratedPropertyValues(NodeState parent,
+                                                                 QPropertyDefinition def)
+        throws RepositoryException {
+        QValue[] genValues = null;
+        /**
+         * todo: need to come up with some callback mechanism for applying system generated values
+         * (e.g. using a NodeTypeInstanceHandler interface)
+         */
+        String[] defaultValues = def.getDefaultValues();
+        if (defaultValues != null && defaultValues.length > 0) {
+            Value[] vs = ValueHelper.convert(defaultValues, def.getRequiredType(), valueFactory);
+            genValues = ValueFormat.getQValues(vs, nsResolver);
+        } else {
+            // some predefined nodetypes declare auto-created properties without
+            // default values
+            QName declaringNT = def.getDeclaringNodeType();
+            QName name = def.getQName();
+            if (QName.MIX_REFERENCEABLE.equals(declaringNT) && QName.JCR_UUID.equals(name)) {
+                // mix:referenceable node type defines jcr:uuid
+                genValues = new QValue[]{QValue.create(parent.getNodeId().getUUID().toString())};
+            } else if (QName.NT_BASE.equals(declaringNT)) {
+                // nt:base node type
+                if (QName.JCR_PRIMARYTYPE.equals(name)) {
+                    // jcr:primaryType property
+                    genValues = new QValue[]{QValue.create(parent.getNodeTypeName())};
+                } else if (QName.JCR_MIXINTYPES.equals(name)) {
+                    // jcr:mixinTypes property
+                    QName[] mixins = parent.getMixinTypeNames();
+                    genValues = new QValue[mixins.length];
+                    for (int i = 0; i < mixins.length; i++) {
+                        genValues[i] = QValue.create(mixins[i]);
+                    }
+                }
+            } else if (QName.NT_HIERARCHYNODE.equals(declaringNT) && QName.JCR_CREATED.equals(name)) {
+                // nt:hierarchyNode node type defines jcr:created property
+                genValues = new QValue[]{QValue.create(Calendar.getInstance())};
+            } else if (QName.NT_RESOURCE.equals(declaringNT) && QName.JCR_LASTMODIFIED.equals(name)) {
+                // nt:resource node type defines jcr:lastModified property
+                genValues = new QValue[]{QValue.create(Calendar.getInstance())};
+            } else if (QName.NT_VERSION.equals(declaringNT) && QName.JCR_CREATED.equals(name)) {
+                // nt:version node type defines jcr:created property
+                genValues = new QValue[]{QValue.create(Calendar.getInstance())};
+            }
+        }
+        return genValues;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/StaleItemStateException.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/StaleItemStateException.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/StaleItemStateException.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/StaleItemStateException.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,48 @@
+/*
+ * 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.jcr2spi.state;
+
+/**
+ * Signals that an item has been modified externally and that the item state
+ * representing it has thus become stale.
+ */
+public class StaleItemStateException extends ItemStateException {
+
+    /**
+     * Constructs a new instance of this class with the specified detail
+     * message.
+     *
+     * @param message the detail message. The detail message is saved for
+     *                later retrieval by the {@link #getMessage()} method.
+     */
+    public StaleItemStateException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new instance of this class with the specified detail
+     * message and root cause.
+     *
+     * @param message   the detail message. The detail message is saved for
+     *                  later retrieval by the {@link #getMessage()} method.
+     * @param rootCause root failure cause
+     */
+    public StaleItemStateException(String message, Throwable rootCause) {
+        super(message, rootCause);
+    }
+
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/StaleItemStateException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/StaleItemStateException.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientChangeLog.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientChangeLog.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientChangeLog.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientChangeLog.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,333 @@
+/*
+ * 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.jcr2spi.state;
+
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.IdFactory;
+import org.apache.commons.collections.iterators.IteratorChain;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+import java.util.Iterator;
+
+/**
+ * <code>TransientChangeLog</code> extends a {@link ChangeLog} and adds
+ * more methods that support transient changes (e.g. resurrect deleted state).
+ * Furthermore the item states of a transient change log are not disconnected
+ * when added.
+ */
+public class TransientChangeLog extends ChangeLog implements TransientItemStateManager {
+
+    // TODO: TO-BE-FIXED. Usage of SPI_ItemId requries different handling
+
+    /**
+     * Logger instance for this class.
+     */
+    private static final Logger log = LoggerFactory.getLogger(TransientChangeLog.class);
+
+    /**
+     *
+     */
+    private final IdFactory idFactory;
+
+    /**
+     * ItemStateManager view of the states in the attic; lazily instantiated
+     * in {@link #getAttic()}
+     */
+    private AtticItemStateManager attic;
+
+
+    TransientChangeLog(IdFactory idFactory) {
+        this.idFactory = idFactory;
+    }
+
+    //------------------< ChangeLog overwrites >--------------------------------
+
+    /**
+     * A state has been modified. If the state is not a new state
+     * (not in the collection of added ones), then add
+     * it to the modified states collection.
+     *
+     * @param state state that has been modified
+     */
+    public void modified(ItemState state) {
+        if (!addedStates.containsKey(state.getId())) {
+            modifiedStates.put(state.getId(), state);
+        }
+    }
+
+    /**
+     * A state has been deleted. If the state is not a new state
+     * (not in the collection of added ones), then remove
+     * it from the modified states collection and add it to the
+     * deleted states collection.
+     *
+     * @param state state that has been deleted
+     */
+    public void deleted(ItemState state) {
+        if (addedStates.remove(state.getId()) == null) {
+            modifiedStates.remove(state.getId());
+            deletedStates.put(state.getId(), state);
+        }
+    }
+
+    //-----------------< TransientItemStateManager >----------------------------
+
+    /**
+     * @inheritDoc
+     */
+    public int getEntriesCount() {
+        return addedStates.size() + modifiedStates.size();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public int getEntriesInAtticCount() {
+        return deletedStates.size();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public Iterator getEntries() {
+        IteratorChain it = new IteratorChain();
+        it.addIterator(modifiedStates());
+        it.addIterator(addedStates());
+        return it;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public Iterator getEntriesInAttic() {
+        return deletedStates();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public NodeState createNodeState(NodeId id,
+                                     QName nodeTypeName,
+                                     NodeId parentId) {
+        // DIFF JACKRABBIT: not needed anymore
+        // check map; synchronized to ensure an entry is not created twice.
+//        synchronized (addedStates) {
+//            if (addedStates.containsKey(id) || modifiedStates.containsKey(id)) {
+//                String msg = "there's already a node state instance with id " + id;
+//                log.debug(msg);
+//                throw new ItemStateException(msg);
+//            }
+//
+//            NodeState state = new NodeState(id, nodeTypeName, parentId,
+//                    initialStatus, true);
+//            // put transient state in the map
+//            addedStates.put(id, state);
+//            return state;
+//        }
+
+        return new NodeState(id, nodeTypeName, parentId, ItemState.STATUS_NEW, true, idFactory);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public NodeState createNodeState(NodeState overlayedState) {
+        ItemId id = overlayedState.getNodeId();
+
+        // check map; synchronized to ensure an entry is not created twice.
+        synchronized (addedStates) {
+            NodeState state;
+            if ((state = (NodeState) addedStates.get(id)) != null
+                    || (state = (NodeState) modifiedStates.get(id)) != null) {
+                String msg = "there's already a node state instance with id " + id;
+                log.warn(msg);
+                return state;
+            }
+
+            state = new NodeState(overlayedState, ItemState.STATUS_EXISTING_MODIFIED, true);
+            // put transient state in the map
+            modifiedStates.put(id, state);
+            return state;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public PropertyState createPropertyState(NodeId parentId, QName propName) {
+        PropertyId id = idFactory.createPropertyId(parentId, propName);
+        return new PropertyState(id, ItemState.STATUS_NEW, true);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public PropertyState createPropertyState(PropertyState overlayedState) {
+
+        PropertyId id = overlayedState.getPropertyId();
+
+        // check map; synchronized to ensure an entry is not created twice.
+        synchronized (addedStates) {
+            PropertyState state;
+            if ((state = (PropertyState) addedStates.get(id)) != null
+                    || (state = (PropertyState) modifiedStates.get(id)) != null) {
+                String msg = "there's already a property state instance with id " + id;
+                log.warn(msg);
+                return state;
+            }
+
+            state = new PropertyState(overlayedState, ItemState.STATUS_EXISTING_MODIFIED, true);
+            // put transient state in the map
+            modifiedStates.put(id, state);
+            return state;
+        }
+    }
+
+    /**
+     * Disposes a single item <code>state</code>. The state is discarded removed
+     * from the map of added or modified states and disconnected from the
+     * underlying state. This method does not take states into account that are
+     * marked as deleted.
+     *
+     * @param state the item state to dispose.
+     */
+    public void disposeItemState(ItemState state) {
+        state.discard();
+        if (addedStates.remove(state.getId()) == null) {
+            modifiedStates.remove(state.getId());
+        }
+        state.onDisposed();
+    }
+
+    /**
+     * A state has been deleted. If the state is not a new state
+     * (not in the collection of added ones), then remove
+     * it from the modified states collection.
+     * The state is added to the deleted states collection in any case.
+     *
+     * @param state state that has been deleted
+     */
+    public void moveItemStateToAttic(ItemState state) {
+        if (addedStates.remove(state.getId()) == null) {
+            modifiedStates.remove(state.getId());
+        }
+        deletedStates.put(state.getId(), state);
+    }
+
+    /**
+     * Disposes a single item <code>state</code> that is marked as deleted. The
+     * state is discarded removed from the map of removed states and
+     * disconnected from the underlying state.
+     *
+     * @param state the item state to dispose.
+     */
+    public void disposeItemStateInAttic(ItemState state) {
+        state.discard();
+        deletedStates.remove(state.getId());
+        state.onDisposed();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void disposeAllItemStates() {
+        IteratorChain it = new IteratorChain();
+        it.addIterator(modifiedStates());
+        it.addIterator(addedStates());
+        it.addIterator(deletedStates());
+        while (it.hasNext()) {
+            ItemState state = (ItemState) it.next();
+            state.discard();
+            state.onDisposed();
+        }
+        reset();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public ItemStateManager getAttic() {
+        if (attic == null) {
+            attic = new AtticItemStateManager();
+        }
+        return attic;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void disposeOperations(Iterator operations) {
+        while (operations.hasNext()) {
+            removeOperation((Operation) operations.next());
+        }
+    }
+
+    //--------------------------------------------------------< inner classes >
+
+    /**
+     * ItemStateManager view of the states in the attic
+     *
+     * @see TransientItemStateManager#getAttic
+     */
+    private class AtticItemStateManager implements ItemStateManager {
+
+        AtticItemStateManager() {
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public ItemState getItemState(ItemId id)
+                throws NoSuchItemStateException, ItemStateException {
+
+            ItemState state = (ItemState) deletedStates.get(id);
+            if (state != null) {
+                return state;
+            } else {
+                throw new NoSuchItemStateException(id.toString());
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean hasItemState(ItemId id) {
+            return deletedStates.containsKey(id);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public NodeReferences getNodeReferences(NodeId id)
+                throws NoSuchItemStateException, ItemStateException {
+            // n/a
+            throw new ItemStateException("getNodeReferences() not implemented");
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean hasNodeReferences(NodeId id) {
+            // n/a
+            return false;
+        }
+    }
+}