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/10/26 13:02:04 UTC

svn commit: r467956 [2/3] - in /jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi: ./ state/ version/

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java Thu Oct 26 04:02:02 2006
@@ -18,6 +18,7 @@
 
 import org.apache.commons.collections.list.AbstractLinkedList;
 import org.apache.commons.collections.iterators.UnmodifiableIterator;
+import org.apache.commons.collections.iterators.IteratorChain;
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.QNodeDefinition;
 import org.apache.jackrabbit.name.Path;
@@ -25,7 +26,6 @@
 import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.ItemId;
 import org.apache.jackrabbit.spi.Event;
-import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.jcr2spi.state.entry.ChildNodeEntry;
 import org.apache.jackrabbit.jcr2spi.state.entry.ChildPropertyEntry;
 import org.apache.jackrabbit.jcr2spi.state.entry.PropertyReference;
@@ -80,7 +80,7 @@
     /**
      * the names of this node's mixin types
      */
-    private QName[] mixinTypeNames = new QName[0];
+    private QName[] mixinTypeNames = QName.EMPTY_ARRAY;
 
     /**
      * insertion-ordered collection of ChildNodeEntry objects
@@ -106,12 +106,6 @@
     private NodeReferences references;
 
     /**
-     * The <code>ItemStateFactory</code> which is used to create new
-     * <code>ItemState</code> instances.
-     */
-    private final ItemStateFactory isf;
-
-    /**
      * Constructs a new node state that is not connected.
      *
      * @param name          the name of this NodeState
@@ -129,12 +123,12 @@
                         QName nodeTypeName, QNodeDefinition definition,
                         int initialStatus, ItemStateFactory isf,
                         IdFactory idFactory, boolean isWorkspaceState) {
-        super(parent, initialStatus, idFactory, isWorkspaceState);
+        super(parent, initialStatus, isf, idFactory, isWorkspaceState);
         this.name = name;
         this.uuid = uuid;
         this.nodeTypeName = nodeTypeName;
         this.definition = definition;
-        this.isf = isf;
+        assertAvailability();
     }
 
     /**
@@ -150,11 +144,28 @@
     protected NodeState(NodeState overlayedState, NodeState parent,
                         int initialStatus, ItemStateFactory isf,
                         IdFactory idFactory) {
-        super(overlayedState, parent, initialStatus, idFactory);
-        this.isf = isf;
-        reset();
+        super(overlayedState, parent, initialStatus, isf, idFactory);
+        if (overlayedState != null) {
+            synchronized (overlayedState) {
+                NodeState wspState = (NodeState) overlayedState;
+                name = wspState.name;
+                uuid = wspState.uuid;
+                nodeTypeName = wspState.nodeTypeName;
+                definition = wspState.definition;
+
+                init(wspState.getMixinTypeNames(), wspState.getChildNodeEntries(), wspState.getPropertyNames(), wspState.getNodeReferences());
+            }
+        }
+        assertAvailability();
     }
 
+    /**
+     *
+     * @param mixinTypeNames
+     * @param childEntries
+     * @param propertyNames
+     * @param references
+     */
     void init(QName[] mixinTypeNames, Collection childEntries, Collection propertyNames, NodeReferences references) {
         if (mixinTypeNames != null) {
             this.mixinTypeNames = mixinTypeNames;
@@ -165,19 +176,34 @@
         Iterator it = propertyNames.iterator();
         while (it.hasNext()) {
             QName propName = (QName) it.next();
-            properties.put(propName, PropertyReference.create(this, propName, isf, idFactory));
+            addPropertyEntry(PropertyReference.create(this, propName, isf, idFactory));
         }
         // re-create child node entries
         childNodeEntries.removeAll();
         it = childEntries.iterator();
         while (it.hasNext()) {
             ChildNodeEntry cne = (ChildNodeEntry) it.next();
-            childNodeEntries.add(cne.getName(), cne.getUUID());
+            childNodeEntries.add(cne.getName(), cne.getUUID(), cne.getIndex());
         }
         // set the node references
         this.references = references;
     }
 
+    private void assertAvailability() {
+        // TODO: improve this.
+        if (uuid != null) {
+            // make sure this state is connected to its childNode-entry
+            ChildNodeEntry cne = parent.childNodeEntries.get(uuid);
+            if (!cne.isAvailable()) {
+                try {
+                    cne.getNodeState();
+                } catch (ItemStateException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
     //----------------------------------------------------------< ItemState >---
     /**
      * Determines if this item state represents a node.
@@ -211,6 +237,7 @@
      * @return the id of this node state.
      */
     public NodeId getNodeId() {
+        NodeState parent = getParent();
         if (uuid != null) {
             return idFactory.createNodeId(uuid);
         } else if (parent != null) {
@@ -238,11 +265,30 @@
      * @return the UUID of this node state or <code>null</code> if this
      * node cannot be identified with a UUID.
      */
-    public final String getUUID() {
+    public String getUUID() {
         return uuid;
     }
 
     /**
+     * Modify the uuid of this state and make sure, that the parent state
+     * contains a proper childNodeEntry for this state. If the given uuid is
+     * not different from the uuid of this state, the method returns silently
+     * without changing neither the parent nor this state.
+     *
+     * @param uuid
+     */
+    private void setUUID(String uuid) {
+        String oldUUID = this.uuid;
+        boolean mod = (oldUUID == null) ? uuid != null : !oldUUID.equals(uuid);
+        if (mod) {
+            this.uuid = uuid;
+            if (getParent() != null) {
+                getParent().childNodeEntries.replaceEntry(this);
+            }
+        }
+    }
+
+    /**
      * Returns the name of this node's node type.
      *
      * @return the name of this node's node type.
@@ -295,11 +341,7 @@
      * @return references
      */
     NodeReferences getNodeReferences() {
-        if (getStatus() == Status.NEW) {
-            return null;
-        } else {
-            return references;
-        }
+        return references;
     }
 
     /**
@@ -504,6 +546,46 @@
     }
 
     /**
+     *
+     * @param propEntry
+     */
+    private void addPropertyEntry(ChildPropertyEntry propEntry) {
+        QName propName = propEntry.getName();
+        properties.put(propName, propEntry);
+        try {
+            if (isWorkspaceState() && isUuidOrMixin(propName)) {
+                if (QName.JCR_UUID.equals(propName) && uuid == null) {
+                    PropertyState ps = propEntry.getPropertyState();
+                    setUUID(ps.getValue().getString());
+                } else if (QName.JCR_MIXINTYPES.equals(propName) && (mixinTypeNames == null || mixinTypeNames.length == 0)) {
+                    PropertyState ps = propEntry.getPropertyState();
+                    mixinTypeNames = getMixinNames(ps);
+                }
+            }
+        } catch (ItemStateException e) {
+            log.error("Internal Error", e);
+        } catch (RepositoryException e) {
+            log.error("Internal Error", e);
+        }
+    }
+
+    /**
+     *
+     * @param propName
+     */
+    private void removePropertyEntry(QName propName) {
+        if (properties.remove(propName) != null) {
+            if (isWorkspaceState()) {
+                if (QName.JCR_UUID.equals(propName)) {
+                    setUUID(null);
+                } else if (QName.JCR_MIXINTYPES.equals(propName)) {
+                    mixinTypeNames = QName.EMPTY_ARRAY;
+                }
+            }
+        }
+    }
+
+    /**
      * TODO: find a better way to provide the index of a child node entry
      * Returns the index of the given <code>ChildNodeEntry</code> and with
      * <code>name</code>.
@@ -538,77 +620,37 @@
     /**
      *
      * @param event
-     * @param changeLog
-     * @see ItemState#refresh(Event, ChangeLog)
+     * @see ItemState#refresh(Event)
      */
-    synchronized void refresh(Event event, ChangeLog changeLog) {
+    synchronized void refresh(Event event) {
         checkIsWorkspaceState();
 
         NodeId id = getNodeId();
+        QName name = event.getQPath().getNameElement().getName();
         switch (event.getType()) {
             case Event.NODE_ADDED:
-            case Event.PROPERTY_ADDED:
-                if (!id.equals(event.getParentId())) {
-                    // TODO: TOBEFIXED. this should never occur and indicates severe consistency issue.
-                    throw new IllegalArgumentException("Event parent (" + event.getParentId() + ") does not match this state with id: " + id);
-                }
-                ItemId evId = event.getItemId();
-                ItemState newState = null;
-
-                if (evId.denotesNode()) {
-                    QName name = event.getQPath().getNameElement().getName();
-                    int index = event.getQPath().getNameElement().getNormalizedIndex();
-                    String uuid = (((NodeId)evId).getPath() != null) ? null : ((NodeId)evId).getUUID();
-
-                    // add new childNodeEntry if it has not been added by
-                    // some earlier 'add' event
-                    // TODO: TOBEFIXED for SNSs
-                    ChildNodeEntry cne = getChildNodeEntry(name, index);
-                    if (cne == null || ((uuid == null) ? cne.getUUID() != null : !uuid.equals(cne.getUUID()))) {
-                        cne = childNodeEntries.add(name, uuid);
-                    }
-                    try {
-                        newState = cne.getNodeState();
-                    } catch (ItemStateException e) {
-                        log.error("Internal error", e);
-                    }
-                } else {
-                    QName pName = ((PropertyId) event.getItemId()).getQName();
-                    // create a new property reference if it has not been
-                    // added by some earlier 'add' event
-                    ChildPropertyEntry re;
-                    if (hasPropertyName(pName)) {
-                        re = (ChildPropertyEntry) properties.get(pName);
-                    } else {
-                        re = PropertyReference.create(this, pName, isf, idFactory);
-                        properties.put(pName, re);
-                    }
-                    try {
-                        newState = re.getPropertyState();
-                    } catch (ItemStateException e) {
-                        log.error("Internal error", e);
-                    }
-                    // make sure this state is up to date (uuid/mixins)
-                    refresh(pName, event.getType());
+                int index = event.getQPath().getNameElement().getNormalizedIndex();
+                NodeId evId = (NodeId) event.getItemId();
+                String uuid = (evId.getPath() != null) ? null : evId.getUUID();
+
+                // add new childNodeEntry if it has not been added by
+                // some earlier 'add' event
+                // TODO: TOBEFIXED for SNSs
+                ChildNodeEntry cne = childNodeEntries.get(name, index);
+                if (cne == null || ((uuid == null) ? cne.getUUID() != null : !uuid.equals(cne.getUUID()))) {
+                    cne = childNodeEntries.add(name, uuid, index);
                 }
+                // and let the transiently modified session state now, that
+                // its workspace state has been touched.
+                setStatus(Status.MODIFIED);
+                break;
 
-                // connect the added state from the transient layer to the
-                // new workspaceState and make sure its data are updated.
-                if (newState != null && changeLog != null) {
-                    for (Iterator it = changeLog.addedStates(); it.hasNext();) {
-                        ItemState added = (ItemState) it.next();
-                        if (added.hasOverlayedState()) {
-                            // already connected
-                            continue;
-                        }
-                        // TODO: TOBEFIXED. may fail (produce wrong results) for SNSs, since currently events upon 'save' are not garantied to be 'local' changes only
-                        // TODO: TOBEFIXED. equals to false if added-state is referenceable.
-                        if (added.getId().equals(evId)) {
-                            added.connect(newState);
-                            added.merge();
-                            break;
-                        }
-                    }
+            case Event.PROPERTY_ADDED:
+                // create a new property reference if it has not been
+                // added by some earlier 'add' event
+                if (!hasPropertyName(name)) {
+                    ChildPropertyEntry re = PropertyReference.create(this, name, isf, idFactory);
+                    addPropertyEntry(re);
                 }
                 // and let the transiently modified session state now, that
                 // its workspace state has been touched.
@@ -617,9 +659,8 @@
 
             case Event.NODE_REMOVED:
                 if (id.equals(event.getParentId())) {
-                    QName qName = event.getQPath().getNameElement().getName();
-                    int index = event.getQPath().getNameElement().getNormalizedIndex();
-                    childNodeEntries.remove(qName, index);
+                    index = event.getQPath().getNameElement().getNormalizedIndex();
+                    childNodeEntries.remove(name, index);
                     setStatus(Status.MODIFIED);
                 } else if (id.equals(event.getItemId())) {
                     setStatus(Status.REMOVED);
@@ -630,27 +671,19 @@
                 break;
 
             case Event.PROPERTY_REMOVED:
-                if (id.equals(event.getParentId())) {
-                    QName pName = ((PropertyId) event.getItemId()).getQName();
-                    properties.remove(pName);
-                    // make sure this state is up to date (uuid/mixins)
-                    refresh(pName, event.getType());
-                    setStatus(Status.MODIFIED);
-                } else {
-                    // ILLEGAL
-                    throw new IllegalArgumentException("Illegal event type " + event.getType() + " for NodeState.");
-                }
+                removePropertyEntry(name);
+                setStatus(Status.MODIFIED);
                 break;
 
             case Event.PROPERTY_CHANGED:
-                if (id.equals(event.getParentId())) {
-                    QName pName = ((PropertyId) event.getItemId()).getQName();
-                    if (refresh(pName, event.getType())) {
-                        setStatus(Status.MODIFIED);
+                if (QName.JCR_UUID.equals(name) || QName.JCR_MIXINTYPES.equals(name)) {
+                    try {
+                        PropertyState ps = getPropertyState(name);
+                        adjustNodeState(this, new PropertyState[] {ps});
+                    } catch (ItemStateException e) {
+                        // should never occur.
+                        log.error("Internal error while updating node state.", e);
                     }
-                } else {
-                    // ILLEGAL
-                    throw new IllegalArgumentException("Illegal event type " + event.getType() + " for NodeState.");
                 }
                 break;
             default:
@@ -659,143 +692,191 @@
         }
     }
 
+    //----------------------------------------------------< Session - State >---
     /**
-     * Returns true, if the uuid or the mixin types of this state have been
-     * modified.
-     *
-     * @param propertyName
-     * @param eventType
-     * @return
+     * {@inheritDoc}
+     * @see ItemState#refresh(Collection,ChangeLog)
      */
-    private boolean refresh(QName propertyName, int eventType) {
-        if (QName.JCR_UUID.equals(propertyName)) {
-            // TODO: to be fixed.
-        } else if (QName.JCR_MIXINTYPES.equals(propertyName)) {
-            if (eventType == Event.PROPERTY_REMOVED) {
-                mixinTypeNames = QName.EMPTY_ARRAY;
-            } else { // added or changed
-                try {
-                    PropertyState ps = getPropertyState(propertyName);
-                    QValue[] values = ps.getValues();
-                    QName[] newMixins = new QName[values.length];
-                    for (int i = 0; i < values.length; i++) {
-                        newMixins[i] = QName.valueOf(values[i].getString());
-                    }
-                    mixinTypeNames = newMixins;
-                } catch (ItemStateException e) {
-                    // should never occur.
-                    log.error("Internal error while updating mixin types.", e);
-                } catch (RepositoryException e) {
-                    // should never occur.
-                    log.error("Internal error while updating mixin types.", e);
+    void refresh(Collection events, ChangeLog changeLog) throws IllegalStateException {
+
+        // remember parent states that have need to adjust their uuid/mixintypes
+        // or that got a new child entry added or existing entries removed.
+        HashMap modParents = new HashMap();
+
+        // process deleted states from the changelog
+        for (Iterator it = changeLog.deletedStates(); it.hasNext();) {
+            ItemState state = (ItemState) it.next();
+            state.setStatus(Status.REMOVED);
+            state.overlayedState.setStatus(Status.REMOVED);
+
+            // adjust parent states unless the parent is removed as well
+            NodeState parent = state.getParent();
+            if (!changeLog.deletedStates.contains(parent)) {
+                NodeState overlayedParent = (NodeState) parent.overlayedState;
+                if (state.isNode()) {
+                    overlayedParent.childNodeEntries.remove((NodeState)state.overlayedState);
+                } else {
+                    overlayedParent.removePropertyEntry(state.overlayedState.getQName());
                 }
+                modifiedParent(parent, state, modParents);
             }
-            return true;
-        }
-        return false;
-    }
+            // don't remove processed state from changelog, but from event list
+            // state on changelog is used for check if parent is deleted as well.
+            removeEvent(events, state);
+        }
+
+        // process added states from the changelog. since the changlog maintains
+        // LinkedHashSet for its entries, the iterator will not return a added
+        // entry before its NEW parent.
+        for (Iterator it = changeLog.addedStates(); it.hasNext();) {
+            ItemState addedState = (ItemState) it.next();
+            NodeState parent = addedState.getParent();
+            // TODO: only retrieve overlayed state, if necessary
+            try {
+                // adjust parent child-entries
+                NodeState overlayedParent = (NodeState) parent.overlayedState;
+                QName addedName = addedState.getQName();
+                if (addedState.isNode()) {
+                    int index = parent.getChildNodeEntry((NodeState) addedState).getIndex();
+                    ChildNodeEntry cne;
+                    if (overlayedParent.hasChildNodeEntry(addedName, index)) {
+                        cne = overlayedParent.getChildNodeEntry(addedName, index);
+                    } else {
+                        cne = overlayedParent.childNodeEntries.add(addedState.getQName(), null, index);
+                    }
+                    NodeState overlayed = cne.getNodeState();
+                    if (overlayed.getUUID() != null) {
+                        overlayedParent.childNodeEntries.replaceEntry(overlayed);
+                    }
+                    addedState.connect(overlayed);
+                } else {
+                    ChildPropertyEntry pe;
+                    if (overlayedParent.hasPropertyName(addedName)) {
+                        pe = (ChildPropertyEntry) overlayedParent.properties.get(addedName);
+                    } else {
+                        pe = PropertyReference.create(overlayedParent, addedName, overlayedParent.isf,  overlayedParent.idFactory);
+                        overlayedParent.addPropertyEntry(pe);
+                    }
+                    addedState.connect(pe.getPropertyState());
+                }
 
-    //----------------------------------------------------< Session - State >---
-    /**
-     * {@inheritDoc}
-     * @see ItemState#reset()
-     */
-    synchronized void reset() {
-        checkIsSessionState();
+                // make sure the new state gets updated (e.g. uuid created by server)
+                addedState.reset();
+                // and mark the added-state existing
+                addedState.setStatus(Status.EXISTING);
+                // if parent is modified -> remember for final status reset
+                if (parent.getStatus() == Status.EXISTING_MODIFIED) {
+                    modifiedParent(parent, addedState, modParents);
+                }
 
-        if (overlayedState != null) {
-            synchronized (overlayedState) {
-                NodeState wspState = (NodeState) overlayedState;
-                name = wspState.name;
-                uuid = wspState.uuid;
-                nodeTypeName = wspState.nodeTypeName;
-                definition = wspState.definition;
+                it.remove();
+                removeEvent(events, addedState);
+            } catch (ItemStateException e) {
+                log.error("Internal error.", e);
+            }
+        }
 
-                init(wspState.getMixinTypeNames(), wspState.getChildNodeEntries(), wspState.getPropertyNames(), wspState.getNodeReferences());
+        for (Iterator it = changeLog.modifiedStates(); it.hasNext();) {
+            ItemState modState = (ItemState) it.next();
+            if (modState.isNode()) {
+                continue;
+            }
+            // push changes down to overlayed state
+            int type = ((PropertyState) modState).getType();
+            QValue[] values = ((PropertyState) modState).getValues();
+            ((PropertyState) modState.overlayedState).init(type, values);
+
+            modState.setStatus(Status.EXISTING);
+            // if property state defines a modified jcr:mixinTypes
+            // the parent is listed as modified state and needs to be
+            // processed at the end.
+            if (isUuidOrMixin(modState.getQName())) {
+                modifiedParent(this, modState, modParents);
+            }
+            // remove the processed event from the set
+            it.remove();
+            removeEvent(events, modState);
+        }
+
+        /* process all parent states that need their uuid or mixin-types being
+           adjusted because that property has been added or modified */
+        for (Iterator it = modParents.keySet().iterator(); it.hasNext();) {
+            NodeState parent = (NodeState) it.next();
+            List l = (List) modParents.get(parent);
+            adjustNodeState(parent, (PropertyState[]) l.toArray(new PropertyState[l.size()]));
+        }
+
+        /* finally check if all entries in the changelog have been processed
+           and eventually force a reload in order not to have any states with
+           wrong transient status floating around. */
+        Iterator[] its = new Iterator[] {changeLog.addedStates(), changeLog.deletedStates(), changeLog.modifiedStates()};
+        IteratorChain chain = new IteratorChain(its);
+        while (chain.hasNext()) {
+            ItemState state = (ItemState) chain.next();
+            if (!(state.getStatus() == Status.EXISTING || state.getStatus() == Status.REMOVED)) {
+                // error: state has not been processed
+                // TODO: discard state and force reload of all data
             }
         }
     }
 
     /**
      * {@inheritDoc}
-     * @see ItemState#merge()
+     * @see ItemState#reset()
      */
-    synchronized void merge() {
+    synchronized void reset() {
         checkIsSessionState();
 
         if (overlayedState != null) {
             synchronized (overlayedState) {
                 NodeState wspState = (NodeState) overlayedState;
                 name = wspState.name;
-                uuid = wspState.uuid;
+                setUUID(wspState.uuid);
                 nodeTypeName = wspState.nodeTypeName;
                 definition = wspState.definition;
 
                 mixinTypeNames = wspState.mixinTypeNames;
-                references = wspState.getNodeReferences();
 
-                // search for removed properties
-                Collection wspProps = wspState.getPropertyNames();
+                // remove all entries in the attic
+                propertiesInAttic.clear();
+
+                // merge prop-names
+                Collection wspPropNames = wspState.getPropertyNames();
+                for (Iterator it = wspPropNames.iterator(); it.hasNext();) {
+                    QName propName = (QName) it.next();
+                    if (!hasPropertyName(propName)) {
+                        addPropertyEntry(PropertyReference.create(this, propName, isf, idFactory));
+                    }
+                }
                 for (Iterator it = properties.keySet().iterator(); it.hasNext();) {
-                    ChildPropertyEntry pe = (ChildPropertyEntry) properties.get((QName) it.next());
-                    if (pe.isAvailable()) {
-                        try {
-                            PropertyState ps = getPropertyState(pe.getName());
-                            if (ps.getStatus() == Status.REMOVED || ps.getStatus() == Status.STALE_DESTROYED) {
-                                it.remove();
-                            }
-                        } catch (ItemStateException e) {
-                            log.error("Internal error while merging item node states.", e);
-                        }
-                    } else if (!wspProps.contains(pe.getName())) {
-                        // not available and not present in wsp-layer any more.
+                    // remove all prop-entries in the session state that are
+                    // not present in the wsp-state.
+                    if (!wspPropNames.contains(it.next())) {
                         it.remove();
                     }
                 }
-                // add missing property entries
-                for (Iterator it = wspProps.iterator(); it.hasNext();) {
-                    QName propName = (QName) it.next();
-                    if (!hasPropertyName(propName)) {
-                        properties.put(propName, PropertyReference.create(this, propName, isf, idFactory));
-                    } // else property is already listed
-                }
 
-                Collection wspEntries = wspState.getChildNodeEntries();
-                // remove child entries, that are 'REMOVED' in the wsp layer
-                Set toRemove = new HashSet();
+                // merge child node entries
+                for (Iterator it = wspState.getChildNodeEntries().iterator(); it.hasNext();) {
+                    ChildNodeEntry cne = (ChildNodeEntry) it.next();
+                    int index = cne.getIndex();
+                    if (!childNodeEntries.contains(cne.getName(), index, cne.getUUID())) {
+                        childNodeEntries.add(cne.getName(), cne.getUUID(), index);
+                    }
+                }
+                List toRemove = new ArrayList();
                 for (Iterator it = getChildNodeEntries().iterator(); it.hasNext();) {
                     ChildNodeEntry cne = (ChildNodeEntry) it.next();
-                    if (cne.isAvailable()) {
-                        try {
-                            NodeState ns = cne.getNodeState();
-                            if (ns.getStatus() == Status.REMOVED) {
-                                toRemove.add(cne);
-                            }
-                        } catch (ItemStateException e) {
-                            // should not occur
-                            log.error("Internal error while merging item node states.", e);
-                        }
-                    } else if (wspState.getChildNodeEntries(cne.getName()).isEmpty()) {
+                    if (!wspState.childNodeEntries.contains(cne.getName(), cne.getIndex(), cne.getUUID())) {
                         toRemove.add(cne);
-                    } // TODO: clean up same-named siblings
+                    }
                 }
                 for (Iterator it = toRemove.iterator(); it.hasNext();) {
                     ChildNodeEntry cne = (ChildNodeEntry) it.next();
                     childNodeEntries.remove(cne.getName(), cne.getIndex());
                 }
-
-                // add missing child entries
-                for (Iterator it = wspEntries.iterator(); it.hasNext();) {
-                    ChildNodeEntry wspEntry = (ChildNodeEntry) it.next();
-                    List namedEntries = getChildNodeEntries(wspEntry.getName());
-                    if (namedEntries.isEmpty()) {
-                        // simple case: no cne with the given name
-                        childNodeEntries.add(wspEntry.getName(), wspEntry.getUUID());
-                    } else {
-                        List wspCnes = wspState.getChildNodeEntries(wspEntry.getName());
-                        // TODO: compare sn-siblings an add missing ones
-                    }
-                }
+                // set the node references
+                references = wspState.references;
             }
         }
     }
@@ -839,7 +920,7 @@
             setStatus(Status.REMOVED);
         }
         // now inform parent
-        parent.childNodeStateRemoved(this);
+        getParent().childNodeStateRemoved(this);
     }
 
     /**
@@ -919,18 +1000,18 @@
                 // set removed
                 setStatus(Status.REMOVED);
                 // remove from parent
-                parent.childNodeStateRemoved(this);
+                getParent().childNodeStateRemoved(this);
                 affectedItemStates.add(this);
                 break;
             case Status.REMOVED:
                 // shouldn't happen actually, because a 'removed' state is not
                 // accessible anymore
                 log.warn("trying to revert an already removed node state");
-                parent.childNodeStateRemoved(this);
+                getParent().childNodeStateRemoved(this);
                 break;
             case Status.STALE_DESTROYED:
                 // overlayed state does not exist anymore
-                parent.childNodeStateRemoved(this);
+                getParent().childNodeStateRemoved(this);
                 affectedItemStates.add(this);
                 break;
         }
@@ -938,9 +1019,9 @@
 
     /**
      * @inheritDoc
-     * @see ItemState#collectTransientStates(Set)
+     * @see ItemState#collectTransientStates(Collection)
      */
-    void collectTransientStates(Set transientStates) {
+    void collectTransientStates(Collection transientStates) {
         checkIsSessionState();
 
         switch (getStatus()) {
@@ -978,38 +1059,20 @@
     }
 
     /**
-     * Sets the names of this node's mixin types.
-     *
-     * @param mixinTypeNames set of names of mixin types
-     */
-    synchronized void setMixinTypeNames(QName[] mixinTypeNames) {
-        checkIsSessionState();
-
-        if (mixinTypeNames != null) {
-            this.mixinTypeNames = mixinTypeNames;
-        } else {
-            this.mixinTypeNames = new QName[0];
-        }
-        markModified();
-    }
-
-    /**
      * Adds a child node state to this node state.
      *
      * @param child the node state to add.
-     * @param uuid  the uuid of the child node state or <code>null</code> if
-     *              <code>child</code> cannot be identified with a uuid.
      * @throws IllegalArgumentException if <code>this</code> is not the parent
      *                                  of <code>child</code>.
      */
-    synchronized void addChildNodeState(NodeState child, String uuid) {
+    synchronized void addChildNodeState(NodeState child) {
         checkIsSessionState();
-
         if (child.getParent() != this) {
             throw new IllegalArgumentException("This NodeState is not the parent of child");
         }
         ChildNodeEntry cne = ChildNodeReference.create(child, isf, idFactory);
         childNodeEntries.add(cne);
+
         markModified();
     }
 
@@ -1069,7 +1132,7 @@
                 existingState = ref.getPropertyState();
             } catch (ItemStateException e) {
                 // probably does not exist anymore, remove from properties map
-                properties.remove(propertyName);
+                removePropertyEntry(propertyName);
             }
             if (existingState != null) {
                 if (existingState.getStatus() == Status.EXISTING_REMOVED) {
@@ -1080,7 +1143,7 @@
                 }
             }
         }
-        properties.put(propertyName, PropertyReference.create(propState, isf, idFactory));
+        addPropertyEntry(PropertyReference.create(propState, isf, idFactory));
         markModified();
     }
 
@@ -1099,11 +1162,10 @@
         // remove property state from map of properties if it does not exist
         // anymore, otherwise leave the property state in the map
         if (propState.getStatus() == Status.REMOVED) {
-            properties.remove(propState.getQName());
+            removePropertyEntry(propState.getQName());
         }
         markModified();
     }
-
     /**
      * Reorders the child node <code>insertNode</code> before the child node
      * <code>beforeNode</code>.
@@ -1167,8 +1229,7 @@
      */
     private synchronized void rename(QName newName) {
         checkIsSessionState();
-
-        if (parent == null) {
+        if (getParent() == null) {
             throw new IllegalStateException("root node cannot be renamed");
         }
         name = newName;
@@ -1302,6 +1363,80 @@
         return false;
     }
 
+    /**
+     *
+     * @param ps
+     * @return
+     * @throws RepositoryException
+     */
+    private static QName[] getMixinNames(PropertyState ps) throws RepositoryException {
+        assert QName.JCR_MIXINTYPES.equals(ps.getQName());
+
+        QValue[] values = ps.getValues();
+        QName[] newMixins = new QName[values.length];
+        for (int i = 0; i < values.length; i++) {
+            newMixins[i] = QName.valueOf(values[i].getString());
+        }
+        return newMixins;
+    }
+
+    private static boolean isUuidOrMixin(QName propName) {
+        return QName.JCR_UUID.equals(propName) || QName.JCR_MIXINTYPES.equals(propName);
+    }
+
+    private static void modifiedParent(NodeState parent, ItemState child, Map modParents) {
+        List l;
+        if (modParents.containsKey(parent)) {
+            l = (List) modParents.get(parent);
+        } else {
+            l = new ArrayList(2);
+            modParents.put(parent, l);
+        }
+        if (child != null && !child.isNode() && isUuidOrMixin(child.getQName())) {
+            l.add(child);
+        }
+    }
+
+    /**
+     *
+     * @param parent
+     * @param props
+     */
+    private static void adjustNodeState(NodeState parent, PropertyState[] props) {
+        NodeState overlayed = (parent.isWorkspaceState()) ? parent : (NodeState) parent.overlayedState;
+        NodeState sState = (parent.isWorkspaceState()) ? (NodeState) overlayed.getSessionState() : parent;
+
+        if (overlayed != null) {
+            for (int i = 0; i < props.length; i++) {
+                try {
+                    if (QName.JCR_UUID.equals(props[i].getQName())) {
+                        String uuid = (props[i].getStatus() == Status.REMOVED) ? null : props[i].getValue().getString();
+                        sState.setUUID(uuid);
+                        overlayed.setUUID(uuid);
+                    } else if (QName.JCR_MIXINTYPES.equals(props[i].getQName())) {
+                        QName[] mixins = (props[i].getStatus() == Status.REMOVED) ? QName.EMPTY_ARRAY : getMixinNames(props[i]);
+
+                        sState.mixinTypeNames = mixins;
+                        overlayed.mixinTypeNames = mixins;
+                    } // else: ignore.
+                } catch (RepositoryException e) {
+                    // should never occur.
+                    log.error("Internal error while updating node state.", e);
+                }
+            }
+
+            // make sure all other modifications on the overlayed state are
+            // reflected on the session-state.
+            sState.reset();
+            // make sure, the session-state gets its status reset to Existing.
+            if (sState.getStatus() == Status.EXISTING_MODIFIED) {
+                sState.setStatus(Status.EXISTING);
+            }
+        } else {
+            // should never occur.
+            log.warn("Error while adjusting nodestate: Overlayed state is missing.");
+        }
+    }
     //------------------------------------------------------< inner classes >---
     /**
      * <code>ChildNodeEntries</code> represents an insertion-ordered
@@ -1326,6 +1461,61 @@
         private final Map nameMap = new HashMap();
 
         /**
+         *
+         * @param name
+         * @param index
+         * @param uuid
+         * @return
+         */
+        private boolean contains(QName name, int index, String uuid) {
+            if (!nameMap.containsKey(name)) {
+                // no matching child node entry
+                return false;
+            }
+            Object o = nameMap.get(name);
+            if (o instanceof List) {
+                // SNS
+                for (Iterator it = ((List) o).iterator(); it.hasNext(); ) {
+                    LinkedEntries.LinkNode n = (LinkedEntries.LinkNode) it.next();
+                    ChildNodeEntry cne = n.getChildNodeEntry();
+                    if (uuid == null) {
+                        if (cne.getIndex() == index) {
+                            return true;
+                        }
+                    } else if (uuid.equals(cne.getUUID())) {
+                        return true;
+                    }
+                }
+                // no matching entry found
+                return false;
+            } else {
+                // single child node with this name
+                ChildNodeEntry cne = ((LinkedEntries.LinkNode) o).getChildNodeEntry();
+                if (uuid == null) {
+                    return cne.getUUID() == null;
+                } else {
+                    return uuid.equals(cne.getUUID());
+                }
+            }
+        }
+
+        /**
+         * 
+         * @param uuid
+         * @return
+         */
+        private ChildNodeEntry get(String uuid) {
+            for (Iterator it = entries.iterator(); it.hasNext();) {
+                LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) it.next();
+                ChildNodeEntry cne = ln.getChildNodeEntry();
+                if (cne.getUUID() == uuid) {
+                    return cne;
+                }
+            }
+            return null;
+        }
+
+        /**
          * Returns the <code>ChildNodeEntry</code> for the given
          * <code>nodeState</code>.
          *
@@ -1346,7 +1536,7 @@
                     ChildNodeEntry cne = n.getChildNodeEntry();
                     // only check available child node entries
                     try {
-                        if (cne.isAvailable() && cne.getNodeState() == nodeState) {
+                        if (cne.getNodeState() == nodeState) {
                             return cne;
                         }
                     } catch (ItemStateException e) {
@@ -1357,7 +1547,7 @@
                 // single child node with this name
                 ChildNodeEntry cne = ((LinkedEntries.LinkNode) o).getChildNodeEntry();
                 try {
-                    if (cne.isAvailable() && cne.getNodeState() == nodeState) {
+                    if (cne.getNodeState() == nodeState) {
                         return cne;
                     }
                 } catch (ItemStateException e) {
@@ -1415,8 +1605,7 @@
                 });
             } else {
                 // map entry is a single child node entry
-                return Collections.singletonList(
-                        ((LinkedEntries.LinkNode) obj).getChildNodeEntry());
+                return Collections.singletonList(((LinkedEntries.LinkNode) obj).getChildNodeEntry());
             }
         }
 
@@ -1483,31 +1672,35 @@
          * @return the created ChildNodeEntry.
          */
         ChildNodeEntry add(QName nodeName, String uuid) {
-            List siblings = null;
-            Object obj = nameMap.get(nodeName);
-            if (obj != null) {
-                if (obj instanceof List) {
-                    // map entry is a list of siblings
-                    siblings = (List) obj;
-                } else {
-                    // map entry is a single child node entry,
-                    // convert to siblings list
-                    siblings = new ArrayList();
-                    siblings.add(obj);
-                    nameMap.put(nodeName, siblings);
-                }
-            }
-
-            ChildNodeEntry entry = ChildNodeReference.create(NodeState.this, nodeName, uuid, isf, idFactory);
-            LinkedEntries.LinkNode ln = entries.add(entry);
+            ChildNodeEntry cne = ChildNodeReference.create(NodeState.this, nodeName, uuid, isf, idFactory);
+            add(cne);
+            return cne;
+        }
 
-            if (siblings != null) {
-                siblings.add(ln);
-            } else {
-                nameMap.put(nodeName, ln);
-            }
+        /**
+         * Insert a new childnode entry at the position indicated by index.
+         * @param nodeName
+         * @param uuid
+         * @param index
+         * @return
+         */
+        ChildNodeEntry add(QName nodeName, String uuid, int index) {
+            ChildNodeEntry cne = add(nodeName, uuid);
+            // TODO: in case of SNS, move new cne to the right position.
+            return cne;
+        }
 
-            return entry;
+        /**
+         * Adds a <code>childNode</code> to the end of the list.
+         *
+         * @param childState the <code>NodeState</code> to add.
+         * @return the <code>ChildNodeEntry</code> which was created for
+         *         <code>childNode</code>.
+         */
+        ChildNodeEntry add(NodeState childState) {
+            ChildNodeEntry cne = ChildNodeReference.create(childState, isf, idFactory);
+            add(cne);
+            return cne;
         }
 
         /**
@@ -1542,19 +1735,6 @@
         }
 
         /**
-         * Adds a <code>childNode</code> to the end of the list.
-         *
-         * @param childNode the <code>NodeState</code> to add.
-         * @return the <code>ChildNodeEntry</code> which was created for
-         *         <code>childNode</code>.
-         */
-        ChildNodeEntry add(NodeState childNode) {
-            ChildNodeEntry cne = ChildNodeReference.create(childNode, isf, idFactory);
-            add(cne);
-            return cne;
-        }
-
-        /**
          * Appends a list of <code>ChildNodeEntry</code>s to this list.
          *
          * @param entriesList the list of <code>ChildNodeEntry</code>s to add.
@@ -1667,11 +1847,10 @@
          *
          * @param insertNode the node state to move.
          * @param beforeNode the node state where <code>insertNode</code> is
-         *                   reordered to.
+         * reordered to.
          * @throws NoSuchItemStateException if <code>insertNode</code> or
-         *                                  <code>beforeNode</code> does not
-         *                                  have a <code>ChildNodeEntry</code>
-         *                                  in this <code>ChildNodeEntries</code>.
+         * <code>beforeNode</code> does not have a <code>ChildNodeEntry</code>
+         * in this <code>ChildNodeEntries</code>.
          */
         public void reorder(NodeState insertNode, NodeState beforeNode)
                 throws NoSuchItemStateException {
@@ -1731,6 +1910,24 @@
         }
 
         /**
+         * If the given child state got a (new) uuid assigned or its removed,
+         * its childEntry must be adjusted.
+         *
+         * @param childState
+         */
+        private void replaceEntry(NodeState childState) {
+            // NOTE: test if child-state needs to get a new entry not checked here.
+            try {
+                Object replaceObj = nameMap.get(childState.getQName());
+                LinkedEntries.LinkNode ln = getLinkNode(replaceObj, childState);
+                ChildNodeEntry newCne = ChildNodeReference.create(childState, isf, idFactory);
+                entries.replaceNode(ln, newCne);
+            } catch (NoSuchItemStateException e) {
+                log.error("Internal error.", e);
+            }
+        }
+
+        /**
          * Returns the matching <code>LinkNode</code> from a list or a single
          * <code>LinkNode</code>.
          *
@@ -1893,6 +2090,17 @@
             } else {
                 addNode(insert, before);
             }
+        }
+
+        /**
+         * Replace the value of the given LinkNode with a new childNodeEntry
+         * value.
+         *
+         * @param node
+         * @param value
+         */
+        void replaceNode(LinkNode node, ChildNodeEntry value) {
+            updateNode(node, value);
         }
 
         /**

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java Thu Oct 26 04:02:02 2006
@@ -33,6 +33,8 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Set;
+import java.util.Collection;
+import java.util.Iterator;
 
 /**
  * <code>PropertyState</code> represents the state of a <code>Property</code>.
@@ -44,7 +46,12 @@
     /**
      * The name of this property state.
      */
-    private QName name;
+    private final QName name;
+
+    /**
+     * Property definition
+     */
+    private final QPropertyDefinition def;
 
     /**
      * The internal value(s)
@@ -57,11 +64,6 @@
     private int type;
 
     /**
-     * Property definition
-     */
-    private QPropertyDefinition def;
-
-    /**
      * Constructs a new property state that is initially connected to an
      * overlayed state.
      *
@@ -71,8 +73,11 @@
      * @param idFactory
      */
     protected PropertyState(PropertyState overlayedState, NodeState parent,
-                            int initialStatus, IdFactory idFactory) {
-        super(overlayedState, parent, initialStatus, idFactory);
+                            int initialStatus, ItemStateFactory isf, IdFactory idFactory) {
+        super(overlayedState, parent, initialStatus, isf, idFactory);
+        this.name = overlayedState.name;
+        this.def = overlayedState.def;
+
         reset();
     }
 
@@ -86,13 +91,12 @@
      * @param idFactory
      */
     protected PropertyState(QName name, NodeState parent, QPropertyDefinition definition,
-                            int initialStatus, IdFactory idFactory, boolean isWorkspaceState) {
-        super(parent, initialStatus, idFactory, isWorkspaceState);
+                            int initialStatus, ItemStateFactory isf, IdFactory idFactory,
+                            boolean isWorkspaceState) {
+        super(parent, initialStatus, isf, idFactory, isWorkspaceState);
         this.name = name;
         this.def = definition;
-
-        type = PropertyType.UNDEFINED;
-        values = QValue.EMPTY_ARRAY;
+        init(PropertyType.UNDEFINED, QValue.EMPTY_ARRAY);
     }
 
     /**
@@ -101,6 +105,18 @@
      * @param values
      */
     void init(int type, QValue[] values) {
+        // free old values as necessary
+        QValue[] oldValues = this.values;
+        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();
+                }
+            }
+        }
         this.type = type;
         this.values = (values == null) ? QValue.EMPTY_ARRAY : values;
     }
@@ -141,7 +157,7 @@
      * @return the id of this property.
      */
     public PropertyId getPropertyId() {
-        return idFactory.createPropertyId(parent.getNodeId(), getQName());
+        return idFactory.createPropertyId(getParent().getNodeId(), getQName());
     }
 
     /**
@@ -205,27 +221,30 @@
 
     //----------------------------------------------------< Workspace State >---
     /**
-     * @see ItemState#refresh(Event, ChangeLog)
+     * @see ItemState#refresh(Event)
      */
-    synchronized void refresh(Event event, ChangeLog changeLog) {
+    synchronized void refresh(Event event) {
         checkIsWorkspaceState();
 
         switch (event.getType()) {
             case Event.PROPERTY_REMOVED:
-                if (event.getItemId().equals(getId())) {
-                    setStatus(Status.REMOVED);
-                } else {
-                    // ILLEGAL
-                    throw new IllegalArgumentException("EventId " + event.getItemId() + " does not match id of this property state.");
-                }
+                setStatus(Status.REMOVED);
                 break;
 
             case Event.PROPERTY_CHANGED:
-                if (event.getItemId().equals(getId())) {
+                // TODO: improve.
+                /* retrieve property value and type from server even if
+                   changes were issued from this session (changelog).
+                   this is currently the only way to update the workspace
+                   state, which is not connected to its overlaying session-state.
+                */
+                try {
+                    PropertyState tmp = isf.createPropertyState(getPropertyId(), parent);
+                    init(tmp.getType(), tmp.getValues());
                     setStatus(Status.MODIFIED);
-                } else {
-                    // ILLEGAL
-                    throw new IllegalArgumentException("EventId " + event.getItemId() + " does not match id of this property state.");
+                } catch (ItemStateException e) {
+                    // TODO: rather throw?
+                    log.error("Internal Error", e);
                 }
                 break;
 
@@ -240,6 +259,29 @@
     //----------------------------------------------------< Session - State >---
     /**
      * {@inheritDoc}
+     * @see ItemState#refresh(Collection,ChangeLog)
+     */
+    void refresh(Collection events, ChangeLog changeLog) throws IllegalStateException {
+        for (Iterator it = changeLog.modifiedStates(); it.hasNext();) {
+            ItemState modState = (ItemState) it.next();
+            if (modState == this) {
+                /*
+                NOTE: overlayedState must be existing, otherwise save was not
+                possible on prop. Similarly a property can only be the changelog
+                target, if it was modified. removal, add must be persisted on parent.
+                */
+                // push changes to overlayed state and reset status
+                ((PropertyState) overlayedState).init(getType(), getValues());
+                setStatus(Status.EXISTING);
+                // parent must not be informed, since all properties that
+                // affect the parent state (uuid, mixins) are protected.
+                removeEvent(events, modState);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
      * @see ItemState#reset()
      */
     synchronized void reset() {
@@ -247,31 +289,23 @@
         if (overlayedState != null) {
             synchronized (overlayedState) {
                 PropertyState wspState = (PropertyState) overlayedState;
-                name = wspState.name;
-                type = wspState.type;
-                def = wspState.def;
-                values = wspState.values;
+                init(wspState.type, wspState.values);
             }
         }
     }
 
-    synchronized void merge() {
-        reset();
-    }
-
     /**
      * @inheritDoc
      * @see ItemState#remove()
      */
     void remove() {
         checkIsSessionState();
-
         if (getStatus() == Status.NEW) {
             setStatus(Status.REMOVED);
         } else {
             setStatus(Status.EXISTING_REMOVED);
         }
-        parent.propertyStateRemoved(this);
+        getParent().propertyStateRemoved(this);
     }
 
     /**
@@ -297,18 +331,18 @@
                 // set removed
                 setStatus(Status.REMOVED);
                 // and remove from parent
-                parent.propertyStateRemoved(this);
+                getParent().propertyStateRemoved(this);
                 affectedItemStates.add(this);
                 break;
             case Status.REMOVED:
                 // shouldn't happen actually, because a 'removed' state is not
                 // accessible anymore
                 log.warn("trying to revert an already removed property state");
-                parent.propertyStateRemoved(this);
+                getParent().propertyStateRemoved(this);
                 break;
             case Status.STALE_DESTROYED:
                 // overlayed does not exist anymore
-                parent.propertyStateRemoved(this);
+                getParent().propertyStateRemoved(this);
                 affectedItemStates.add(this);
                 break;
         }
@@ -316,9 +350,9 @@
 
     /**
      * @inheritDoc
-     * @see ItemState#collectTransientStates(Set)
+     * @see ItemState#collectTransientStates(Collection)
      */
-    void collectTransientStates(Set transientStates) {
+    void collectTransientStates(Collection transientStates) {
         checkIsSessionState();
 
         switch (getStatus()) {
@@ -346,12 +380,11 @@
      */
     void setValues(QValue[] values, int type) throws RepositoryException {
         checkIsSessionState();
-
         // make sure the arguements are consistent and do not violate the
         // given property definition.
         validate(values, type, this.def);
-        this.values = values;
-        this.type = type;
+        init(type, values);
+
         markModified();
     }
 

Modified: 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?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java Thu Oct 26 04:02:02 2006
@@ -18,8 +18,6 @@
 
 import org.apache.jackrabbit.jcr2spi.HierarchyManager;
 import org.apache.jackrabbit.jcr2spi.HierarchyManagerImpl;
-import org.apache.jackrabbit.jcr2spi.state.entry.ChildPropertyEntry;
-import org.apache.jackrabbit.jcr2spi.state.entry.ChildNodeEntry;
 import org.apache.jackrabbit.jcr2spi.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.jcr2spi.util.LogUtil;
 import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
@@ -56,6 +54,7 @@
 import org.apache.jackrabbit.spi.ItemId;
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.value.QValue;
+import org.apache.jackrabbit.uuid.UUID;
 
 import javax.jcr.InvalidItemStateException;
 import javax.jcr.ItemNotFoundException;
@@ -74,14 +73,13 @@
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.lock.LockException;
-import java.util.ArrayList;
 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.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.io.InputStream;
 import java.io.IOException;
 
@@ -126,7 +124,6 @@
 
         // create hierarchy manager
         hierMgr = new HierarchyManagerImpl(this, nsResolver);
-
     }
 
     /**
@@ -193,8 +190,33 @@
      * @param nodeState
      */
     public Collection getReferingStates(NodeState nodeState) throws ItemStateException {
-        // TODO: not correct. ItemManager later on expectes overlaying state
-        return workspaceItemStateMgr.getReferingStates(nodeState);
+        NodeState wspState = (NodeState) nodeState.getWorkspaceState();
+        if (wspState == null) {
+            // new state => unable to determine references
+            return Collections.EMPTY_SET;
+        }
+
+        Collection rs = workspaceItemStateMgr.getReferingStates(wspState);
+        if (rs.isEmpty()) {
+            return rs;
+        } else {
+            // retrieve session-propertystates
+            Set refStates = new HashSet();
+            for (Iterator it =  rs.iterator(); it.hasNext();) {
+                PropertyState wState = (PropertyState) it.next();
+                ItemState sState = wState.getSessionState();
+                if (sState == null) {
+                    // overlaying state has not been build up to now
+                   sState = getItemState(wState.getPropertyId());
+                }
+                // add property state to list of refering states unless it has
+                // be removed in the transient layer.
+                if (sState.isValid()) {
+                   refStates.add(sState);
+                }
+            }
+            return Collections.unmodifiableCollection(refStates);
+        }
     }
 
     /**
@@ -206,7 +228,12 @@
      * @param nodeState
      */
     public boolean hasReferingStates(NodeState nodeState) {
-        return workspaceItemStateMgr.hasReferingStates(nodeState);
+        try {
+            return !getReferingStates(nodeState).isEmpty();
+        } catch (ItemStateException e) {
+            log.warn("Internal error", e);
+            return false;
+        }
     }
 
     //------------------------------------------< UpdatableItemStateManager >---
@@ -367,33 +394,20 @@
     private void collectTransientStates(ItemState state, ChangeLog changeLog, boolean throwOnStale)
             throws StaleItemStateException, ItemStateException {
         // fail-fast test: check status of this item's state
-        switch (state.getStatus()) {
-            case Status.NEW:
-                {
-                    String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": cannot save a new item.";
-                    log.debug(msg);
-                    throw new ItemStateException(msg);
-                }
+        if (state.getStatus() == Status.NEW) {
+            String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": cannot save a new item.";
+            log.debug(msg);
+            throw new ItemStateException(msg);
         }
-        if (throwOnStale) {
-            switch (state.getStatus()) {
-                case Status.STALE_MODIFIED:
-                    {
-                        String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": the item cannot be saved because it has been modified externally.";
-                        log.debug(msg);
-                        throw new StaleItemStateException(msg);
-                    }
-                case Status.STALE_DESTROYED:
-                    {
-                        String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": the item cannot be saved because it has been deleted externally.";
-                        log.debug(msg);
-                        throw new StaleItemStateException(msg);
-                    }
-            }
+
+        if (throwOnStale && Status.isStale(state.getStatus())) {
+            String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": the item cannot be saved because it has been modified/removed externally.";
+            log.debug(msg);
+            throw new StaleItemStateException(msg);
         }
 
         // Set of transient states that should be persisted
-        Set transientStates = new HashSet();
+        Set transientStates = new LinkedHashSet();
         state.collectTransientStates(transientStates);
 
         for (Iterator it = transientStates.iterator(); it.hasNext();) {
@@ -522,23 +536,12 @@
      * @inheritDoc
      */
     public void visit(SetMixin operation) throws ConstraintViolationException, AccessDeniedException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
-        // remember if an existing mixin is being removed.
-        boolean anyRemoved;
-
+        // NOTE: nodestate is only modified upon save of the changes!
         QName[] mixinNames = operation.getMixinNames();
         NodeState nState = operation.getNodeState();
 
-        // mixin-names to be execute on the nodestate (and corresponding property state)
+        // new array of mixinNames to be set 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 = new ArrayList();
-            originalMixins.addAll(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
@@ -559,10 +562,6 @@
                 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)) {
                 try {
@@ -576,53 +575,7 @@
             }
         }
 
-        // 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.getPropertyEntries()).iterator();
-            while (childProps.hasNext()) {
-                try {
-                    ChildPropertyEntry entry = (ChildPropertyEntry) childProps.next();
-                    PropertyState childState = entry.getPropertyState();
-                    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);
-                    }
-                } catch (ItemStateException e) {
-                    // ignore. cleanup will occure upon save anyway
-                    log.error("Error while removing child node defined by removed mixin: {0}", e.getMessage());
-                }
-            }
-            // use temp array to avoid ConcurrentModificationException
-            Iterator childNodes = new ArrayList(nState.getChildNodeEntries()).iterator();
-            while (childNodes.hasNext()) {
-                try {
-                    ChildNodeEntry entry = (ChildNodeEntry) childNodes.next();
-                    NodeState childState = entry.getNodeState();
-                    // 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);
-                    }
-                } catch (ItemStateException e) {
-                    // ignore. cleanup will occure upon save anyway
-                    log.error("Error while removing child property defined by removed mixin: {0}", e.getMessage());
-                }
-            }
-        }
-
+        nState.markModified();
         transientStateMgr.addOperation(operation);
     }
 
@@ -734,13 +687,14 @@
         validator.checkAddProperty(parent, propertyName, pDef, options);
 
         // create property state
-        PropertyState propState = transientStateMgr.createNewPropertyState(propertyName, parent, pDef);
-
-        // NOTE: callers must make sure, the property type is not 'undefined'
-        propState.setValues(values, propertyType);
+        PropertyState propState = transientStateMgr.createNewPropertyState(propertyName, parent, pDef, values, propertyType);
     }
 
-    private void addNodeState(NodeState parent, QName nodeName, QName nodeTypeName, String uuid, QNodeDefinition definition, int options) throws RepositoryException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
+    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
@@ -773,7 +727,7 @@
             QPropertyDefinition pd = pda[i];
             QValue[] autoValue = computeSystemGeneratedPropertyValues(nodeState, pd);
             if (autoValue != null) {
-                int propOptions = 0; // nothing to check
+                int propOptions = ItemStateValidator.CHECK_NONE;
                 // execute 'addProperty' without adding operation.
                 addPropertyState(nodeState, pd.getQName(), pd.getRequiredType(), autoValue, pd, propOptions);
             }
@@ -824,19 +778,6 @@
         throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
         // assert that the property can be modified.
         validator.checkSetProperty(propState, options);
-
-        // free old values as necessary
-        QValue[] oldValues = propState.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();
-                }
-            }
-        }
         propState.setValues(iva, valueType);
     }
 
@@ -874,7 +815,11 @@
             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())};
+                String uuid = parent.getUUID();
+                if (uuid == null) {
+                    uuid = UUID.randomUUID().toString();
+                }
+                genValues = new QValue[]{QValue.create(uuid)};
             } else if (QName.NT_BASE.equals(declaringNT)) {
                 // nt:base node type
                 if (QName.JCR_PRIMARYTYPE.equals(name)) {

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java Thu Oct 26 04:02:02 2006
@@ -62,11 +62,41 @@
      */
     public static final int REMOVED = 8;
 
-    
-    public static boolean isTerminalStatus(int status) {
+    /**
+     * Returns <code>true</code> if the given status is a terminal status, i.e.
+     * the given status one of:
+     * <ul>
+     * <li>{@link #REMOVED}</li>
+     * <li>{@link #STALE_DESTROYED}</li>
+     * </ul>
+     *
+     * @param status
+     * @return
+     */
+    public static boolean isTerminal(int status) {
         return status == REMOVED || status == STALE_DESTROYED;
     }
 
+    /**
+     * Returns <code>true</code> if this item state is valid, that is its status
+     * is one of:
+     * <ul>
+     * <li>{@link #EXISTING}</li>
+     * <li>{@link #EXISTING_MODIFIED}</li>
+     * <li>{@link #NEW}</li>
+     * </ul>
+     *
+     * @param status
+     * @return
+     */
+    public static boolean isValid(int status) {
+        return status == EXISTING || status == EXISTING_MODIFIED || status == NEW;
+    }
+
+    public static boolean isStale(int status) {
+        return status == STALE_DESTROYED || status == STALE_MODIFIED;
+    }
+
     public static boolean isValidStatusChange(int oldStatus, int newStatus,
                                               boolean isWorkspaceState) {
         if (oldStatus == newStatus) {
@@ -112,10 +142,9 @@
                case REMOVED:
                    isValid = (oldStatus == NEW || oldStatus == EXISTING || oldStatus == EXISTING_REMOVED);
                    break;
-               /* default:
-                  NEW cannot change state to NEW -> false
-                  MODIFIED never applicable to session state -> false */
-
+                /* default:
+                   NEW cannot change state to NEW -> false
+                   MODIFIED never applicable to session state -> false */
             }
         }
         return isValid;

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java Thu Oct 26 04:02:02 2006
@@ -35,16 +35,14 @@
     private final IdFactory idFactory;
     private final ItemStateManager parent;
 
-    private ItemStateLifeCycleListener listener;
+    private ItemStateCache cache;
+    private ItemStateCreationListener listener;
 
     TransientISFactory(IdFactory idFactory, ItemStateManager parent) {
         this.idFactory = idFactory;
         this.parent = parent;
     }
 
-    void setListener(ItemStateLifeCycleListener listener) {
-        this.listener = listener;
-    }
     //------------------------------------------< TransientItemStateFactory >---
     /**
      * @inheritDoc
@@ -55,10 +53,15 @@
                                         QNodeDefinition definition) {
         NodeState nodeState = new NodeState(name, uuid, parent, nodetypeName,
             definition, Status.NEW, this, idFactory, false);
-        // get a notification when this item state is saved or invalidated
+
+        // notify listeners when this item state is saved or invalidated
+        nodeState.addListener(cache);
         nodeState.addListener(listener);
-        // notify listener that a node state has been created
-        listener.statusChanged(nodeState, Status.NEW);
+
+        // notify listeners that a node state has been created
+        cache.created(nodeState);
+        listener.created(nodeState);
+
         return nodeState;
     }
 
@@ -68,14 +71,27 @@
      */
     public PropertyState createNewPropertyState(QName name, NodeState parent, QPropertyDefinition definition) {
         PropertyState propState = new PropertyState(name, parent,
-            definition, Status.NEW, idFactory, false);
+            definition, Status.NEW, this, idFactory, false);
+
         // get a notification when this item state is saved or invalidated
+        propState.addListener(cache);
         propState.addListener(listener);
-        // notify listener that a property state has been created
-        listener.statusChanged(propState, Status.NEW);
+
+        // notify listeners that a property state has been created
+        cache.created(propState);
+        listener.created(propState);
+
         return propState;
     }
 
+    /**
+     * @inheritDoc
+     * @see TransientItemStateFactory#setListener(ItemStateCreationListener)
+     */
+    public void setListener(ItemStateCreationListener listener) {
+        this.listener = listener;
+    }
+
     //---------------------------------------------------< ItemStateFactory >---
     /**
      * @inheritDoc
@@ -84,9 +100,10 @@
     public NodeState createRootState(ItemStateManager ism) throws ItemStateException {
         // retrieve state to overlay
         NodeState overlayedState = (NodeState) parent.getRootState();
-        NodeState nodeState = new NodeState(overlayedState, null,
-            Status.EXISTING, this, idFactory);
-        nodeState.addListener(listener);
+        NodeState nodeState = new NodeState(overlayedState, null, Status.EXISTING, this, idFactory);
+
+        nodeState.addListener(cache);
+        cache.created(nodeState);
         return nodeState;
     }
 
@@ -96,16 +113,22 @@
      */
     public NodeState createNodeState(NodeId nodeId, ItemStateManager ism)
         throws NoSuchItemStateException, ItemStateException {
-        // retrieve state to overlay
-        NodeState overlayedState = (NodeState) parent.getItemState(nodeId);
-        NodeState overlayedParent = overlayedState.getParent();
-        NodeState parentState = null;
-        if (overlayedParent != null) {
-            parentState = (NodeState) ism.getItemState(overlayedParent.getId());
+
+        NodeState nodeState = cache.getNodeState(nodeId);
+        if (nodeState == null) {
+            // retrieve state to overlay
+            NodeState overlayedState = (NodeState) parent.getItemState(nodeId);
+            NodeState overlayedParent = overlayedState.getParent();
+
+            NodeState parentState = null;
+            if (overlayedParent != null) {
+                parentState = (NodeState) ism.getItemState(overlayedParent.getId());
+            }
+
+            nodeState = new NodeState(overlayedState, parentState, Status.EXISTING, this, idFactory);
+            nodeState.addListener(cache);
+            cache.created(nodeState);
         }
-        NodeState nodeState = new NodeState(overlayedState, parentState,
-            Status.EXISTING, this, idFactory);
-        nodeState.addListener(listener);
         return nodeState;
     }
 
@@ -115,11 +138,16 @@
      */
     public NodeState createNodeState(NodeId nodeId, NodeState parentState)
         throws NoSuchItemStateException, ItemStateException {
-        // retrieve state to overlay
-        NodeState overlayedState = (NodeState) parent.getItemState(nodeId);
-        NodeState nodeState = new NodeState(overlayedState, parentState,
-            Status.EXISTING, this, idFactory);
-        nodeState.addListener(listener);
+
+        NodeState nodeState = cache.getNodeState(nodeId);
+        if (nodeState == null) {
+            // retrieve state to overlay
+            NodeState overlayedState = (NodeState) parent.getItemState(nodeId);
+            nodeState = new NodeState(overlayedState, parentState, Status.EXISTING, this, idFactory);
+
+            nodeState.addListener(cache);
+            cache.created(nodeState);
+        }
         return nodeState;
     }
 
@@ -130,11 +158,24 @@
     public PropertyState createPropertyState(PropertyId propertyId,
                                              NodeState parentState)
         throws NoSuchItemStateException, ItemStateException {
-        // retrieve state to overlay
-        PropertyState overlayedState = (PropertyState) parent.getItemState(propertyId);
-        PropertyState propState = new PropertyState(overlayedState, parentState,
-            Status.EXISTING, idFactory);
-        propState.addListener(listener);
+
+        PropertyState propState = cache.getPropertyState(propertyId);
+        if (propState == null) {
+            // retrieve state to overlay
+            PropertyState overlayedState = (PropertyState) parent.getItemState(propertyId);
+            propState = new PropertyState(overlayedState, parentState, Status.EXISTING, this, idFactory);
+
+            propState.addListener(cache);
+            cache.created(propState);
+        }
         return propState;
+    }
+
+    /**
+     * @inheritDoc
+     * @see ItemStateFactory#setCache(ItemStateCache)
+     */
+    public void setCache(ItemStateCache cache) {
+        this.cache = cache;
     }
 }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java Thu Oct 26 04:02:02 2006
@@ -55,4 +55,11 @@
     public PropertyState createNewPropertyState(QName name,
                                                 NodeState parent,
                                                 QPropertyDefinition definition);
+
+    /**
+     * Set the listener that gets informed about NEW states.
+     *
+     * @param listener
+     */
+    public void setListener(ItemStateCreationListener listener);
 }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java Thu Oct 26 04:02:02 2006
@@ -22,10 +22,13 @@
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.QNodeDefinition;
 import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.value.QValue;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 
 import javax.jcr.ItemExistsException;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
 import java.util.Iterator;
 import java.util.Collection;
 
@@ -38,10 +41,10 @@
  * item states. While all other modifications can be invoked on the item state
  * instances itself, creating a new node state is done using
  * {@link #createNewNodeState(QName, String, QName, QNodeDefinition, NodeState)}
- * and {@link #createNewPropertyState(QName, NodeState, QPropertyDefinition)}.
+ * and {@link #createNewPropertyState(QName, NodeState, QPropertyDefinition, QValue[], int)}.
  */
 public class TransientItemStateManager extends CachingItemStateManager
-        implements ItemStateLifeCycleListener {
+    implements ItemStateCreationListener {
 
     /**
      * Logger instance for this class.
@@ -55,21 +58,19 @@
     private final ChangeLog changeLog;
 
     /**
-     * The parent item state manager, which return item states that are then
-     * overlayed by transient item states created by this TransientItemStateManager.
-     */
-    private final ItemStateManager parent;
-
-    /**
      * The root node state or <code>null</code> if it hasn't been retrieved yet.
      */
     private NodeState rootNodeState;
 
+    /**
+     *
+     * @param idFactory
+     * @param parent
+     */
     TransientItemStateManager(IdFactory idFactory, ItemStateManager parent) {
         super(new TransientISFactory(idFactory, parent), idFactory);
-        this.parent = parent;
         this.changeLog = new ChangeLog(null);
-        ((TransientISFactory) getTransientFactory()).setListener(this);
+        getTransientFactory().setListener(this);
     }
 
 
@@ -128,9 +129,8 @@
                                  QNodeDefinition definition, NodeState parent) {
         NodeState nodeState = getTransientFactory().createNewNodeState(nodeName, uuid, parent, nodeTypeName, definition);
 
-        parent.addChildNodeState(nodeState, uuid);
+        parent.addChildNodeState(nodeState);
         changeLog.added(nodeState);
-        nodeState.addListener(this);
         return nodeState;
     }
 
@@ -143,15 +143,20 @@
      * @param definition
      * @return the created property state.
      * @throws ItemExistsException if <code>parent</code> already has a property
-     *                             with the given name.
-     */
-    PropertyState createNewPropertyState(QName propName, NodeState parent, QPropertyDefinition definition)
-            throws ItemExistsException {
+     * with the given name.
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     */
+    PropertyState createNewPropertyState(QName propName, NodeState parent,
+                                         QPropertyDefinition definition,
+                                         QValue[] values, int propertyType)
+        throws ItemExistsException, ConstraintViolationException, RepositoryException {
         PropertyState propState = getTransientFactory().createNewPropertyState(propName, parent, definition);
+        // NOTE: callers must make sure, the property type is not 'undefined'
+        propState.init(propertyType, values);
 
         parent.addPropertyState(propState);
         changeLog.added(propState);
-        propState.addListener(this);
         return propState;
     }
 
@@ -186,7 +191,6 @@
     public NodeState getRootState() throws ItemStateException {
         if (rootNodeState == null) {
             rootNodeState = getItemStateFactory().createRootState(this);
-            rootNodeState.addListener(this);
         }
         return rootNodeState;
     }
@@ -246,6 +250,11 @@
      * @see ItemStateLifeCycleListener#statusChanged(ItemState, int)
      */
     public void statusChanged(ItemState state, int previousStatus) {
+        if (!Status.isValidStatusChange(previousStatus, state.getStatus(), false)) {
+            log.error("ItemState has invalid status: " + state.getStatus());
+            return;
+        }
+
         // TODO: depending on status of state adapt change log
         // e.g. a revert on states will reset the status from
         // 'existing modified' to 'existing'.
@@ -301,12 +310,20 @@
             case Status.STALE_MODIFIED:
                 // state is now stale. keep in modified. wait until refreshed
                 break;
-            case Status.NEW:
-                // new state has been created
-                changeLog.added(state);
-                break;
             default:
                 log.error("ItemState has invalid status: " + state.getStatus());
+        }
+    }
+
+    //-----------------------------------------< ItemStateCreationListener >---
+
+    /**
+     * @see ItemStateCreationListener#created(ItemState)
+     */
+    public void created(ItemState state) {
+        // new state has been created
+        if (state.getStatus() == Status.NEW) {
+            changeLog.added(state);
         }
     }
 }