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 2007/02/13 10:31:53 UTC

svn commit: r506927 [2/8] - in /jackrabbit/trunk/contrib/spi: jcr2spi/ jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/ jcr2spi...

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java?view=diff&rev=506927&r1=506926&r2=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java Tue Feb 13 01:31:36 2007
@@ -17,11 +17,12 @@
 package org.apache.jackrabbit.jcr2spi;
 
 import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
 import org.apache.jackrabbit.jcr2spi.state.UpdatableItemStateManager;
-import org.apache.jackrabbit.jcr2spi.state.ItemStateManager;
 import org.apache.jackrabbit.jcr2spi.state.ItemState;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
 import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeRegistry;
 import org.apache.jackrabbit.jcr2spi.query.QueryManagerImpl;
 import org.apache.jackrabbit.jcr2spi.operation.Move;
@@ -91,22 +92,14 @@
      */
     private final WorkspaceManager wspManager;
 
-    /**
-     * The hierarchy manager that reflects workspace state only
-     * (i.e. that is isolated from transient changes made through
-     * the session).
-     */
-    private HierarchyManager hierManager;
     private LockManager lockManager;
     private ObservationManager obsManager;
     private QueryManager qManager;
     private VersionManager versionManager;
-    private ItemStateValidator validator;
 
     public WorkspaceImpl(String name, SessionImpl session, RepositoryConfig config, SessionInfo sessionInfo) throws RepositoryException {
         this.name = name;
         this.session = session;
-
         wspManager = createManager(config.getRepositoryService(), sessionInfo, session.getCacheBehaviour(), config.getPollingInterval());
     }
 
@@ -261,7 +254,7 @@
         session.checkIsAlive();
         if (qManager == null) {
             qManager = new QueryManagerImpl(session, session.getLocalNamespaceMappings(),
-                session.getItemManager(), session.getItemStateManager(), wspManager);
+                session.getItemManager(), session.getHierarchyManager(), wspManager);
         }
         return qManager;
     }
@@ -366,17 +359,7 @@
      * @see ManagerProvider#getHierarchyManager()
      */
     public HierarchyManager getHierarchyManager() {
-        if (hierManager == null) {
-            hierManager = new HierarchyManagerImpl(getItemStateManager(), getNamespaceResolver());
-        }
-        return hierManager;
-    }
-
-    /**
-     * @see ManagerProvider#getItemStateManager()
-     */
-    public ItemStateManager getItemStateManager() {
-        return wspManager;
+        return wspManager.getHierarchyManager();
     }
 
     /**
@@ -433,17 +416,18 @@
     UpdatableItemStateManager getUpdatableItemStateManager() {
         return wspManager;
     }
+    
+    ItemStateFactory getItemStateFactory() {
+        return wspManager.getItemStateFactory();
+    }
 
     /**
-     * Validator for the <code>Workspace</code>. It contrast from {@link SessionImpl#getValidator()}
-     * in terms of <code>HierarchyManager</code> and <code>ItemManager</code>.
+     * Returns the validator of the session
+     *
      * @return validator
      */
     private ItemStateValidator getValidator() {
-        if (validator == null) {
-            validator = new ItemStateValidator(getNodeTypeRegistry(), this);
-        }
-        return validator;
+        return session.getValidator();
     }
     
     //-----------------------------------------------------< initialization >---
@@ -468,7 +452,7 @@
      * @return a new <code>LockManager</code> instance.
      */
     protected LockManager createLockManager(WorkspaceManager wspManager, ItemManager itemManager) {
-        LockManager lMgr = new LockManagerImpl(wspManager, itemManager);
+        LockManager lMgr = new LockManagerImpl(wspManager, itemManager, session.getCacheBehaviour());
         session.addListener((LockManagerImpl) lMgr);
         return lMgr;
     }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java?view=diff&rev=506927&r1=506926&r2=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java Tue Feb 13 01:31:36 2007
@@ -22,7 +22,6 @@
 import org.apache.jackrabbit.jcr2spi.name.NamespaceStorage;
 import org.apache.jackrabbit.jcr2spi.name.NamespaceRegistryImpl;
 import org.apache.jackrabbit.jcr2spi.state.ItemState;
-import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
 import org.apache.jackrabbit.jcr2spi.state.PropertyState;
 import org.apache.jackrabbit.jcr2spi.state.ChangeLog;
@@ -30,8 +29,8 @@
 import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.state.WorkspaceItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.state.NodeState;
-import org.apache.jackrabbit.jcr2spi.state.ItemStateManager;
-import org.apache.jackrabbit.jcr2spi.state.WorkspaceItemStateManager;
+import org.apache.jackrabbit.jcr2spi.state.TransientItemStateFactory;
+import org.apache.jackrabbit.jcr2spi.state.TransientISFactory;
 import org.apache.jackrabbit.jcr2spi.operation.OperationVisitor;
 import org.apache.jackrabbit.jcr2spi.operation.AddNode;
 import org.apache.jackrabbit.jcr2spi.operation.AddProperty;
@@ -58,9 +57,11 @@
 import org.apache.jackrabbit.jcr2spi.security.AccessManager;
 import org.apache.jackrabbit.jcr2spi.observation.InternalEventListener;
 import org.apache.jackrabbit.jcr2spi.config.CacheBehaviour;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyEventListener;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManagerImpl;
 import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.name.QName;
-import org.apache.jackrabbit.name.MalformedPathException;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.NodeId;
@@ -105,7 +106,6 @@
 import java.util.Iterator;
 import java.util.Set;
 import java.util.HashSet;
-import java.util.Collection;
 import java.util.Map;
 import java.util.Collections;
 import java.io.InputStream;
@@ -125,7 +125,8 @@
     private final RepositoryService service;
     private final SessionInfo sessionInfo;
 
-    private final ItemStateManager cache;
+    private final ItemStateFactory isf;
+    private final HierarchyManager hierarchyManager;
     private final CacheBehaviour cacheBehaviour;
 
     private final NamespaceRegistryImpl nsRegistry;
@@ -164,13 +165,15 @@
         this.sessionInfo = sessionInfo;
         this.cacheBehaviour = cacheBehaviour;
 
-        cache = createItemStateManager();
-
         Map repositoryDescriptors = service.getRepositoryDescriptors();
-
         nsRegistry = createNamespaceRegistry(repositoryDescriptors);
         ntRegistry = createNodeTypeRegistry(nsRegistry, repositoryDescriptors);
         changeFeed = createChangeFeed(pollingInterval);
+
+        TransientItemStateFactory stateFactory = createItemStateFactory(ntRegistry);
+        this.isf = stateFactory;
+        this.hierarchyManager = createHierarchyManager(stateFactory, service.getIdFactory());
+        createHierarchyListener(hierarchyManager);
     }
 
     public NamespaceRegistryImpl getNamespaceRegistryImpl() {
@@ -181,6 +184,10 @@
         return ntRegistry;
     }
 
+    public HierarchyManager getHierarchyManager() {
+        return hierarchyManager;
+    }
+
     public String[] getWorkspaceNames() throws RepositoryException {
         return service.getWorkspaceNames(sessionInfo);
     }
@@ -189,6 +196,10 @@
         return service.getIdFactory();
     }
 
+    public ItemStateFactory getItemStateFactory() {
+        return isf;
+    }
+
     public LockInfo getLockInfo(NodeId nodeId) throws LockException, RepositoryException {
         return service.getLockInfo(sessionInfo, nodeId);
     }
@@ -320,10 +331,27 @@
      *
      * @return
      */
-    private ItemStateManager createItemStateManager() {
+    private TransientItemStateFactory createItemStateFactory(NodeTypeRegistry ntReg) {
         ItemStateFactory isf = new WorkspaceItemStateFactory(service, sessionInfo, this);
-        WorkspaceItemStateManager ism = new WorkspaceItemStateManager(this, cacheBehaviour, isf, service.getIdFactory());
-        return ism;
+        TransientItemStateFactory tisf = new TransientISFactory(isf, ntReg);
+        return tisf;
+    }
+
+    /**
+     *
+     * @return
+     */
+    private HierarchyManager createHierarchyManager(TransientItemStateFactory tisf, IdFactory idFactory) {
+        return new HierarchyManagerImpl(tisf, idFactory);
+    }
+
+    /**
+     *
+     * @return
+     */
+    private InternalEventListener createHierarchyListener(HierarchyManager hierarchyMgr) {
+        InternalEventListener listener = new HierarchyEventListener(this, hierarchyMgr, cacheBehaviour);
+        return listener;
     }
 
     /**
@@ -390,62 +418,8 @@
         return t;
     }
 
-    //---------------------------------------------------< ItemStateManager >---
-    /**
-     * @inheritDoc
-     * @see ItemStateManager#getRootState()
-     */
-    public NodeState getRootState() throws ItemStateException {
-        // retrieve through cache
-        synchronized (cache) {
-            return cache.getRootState();
-        }
-    }
-
-    /**
-     * @inheritDoc
-     * @see ItemStateManager#getItemState(ItemId)
-     */
-    public ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException {
-        // retrieve through cache
-        synchronized (cache) {
-            return cache.getItemState(id);
-        }
-    }
-
-    /**
-     * @inheritDoc
-     * @see ItemStateManager#hasItemState(ItemId)
-     */
-    public boolean hasItemState(ItemId id) {
-        synchronized (cache) {
-            return cache.hasItemState(id);
-        }
-    }
-
-    /**
-     * @inheritDoc
-     * @see ItemStateManager#getReferingStates(NodeState)
-     * @param nodeState
-     */
-    public Collection getReferingStates(NodeState nodeState) throws ItemStateException {
-        synchronized (cache) {
-            return cache.getReferingStates(nodeState);
-        }
-    }
-
-    /**
-     * @inheritDoc
-     * @see ItemStateManager#hasReferingStates(NodeState)
-     * @param nodeState
-     */
-    public boolean hasReferingStates(NodeState nodeState) {
-        synchronized (cache) {
-            return cache.hasReferingStates(nodeState);
-        }
-    }
-
-    //------ updatable -:>> review ---------------------------------------------
+    //------------------------------------------< UpdatableItemStateManager >---
+    // TODO: review
     /**
      * Creates a new batch from the single workspace operation and executes it.
      *
@@ -468,7 +442,7 @@
             // execute operation and delegate invalidation of affected item
             // states to the operation.
             new OperationVisitorImpl(sessionInfo).execute(operation);
-            operation.persisted();
+            operation.persisted(cacheBehaviour);
         }
     }
 
@@ -515,10 +489,11 @@
                 log.warn("Interrupted while waiting for external change thread to terminate.");
             }
         }
+        hierarchyManager.dispose();
         try {
             service.dispose(sessionInfo);
         } catch (RepositoryException e) {
-            log.warn("Exception while disposing session info: " + e);            
+            log.warn("Exception while disposing session info: " + e);
         }
     }
     //------------------------------------------------------< AccessManager >---
@@ -526,34 +501,14 @@
      * @see AccessManager#isGranted(NodeState, Path, String[])
      */
     public boolean isGranted(NodeState parentState, Path relPath, String[] actions) throws ItemNotFoundException, RepositoryException {
-        // TODO: TOBEFIXED. 
-        ItemState wspState = parentState.getWorkspaceState();
-        if (wspState == null) {
-            Path.PathBuilder pb = new Path.PathBuilder();
-            pb.addAll(relPath.getElements());
-            while (wspState == null) {
-                pb.addFirst(parentState.getQName());
-
-                parentState = parentState.getParent();
-                wspState = parentState.getWorkspaceState();
-            }
-            try {
-                relPath = pb.getPath();
-            } catch (MalformedPathException e) {
-                throw new RepositoryException(e);
-            }
-        }
-
-
-        if (wspState == null) {
-            // internal error. should never occur
-            throw new RepositoryException("Internal error: Unable to retrieve overlayed state in hierarchy.");
-        } else {
-            NodeId parentId = ((NodeState)parentState).getNodeId();
-            // TODO: 'createNodeId' is basically wrong since isGranted is unspecific for any item.
-            ItemId id = getIdFactory().createNodeId(parentId, relPath);
-            return service.isGranted(sessionInfo, id, actions);
-        }
+        // TODO: check again.
+        // build itemId from the given state and the relative path without
+        // making an attempt to retrieve the proper id of the item possibly
+        // identified by the resulting id.
+        // the server must be able to deal with paths and with proper ids anyway.
+        // TODO: 'createNodeId' is basically wrong since isGranted is unspecific for any item.
+        ItemId id = getIdFactory().createNodeId(parentState.getNodeId(), relPath);
+        return service.isGranted(sessionInfo, id, actions);
     }
 
     /**
@@ -692,7 +647,7 @@
                 Iterator it = changeLog.getOperations();
                 while (it.hasNext()) {
                     Operation op = (Operation) it.next();
-                    log.info("executing: " + op);
+                    log.debug("executing " + op.getName());
                     op.accept(this);
                 }
             } finally {
@@ -708,7 +663,7 @@
          * Executes the operations on the repository service.
          */
         private void execute(Operation workspaceOperation) throws RepositoryException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException {
-            log.info("executing: " + workspaceOperation);
+            log.debug("executing " + workspaceOperation.getName());
             workspaceOperation.accept(this);
         }
 
@@ -729,7 +684,6 @@
         public void visit(AddProperty operation) throws RepositoryException {
             NodeId parentId = operation.getParentState().getNodeId();
             QName propertyName = operation.getPropertyName();
-            int type = operation.getPropertyType();
             if (operation.isMultiValued()) {
                 batch.addProperty(parentId, propertyName, operation.getValues());
             } else {
@@ -892,12 +846,11 @@
          */
         public void visit(ResolveMergeConflict operation) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
             try {
-                NodeId nId = operation.getNodeState().getNodeId();
+                NodeState nState = operation.getNodeState();
+                NodeId nId = nState.getNodeId();
                 NodeId vId = operation.getVersionState().getNodeId();
 
-                PropertyState mergeFailedState = (PropertyState) cache.getItemState(
-                        getIdFactory().createPropertyId(nId, QName.JCR_MERGEFAILED));
-
+                PropertyState mergeFailedState = nState.getPropertyState(QName.JCR_MERGEFAILED);
                 QValue[] vs = mergeFailedState.getValues();
 
                 NodeId[] mergeFailedIds = new NodeId[vs.length - 1];
@@ -911,9 +864,7 @@
                     // part of 'jcr:mergefailed' any more
                 }
 
-                PropertyState predecessorState = (PropertyState) cache.getItemState(
-                        getIdFactory().createPropertyId(nId, QName.JCR_PREDECESSORS));
-
+                PropertyState predecessorState = nState.getPropertyState(QName.JCR_PREDECESSORS);
                 vs = predecessorState.getValues();
 
                 boolean resolveDone = operation.resolveDone();

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,841 @@
+/*
+ * $Id$
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.jackrabbit.jcr2spi.hierarchy;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.commons.collections.list.AbstractLinkedList;
+import org.apache.commons.collections.iterators.UnmodifiableIterator;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Collections;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+
+/**
+ * <code>ChildNodeEntries</code> represents an insertion-ordered collection of
+ * <code>NodeEntry</code>s that also maintains the index values of same-name
+ * siblings on insertion and removal.
+ * <p/>
+ * <code>ChildNodeEntries</code> also provides an unmodifiable
+ * <code>Collection</code> view.
+ */
+final class ChildNodeEntries implements Collection {
+
+    private static Logger log = LoggerFactory.getLogger(ChildNodeEntries.class);
+
+    private final NodeEntryImpl parent;
+
+    /**
+     * Linked list of {@link NodeEntry} instances.
+     */
+    private final ChildNodeEntries.LinkedEntries entries = new LinkedEntries();
+
+    /**
+     * map used for lookup by name
+     * (key=name, value=either a single {@link AbstractLinkedList.Node} or a
+     * list of {@link AbstractLinkedList.Node}s which are sns entries)
+     */
+    private final Map nameMap = new HashMap();
+
+    /**
+     * Create <code>ChildNodeEntries</code> for the given node state.
+     *
+     * @param parent
+     */
+    ChildNodeEntries(NodeEntryImpl parent) {
+        this.parent = parent;
+    }
+
+    /**
+     * Returns true, if this ChildNodeEntries contains a entry that matches
+     * the given name and either index or uniqueID:<br>
+     * If <code>uniqueID</code> is not <code>null</code> the given index is
+     * ignored since it is not required to identify a child node entry.
+     * Otherwise the given index is used.
+     *
+     * @param name
+     * @param index
+     * @param uniqueID
+     * @return
+     */
+    boolean contains(QName name, int index, String uniqueID) {
+        if (uniqueID == null) {
+            return contains(name, index);
+        } else {
+            return contains(name, uniqueID);
+        }
+    }
+
+    /**
+     *
+     * @param name
+     * @param index
+     * @return
+     */
+    private boolean contains(QName name, int index) {
+        if (!nameMap.containsKey(name) || index < Path.INDEX_DEFAULT) {
+            // no matching child node entry
+            return false;
+        }
+        Object o = nameMap.get(name);
+        if (o instanceof List) {
+            // SNS
+            int listIndex = index - 1;
+            return listIndex < ((List) o).size();
+        } else {
+            // single child node with this name -> matches only if request
+            // index equals the default-index
+            return index == Path.INDEX_DEFAULT;
+        }
+    }
+
+    /**
+     *
+     * @param name
+     * @param uniqueID
+     * @return
+     */
+    private boolean contains(QName name, String uniqueID) {
+        if (uniqueID == null) {
+            throw new IllegalArgumentException();
+        }
+        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(); ) {
+                ChildNodeEntries.LinkedEntries.LinkNode n = (LinkedEntries.LinkNode) it.next();
+                NodeEntry cne = n.getNodeEntry();
+                if (uniqueID.equals(cne.getUniqueID())) {
+                    return true;
+                }
+            }
+        } else {
+            // single child node with this name
+            NodeEntry cne = ((ChildNodeEntries.LinkedEntries.LinkNode) o).getNodeEntry();
+            return uniqueID.equals(cne.getUniqueID());
+        }
+        // no matching entry found
+        return false;
+    }
+    
+    /**
+     * Returns a <code>List</code> of <code>NodeEntry</code>s for the
+     * given <code>nodeName</code>. This method does <b>not</b> filter out
+     * removed <code>NodeEntry</code>s!
+     *
+     * @param nodeName the child node name.
+     * @return same name sibling nodes with the given <code>nodeName</code>.
+     */
+    List get(QName nodeName) {
+        Object obj = nameMap.get(nodeName);
+        if (obj == null) {
+            return Collections.EMPTY_LIST;
+        }
+        if (obj instanceof List) {
+            final List sns = (List) obj;
+            // map entry is a list of siblings
+            return Collections.unmodifiableList(new AbstractList() {
+
+                public Object get(int index) {
+                    return ((LinkedEntries.LinkNode) sns.get(index)).getNodeEntry();
+                }
+
+                public int size() {
+                    return sns.size();
+                }
+
+                public Iterator iterator() {
+                    return new Iterator() {
+
+                        private Iterator iter = sns.iterator();
+
+                        public void remove() {
+                            throw new UnsupportedOperationException("remove");
+                        }
+
+                        public boolean hasNext() {
+                            return iter.hasNext();
+                        }
+
+                        public Object next() {
+                            return ((LinkedEntries.LinkNode) iter.next()).getNodeEntry();
+                        }
+                    };
+                }
+            });
+        } else {
+            // map entry is a single child node entry
+            return Collections.singletonList(((LinkedEntries.LinkNode) obj).getNodeEntry());
+        }
+    }
+
+    /**
+     * Returns the <code>NodeEntry</code> with the given
+     * <code>nodeName</code> and <code>index</code>. This method ignores
+     * <code>NodeEntry</code>s which are marked removed!
+     *
+     * @param nodeName name of the child node entry.
+     * @param index    the index of the child node entry.
+     * @return the <code>NodeEntry</code> or <code>null</code> if there
+     *         is no such <code>NodeEntry</code>.
+     */
+    NodeEntry get(QName nodeName, int index) {
+        if (index < Path.INDEX_DEFAULT) {
+            throw new IllegalArgumentException("index is 1-based");
+        }
+
+        Object obj = nameMap.get(nodeName);
+        if (obj == null) {
+            return null;
+        }
+        if (obj instanceof List) {
+            // map entry is a list of siblings
+            List siblings = (List) obj;
+            // filter out removed states
+            for (Iterator it = siblings.iterator(); it.hasNext(); ) {
+                NodeEntry cne = ((LinkedEntries.LinkNode) it.next()).getNodeEntry();
+                if (cne.isAvailable()) {
+                    try {
+                        if (cne.getNodeState().isValid()) {
+                            index--;
+                        } else {
+                            // child node removed
+                        }
+                    } catch (ItemStateException e) {
+                        // should never happen, cne.isAvailable() returned true
+                    }
+                } else {
+                    // then this child node entry has never been accessed
+                    // before and is assumed valid // TODO: check if correct.
+                    index--;
+                }
+                if (index == 0) {
+                    return cne;
+                }
+            }
+        } else {
+            // map entry is a single child node entry
+            if (index == Path.INDEX_DEFAULT) {
+                return ((LinkedEntries.LinkNode) obj).getNodeEntry();
+            }
+        }
+        return null;
+    }
+
+    /**
+     *
+     * @param nodeName
+     * @param uniqueID
+     * @return
+     * @throws IllegalArgumentException if the given uniqueID is null.
+     */
+    NodeEntry get(QName nodeName, String uniqueID) {
+        if (uniqueID == null) {
+            throw new IllegalArgumentException();
+        }
+        Iterator cneIter = (nodeName != null) ? get(nodeName).iterator() : entries.iterator();
+        while (cneIter.hasNext()) {
+            NodeEntry cne = (NodeEntry) cneIter.next();
+            if (uniqueID.equals(cne.getUniqueID())) {
+                return cne;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds a <code>NodeEntry</code> to the end of the list.
+     *
+     * @param cne the <code>NodeEntry</code> to add.
+     */
+     void add(NodeEntry cne) {
+        QName nodeName = cne.getQName();
+        List siblings = null;
+        Object obj = nameMap.get(nodeName);
+        if (obj != null) {
+            if (obj instanceof List) {
+                // map entry is a list of siblings
+                siblings = (ArrayList) obj;
+            } else {
+                // map entry is a single child node entry,
+                // convert to siblings list
+                siblings = new ArrayList();
+                siblings.add(obj);
+                nameMap.put(nodeName, siblings);
+            }
+        }
+
+        LinkedEntries.LinkNode ln = entries.add(cne);
+
+        if (siblings != null) {
+            siblings.add(ln);
+        } else {
+            nameMap.put(nodeName, ln);
+        }
+    }
+
+    /**
+     * Adds a <code>NodeEntry</code>. If an entry with the given index
+     * already exists, the the new sibling is inserted before.
+     *
+     * @param cne the <code>NodeEntry</code> to add.
+     */
+    void add(NodeEntry cne, int index) {
+        QName nodeName = cne.getQName();
+
+        // retrieve ev. sibling node with same index
+        // if index is 'undefined' behave just as '#add(NodeEntry).
+        LinkedEntries.LinkNode existing = (index < Path.INDEX_DEFAULT) ? null : getLinkNode(nodeName, index);
+
+        // add new entry (same as #add(NodeEntry)
+        List siblings = null;
+        Object obj = nameMap.get(nodeName);
+        if (obj != null) {
+            if (obj instanceof List) {
+                // map entry is a list of siblings
+                siblings = (ArrayList) obj;
+            } else {
+                // map entry is a single child node entry,
+                // convert to siblings list
+                siblings = new ArrayList();
+                siblings.add(obj);
+                nameMap.put(nodeName, siblings);
+            }
+        }
+
+        LinkedEntries.LinkNode ln = entries.add(cne);
+        if (siblings != null) {
+            siblings.add(ln);
+        } else {
+            nameMap.put(nodeName, ln);
+        }
+
+        // if new entry must be inserted instead of appended at the end
+        // reorder entries now
+        if (existing != null) {
+            reorder(obj, ln, existing);
+        }
+    }
+
+    /**
+     * Removes the child node entry with the given <code>nodeName</code> and
+     * <code>index</code>.
+     *
+     * @param nodeName the name of the child node entry to remove.
+     * @param index    the index of the child node entry to remove.
+     * @return the removed <code>NodeEntry</code> or <code>null</code>
+     *         if there is no matching <code>NodeEntry</code>.
+     */
+    NodeEntry remove(QName nodeName, int index) {
+        if (index < Path.INDEX_DEFAULT) {
+            throw new IllegalArgumentException("index is 1-based");
+        }
+
+        Object obj = nameMap.get(nodeName);
+        if (obj == null) {
+            return null;
+        }
+
+        if (obj instanceof LinkedEntries.LinkNode) {
+            // map entry is a single child node entry
+            if (index != Path.INDEX_DEFAULT) {
+                return null;
+            }
+            LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) obj;
+            nameMap.remove(nodeName);
+            // remove LinkNode from entries
+            ln.remove();
+            return ln.getNodeEntry();
+        }
+
+        // map entry is a list of siblings
+        List siblings = (List) obj;
+        if (index > siblings.size()) {
+            return null;
+        }
+
+        // remove from siblings list
+        LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) siblings.remove(index - 1);
+        NodeEntry removedEntry = ln.getNodeEntry();
+        // remove from ordered entries
+        ln.remove();
+
+        // clean up name lookup map if necessary
+        if (siblings.size() == 0) {
+            // no more entries with that name left:
+            // remove from name lookup map as well
+            nameMap.remove(nodeName);
+        } else if (siblings.size() == 1) {
+            // just one entry with that name left:
+            // discard siblings list and update name lookup map accordingly
+            nameMap.put(nodeName, siblings.get(0));
+        }
+
+        // we're done
+        return removedEntry;
+    }
+
+    /**
+     * Removes the child node entry refering to the node state.
+     *
+     * @param childEntry the entry to be removed.
+     * @return the removed entry or <code>null</code> if there is no such entry.
+     */
+    NodeEntry remove(NodeEntry childEntry) {
+        NodeEntry entry = null;
+        for (Iterator it = get(childEntry.getQName()).iterator(); it.hasNext(); ) {
+            NodeEntry tmp = (NodeEntry) it.next();
+            if (tmp == childEntry) {
+                entry = tmp;
+                break;
+            }
+        }
+        if (entry != null) {
+            return remove(entry.getQName(), entry.getIndex());
+        }
+        return entry;
+    }
+
+    /**
+     * Reorders an existing <code>NodeState</code> before another
+     * <code>NodeState</code>. If <code>beforeNode</code> is
+     * <code>null</code> <code>insertNode</code> is moved to the end of the
+     * child node entries.
+     *
+     * @param insertNode the NodeEntry to move.
+     * @param beforeNode the NodeEntry where <code>insertNode</code> is
+     * reordered to.
+     * @throws NoSuchElementException if <code>insertNode</code> or
+     * <code>beforeNode</code> does not have a <code>NodeEntry</code>
+     * in this <code>ChildNodeEntries</code>.
+     */
+    boolean reorder(NodeEntry insertNode, NodeEntry beforeNode) {
+        Object insertObj = nameMap.get(insertNode.getQName());
+        // the link node to move
+        LinkedEntries.LinkNode insertLN = getLinkNode(insertNode);
+        if (insertLN == null) {
+            return false;
+        }
+        // the link node where insertLN is ordered before
+        LinkedEntries.LinkNode beforeLN = (beforeNode != null) ? getLinkNode(beforeNode) : null;
+        if (beforeNode != null && beforeLN == null) {
+            return false;
+        }
+
+        reorder(insertObj, insertLN, beforeLN);
+        return true;
+    }
+
+    /**
+     *
+     * @param insertObj
+     * @param insertLN
+     * @param beforeLN
+     */
+    private void reorder(Object insertObj, LinkedEntries.LinkNode insertLN, LinkedEntries.LinkNode beforeLN) {
+        if (insertObj instanceof List) {
+            // adapt name lookup lists
+            List insertList = (List) insertObj;
+            if (beforeLN == null) {
+                // simply move to end of list
+                insertList.remove(insertLN);
+                insertList.add(insertLN);
+            } else {
+                // move based on position of beforeLN
+                // count our same name siblings until we reach beforeLN
+                int snsCount = 0;
+                QName insertName = insertLN.getNodeEntry().getQName();
+                for (Iterator it = entries.linkNodeIterator(); it.hasNext(); ) {
+                    LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) it.next();
+                    if (ln == beforeLN) {
+                        insertList.remove(insertLN);
+                        insertList.add(snsCount, insertLN);
+                        break;
+                    } else if (ln == insertLN) {
+                        // do not increment snsCount for node to reorder
+                    } else if (ln.getNodeEntry().getQName().equals(insertName)) {
+                        snsCount++;
+                    }
+                }
+            }
+        } else {
+            // no same name siblings -> nothing to do.
+        }
+
+        // reorder in linked list
+        entries.reorderNode(insertLN, beforeLN);
+    }
+
+    /**
+     * Returns the matching <code>LinkNode</code> from a list or a single
+     * <code>LinkNode</code>. This method will throw <code>NoSuchItemStateException</code>
+     * if none of the entries matches either due to missing entry for given
+     * state name or due to missing availability of the <code>NodeEntry</code>.
+     *
+     * @param nodeEntry the <code>NodeEntry</code> that is compared to the
+     * resolution of any <code>NodeEntry</code> that matches by name.
+     * @return the matching <code>LinkNode</code>.
+     * @throws NoSuchElementException if none of the <code>LinkNode</code>s
+     * matches.
+     */
+    private LinkedEntries.LinkNode getLinkNode(NodeEntry nodeEntry) {
+        Object listOrLinkNode = nameMap.get(nodeEntry.getQName());
+        if (listOrLinkNode == null) {
+            // no matching child node entry
+            return null;
+        }
+
+        if (listOrLinkNode instanceof List) {
+            // has same name sibling
+            for (Iterator it = ((List) listOrLinkNode).iterator(); it.hasNext();) {
+                LinkedEntries.LinkNode n = (LinkedEntries.LinkNode) it.next();
+                NodeEntry cne = n.getNodeEntry();
+                // only check available child node entries
+                if (cne.isAvailable() && cne == nodeEntry) {
+                    return n;
+                }
+            }
+        } else {
+            // single child node with this name
+            NodeEntry cne = ((LinkedEntries.LinkNode) listOrLinkNode).getNodeEntry();
+            if (cne.isAvailable() && cne == nodeEntry) {
+                return (LinkedEntries.LinkNode) listOrLinkNode;
+            }
+        }
+        // not found
+        return null;
+    }
+
+    /**
+     * Returns the matching <code>LinkNode</code> from a list or a single
+     * <code>LinkNode</code>. This method will return <code>null</code>
+     * if none of the entries matches.
+     *
+     * @param name
+     * @param index
+     * @return the matching <code>LinkNode</code> or <code>null</code>.
+     */
+    private LinkedEntries.LinkNode getLinkNode(QName name, int index) {
+        Object listOrLinkNode = nameMap.get(name);
+        if (listOrLinkNode == null) {
+            // no matching child node entry
+            return null;
+        }
+
+        if (listOrLinkNode instanceof List) {
+            // has same name sibling -> check if list size matches
+            int listIndex = index - 1;
+            List lnList = (List) listOrLinkNode;
+            if (listIndex < lnList.size()) {
+                return (LinkedEntries.LinkNode) lnList.get(listIndex);
+            }
+        } else if (index == Path.INDEX_DEFAULT) {
+            // single child node with this name -> matches is requested index
+            // equals to the default index.
+            return (LinkedEntries.LinkNode) listOrLinkNode;
+        }
+
+        // no matching entry
+        return null;
+    }
+    //--------------------------------------------< unmodifiable Collection >---
+    /**
+     * @see Collection#contains(Object)
+     */
+    public boolean contains(Object o) {
+        if (o instanceof NodeEntry) {
+            // narrow down to same name sibling nodes and check list
+            return get(((NodeEntry) o).getQName()).contains(o);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * @see Collection#containsAll(Collection)
+     */
+    public boolean containsAll(Collection c) {
+        Iterator iter = c.iterator();
+        while (iter.hasNext()) {
+            if (!contains(iter.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @see Collection#isEmpty()
+     */
+    public boolean isEmpty() {
+        return entries.isEmpty();
+    }
+
+    /**
+     * @see Collection#iterator()
+     */
+    public Iterator iterator() {
+        return UnmodifiableIterator.decorate(entries.iterator());
+    }
+
+    /**
+     * @see Collection#size()
+     */
+    public int size() {
+        return entries.size();
+    }
+
+    /**
+     * @see Collection#toArray()
+     */
+    public Object[] toArray() {
+        NodeEntry[] array = new NodeEntry[size()];
+        return toArray(array);
+    }
+
+    /**
+     * @see Collection#toArray(Object[])
+     */
+    public Object[] toArray(Object[] a) {
+        if (!a.getClass().getComponentType().isAssignableFrom(NodeEntry.class)) {
+            throw new ArrayStoreException();
+        }
+        if (a.length < size()) {
+            a = new NodeEntry[size()];
+        }
+        Iterator iter = entries.iterator();
+        int i = 0;
+        while (iter.hasNext()) {
+            a[i++] = iter.next();
+        }
+        while (i < a.length) {
+            a[i++] = null;
+        }
+        return a;
+    }
+
+    /**
+     * Throws <code>UnsupportedOperationException</code>.
+     *
+     * @see Collection#add(Object)
+     */
+    public boolean add(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Throws <code>UnsupportedOperationException</code>.
+     *
+     * @see Collection#addAll(Collection)
+     */
+    public boolean addAll(Collection c) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Throws <code>UnsupportedOperationException</code>.
+     *
+     * @see Collection#clear()
+     */
+    public void clear() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Throws <code>UnsupportedOperationException</code>.
+     *
+     * @see Collection#remove(Object)
+     */
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Throws <code>UnsupportedOperationException</code>.
+     *
+     * @see Collection#removeAll(Collection)
+     */
+    public boolean removeAll(Collection c) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Throws <code>UnsupportedOperationException</code>.
+     *
+     * @see Collection#retainAll(Collection)
+     */
+    public boolean retainAll(Collection c) {
+        throw new UnsupportedOperationException();
+    }
+
+    //-------------------------------------------------< AbstractLinkedList >---
+    /**
+     * An implementation of a linked list which provides access to the internal
+     * LinkNode which links the entries of the list.
+     */
+    private static final class LinkedEntries extends AbstractLinkedList {
+
+        LinkedEntries() {
+            super();
+            init();
+        }
+
+        /**
+         * Adds a child node entry to this list.
+         *
+         * @param cne the child node entry to add.
+         * @return the LinkNode which refers to the added <code>NodeEntry</code>.
+         */
+        LinkedEntries.LinkNode add(NodeEntry cne) {
+            LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) createNode(cne);
+            addNode(ln, header);
+            return ln;
+        }
+
+        /**
+         * Reorders an existing <code>LinkNode</code> before another existing
+         * <code>LinkNode</code>. If <code>before</code> is <code>null</code>
+         * the <code>insert</code> node is moved to the end of the list.
+         *
+         * @param insert the node to reorder.
+         * @param before the node where to reorder node <code>insert</code>.
+         */
+        void reorderNode(LinkedEntries.LinkNode insert, LinkedEntries.LinkNode before) {
+            removeNode(insert);
+            if (before == null) {
+                addNode(insert, header);
+            } else {
+                addNode(insert, before);
+            }
+        }
+
+        /**
+         * Replace the value of the given LinkNode with a new NodeEntry
+         * value.
+         *
+         * @param node
+         * @param value
+         */
+        void replaceNode(LinkedEntries.LinkNode node, NodeEntry value) {
+            updateNode(node, value);
+        }
+
+        /**
+         * Create a new <code>LinkNode</code> for a given {@link NodeEntry}
+         * <code>value</code>.
+         *
+         * @param value a child node entry.
+         * @return a wrapping {@link org.apache.jackrabbit.jcr2spi.hierarchy.ChildNodeEntries.LinkedEntries.LinkNode}.
+         * @see AbstractLinkedList#createNode(Object)
+         */
+        protected Node createNode(Object value) {
+            return new LinkedEntries.LinkNode(value);
+        }
+
+        /**
+         * @return a new <code>LinkNode</code>.
+         * @see AbstractLinkedList#createHeaderNode()
+         */
+        protected Node createHeaderNode() {
+            return new LinkedEntries.LinkNode();
+        }
+
+        /**
+         * Returns an iterator over all
+         * @return
+         */
+        Iterator linkNodeIterator() {
+            return new Iterator() {
+
+                private LinkedEntries.LinkNode next = ((LinkedEntries.LinkNode) header).getNextLinkNode();
+
+                private int expectedModCount = modCount;
+
+                public void remove() {
+                    throw new UnsupportedOperationException("remove");
+                }
+
+                public boolean hasNext() {
+                    if (expectedModCount != modCount) {
+                        throw new ConcurrentModificationException();
+                    }
+                    return next != header;
+                }
+
+                public Object next() {
+                    if (expectedModCount != modCount) {
+                        throw new ConcurrentModificationException();
+                    }
+                    if (!hasNext()) {
+                        throw new NoSuchElementException();
+                    }
+                    LinkedEntries.LinkNode n = next;
+                    next = next.getNextLinkNode();
+                    return n;
+                }
+            };
+        }
+
+        //----------------------------------------------------------------------
+
+        /**
+         * Extends the <code>AbstractLinkedList.Node</code>.
+         */
+        private final class LinkNode extends Node {
+
+            protected LinkNode() {
+                super();
+            }
+
+            protected LinkNode(Object value) {
+                super(value);
+            }
+
+            /**
+             * @return the wrapped <code>NodeEntry</code>.
+             */
+            public NodeEntry getNodeEntry() {
+                return (NodeEntry) super.getValue();
+            }
+
+            /**
+             * Removes this <code>LinkNode</code> from the linked list.
+             */
+            public void remove() {
+                removeNode(this);
+            }
+
+            /**
+             * @return the next LinkNode.
+             */
+            public LinkedEntries.LinkNode getNextLinkNode() {
+                return (LinkedEntries.LinkNode) super.getNextNode();
+            }
+        }
+    }
+}

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,88 @@
+/*
+ * 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.hierarchy;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.jcr2spi.state.TransientItemStateFactory;
+import org.apache.jackrabbit.spi.IdFactory;
+
+/**
+ * <code>EntryFactory</code>...
+ */
+public class EntryFactory {
+
+    private static Logger log = LoggerFactory.getLogger(EntryFactory.class);
+
+    /**
+     * IdFactory to create an ItemId based on the parent NodeId.
+     */
+    private final IdFactory idFactory;
+
+    private final NodeEntry rootEntry;
+
+    /**
+     *
+     */
+    private final NodeEntryListener listener;
+
+    /**
+     * The item state factory to create the the item state.
+     */
+    private final TransientItemStateFactory isf;
+
+    public EntryFactory(TransientItemStateFactory isf, IdFactory idFactory, NodeEntryListener listener) {
+        this.idFactory = idFactory;
+        this.isf = isf;
+        this.listener = listener;
+        this.rootEntry = NodeEntryImpl.createRootEntry(this);
+    }
+
+    /**
+     *
+     * @return
+     */
+    public NodeEntry createRootEntry() {
+        return rootEntry;
+    }
+
+    public IdFactory getIdFactory() {
+        return idFactory;
+    }
+
+    public TransientItemStateFactory getItemStateFactory() {
+        return isf;
+    }
+
+    public void notifyIdChange(NodeEntry entry, String previousUniqueID) {
+        listener.uniqueIdChanged(entry, previousUniqueID);
+    }
+
+    public void notifyEntryMoved(NodeEntry old, NodeEntry newEntry) {
+        // TODO: check if correct
+        if (old.getUniqueID() != null) {
+            listener.uniqueIdChanged(newEntry, old.getUniqueID());
+        }
+    }
+
+
+    //--------------------------------------------------------------------------
+    public interface NodeEntryListener {
+
+        public void uniqueIdChanged (NodeEntry entry, String previousUniqueID);
+    }
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryValidation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryValidation.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryValidation.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryValidation.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,112 @@
+/*
+ * 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.hierarchy;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
+
+import java.util.Iterator;
+
+/**
+ * <code>EntryValidation</code>...
+ */
+final class EntryValidation {
+
+    private static Logger log = LoggerFactory.getLogger(EntryValidation.class);
+
+    /**
+     * Returns <code>true</code> if the collection of child node
+     * <code>entries</code> contains at least one valid <code>NodeEntry</code>.
+     *
+     * @param nodeEntries Iterator of NodeEntries to check.
+     * @return <code>true</code> if one of the entries is valid; otherwise
+     *         <code>false</code>.
+     */
+    static boolean containsValidNodeEntry(Iterator nodeEntries) {
+        boolean hasValid = false;
+        while (nodeEntries.hasNext() && !hasValid) {
+            NodeEntry cne = (NodeEntry) nodeEntries.next();
+            hasValid = isValidNodeEntry(cne);
+        }
+        return hasValid;
+    }
+
+    /**
+     * Returns <code>true</code> if the given childnode entry is not
+     * <code>null</code> and resolves to a NodeState, that is valid or if the
+     * childnode entry has not been resolved up to now (assuming the corresponding
+     * nodestate is still valid).
+     *
+     * @param cne NodeEntry to check.
+     * @return <code>true</code> if the given entry is valid.
+     */
+    static boolean isValidNodeEntry(NodeEntry cne) {
+        // shortcut.
+        if (cne == null) {
+            return false;
+        }
+        boolean isValid = false;
+        if (cne.isAvailable()) {
+            try {
+                isValid = cne.getNodeState().isValid();
+            } catch (NoSuchItemStateException e) {
+                // may occur if the cached state is marked 'INVALIDATED' and
+                // does not exist any more on the persistent layer -> invalid.
+            } catch (ItemStateException e) {
+                // should not occur, if the cne is available.
+            }
+        } else {
+            // assume entry is valid
+            // TODO: check if this assumption is correct
+            isValid = true;
+        }
+
+        return isValid;
+    }
+
+    /**
+     * Returns <code>true</code> if the given childproperty entry is not
+     * <code>null</code> and resolves to a PropertyState, that is valid or if the
+     * childproperty entry has not been resolved up to now (assuming the corresponding
+     * PropertyState is still valid).
+     *
+     * @param cpe PropertyEntry to check.
+     * @return <code>true</code> if the given entry is valid.
+     */
+    static boolean isValidPropertyEntry(PropertyEntry cpe) {
+        if (cpe == null) {
+            return false;
+        }
+        boolean isValid = false;
+        if (cpe.isAvailable()) {
+            try {
+                isValid = cpe.getPropertyState().isValid();
+            } catch (NoSuchItemStateException e) {
+                // may occur if the cached state is marked 'INVALIDATED' and
+                // does not exist any more on the persistent layer -> invalid.
+            } catch (ItemStateException e) {
+                // probably deleted in the meantime. should not occur.
+            }
+        } else {
+            // assume entry is valid // TODO check if this assumption is correct.
+            isValid = true;
+        }
+        return isValid;
+    }
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntry.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntry.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntry.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntry.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,165 @@
+/*
+ * 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.hierarchy;
+
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.ChangeLog;
+import org.apache.jackrabbit.jcr2spi.state.StaleItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * <code>HierarchyEntry</code>...
+ */
+public interface HierarchyEntry {
+
+    /**
+     * True if this <code>HierarchyEntry</code> would resolve to a <code>NodeState</code>.
+     *
+     * @return
+     */
+    public boolean denotesNode();
+
+    /**
+     * @return the name of this hierarchy entry.
+     */
+    public QName getQName();
+
+    /**
+     * @return the path of this hierarchy entry.
+     */
+    public Path getPath() throws RepositoryException;
+
+    /**
+     * Returns the <code>NodeEntry</code> being parent to this
+     * <code>HierarchyEntry</code>.
+     *
+     * @return the parent <code>HierarchyEntry</code>
+     */
+    public NodeEntry getParent();
+
+    /**
+     * Unless this <code>HierarchyEntry</code> has been resolved this method
+     * returns {@link Status#_UNDEFINED_} otherwise it returns the status of
+     * the underlying ItemState.
+     *
+     * @return Status of the ItemState or {@link Status#_UNDEFINED_} if this
+     * entry has not been resolved yet.
+     * @see ItemState#getStatus()
+     */
+    public int getStatus();
+
+    /**
+     * Returns <code>true</code> if the referenced <code>ItemState</code> is
+     * available. That is, the referenced <code>ItemState</code> has already
+     * been resolved.<br>
+     * Note, that the validity of the ItemState is not checked.
+     *
+     * @return <code>true</code> if the <code>ItemState</code> is available;
+     * otherwise <code>false</code>.
+     * @see #getItemState()
+     */
+    public boolean isAvailable();
+
+    /**
+     * If this <code>HierarchyEntry</code> has already been resolved before
+     * (see {@link #isAvailable()}), that <code>ItemState</code> is returned.
+     * Note however, that the validity of the State is not asserted.<br>
+     * If the entry has not been resolved yet an attempt is made to resolve this
+     * entry, which may fail if there exists no accessible <code>ItemState</code>
+     * or if the corresponding state has been removed in the mean time.
+     *
+     * @return the referenced <code>ItemState</code>.
+     * @throws NoSuchItemStateException if the <code>ItemState</code> does not
+     * exist anymore.
+     * @throws ItemStateException If an error occurs while retrieving the
+     * <code>ItemState</code>.
+     */
+    public ItemState getItemState() throws NoSuchItemStateException, ItemStateException;
+
+    /**
+     * Invalidates the underlying <code>ItemState</code> if available. If the
+     * <code>recursive</code> flag is true, the hierarchy is traverses and
+     * {@link #invalidate(boolean)} is called on all child entries.<br>
+     * Note, that in contrast to {@link HierarchyEntry#reload(boolean, boolean)}
+     * this method only sets the status of this item state to {@link
+     * Status#INVALIDATED} and does not acutally update it with the persistent
+     * state in the repository.
+     */
+    public void invalidate(boolean recursive);
+
+    /**
+     * Traverses the hierarchy and reverts all transient modifications such as
+     * adding, modifying or removing item states. 'Existing' item states
+     * are reverted to their initial state and their status is reset to {@link Status#EXISTING}.
+     *
+     */
+    public void revert() throws ItemStateException;
+
+    /**
+     * Reloads this hierarchy entry and the corresponding ItemState, if this
+     * entry has already been resolved. If '<code>keepChanges</code>' is true,
+     * states with transient changes are left untouched in order to obtain stale
+     * item states. Otherwise this state gets its data reloaded from the
+     * persistent storage. If '<code>recursive</code>' the complete hierarchy
+     * below this entry is reloaded as well.
+     *
+     * @param keepChanges
+     */
+    public void reload(boolean keepChanges, boolean recursive);
+
+    /**
+     * Traverses the hierarchy and marks all available item states as transiently
+     * removed. They will change their status to either {@link Status#EXISTING_REMOVED} if
+     * the item is existing in the persistent storage or {@link Status#REMOVED}
+     * if the item has been transiently added before. In the latter case, the
+     * corresponding HierarchyEntries can be removed as well from their parent.
+     *
+     * @throws ItemStateException if an error occurs while removing any of the item
+     * state. e.g. an item state is not valid anymore.
+     */
+    public void transientRemove() throws ItemStateException;
+
+    /**
+     * Removes this <code>HierarchyEntry</code> from its parent and sets the
+     * status of the underlying ItemState to {@link Status#REMOVED} or to
+     * {@link Status#STALE_DESTROYED}, respectively. If this entry is a
+     * NodeEntry all descending ItemStates must get their status changed as well.
+     */
+    public void remove();
+
+    /**
+     * Checks if the underlying <code>ItemState</code> is available and if it
+     * has been transiently modified or if is new or stale modified. If either of
+     * the conditions is true, the state is added to the <code>ChangeLog</code>.
+     * If this <code>HierarchyEntry</code> has children it will call
+     * {@link #collectStates(ChangeLog, boolean)} recursively.
+     *
+     * @param changeLog the <code>ChangeLog</code> collecting the transient
+     * item states present in a given tree.
+     * @param throwOnStale If the given flag is true, this methods throws
+     * StaleItemStateException if this state is stale.
+     * @throws StaleItemStateException if <code>throwOnStale</code> is true and
+     * this state is stale.
+     */
+    public void collectStates(ChangeLog changeLog, boolean throwOnStale) throws StaleItemStateException;
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,402 @@
+/*
+ * 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.hierarchy;
+
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ChangeLog;
+import org.apache.jackrabbit.jcr2spi.state.StaleItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import java.lang.ref.WeakReference;
+
+/**
+ * <code>HierarchyEntryImpl</code> implements base functionality for child node
+ * and property references.
+ */
+abstract class HierarchyEntryImpl implements HierarchyEntry {
+
+    private static Logger log = LoggerFactory.getLogger(HierarchyEntryImpl.class);
+
+    /**
+     * Cached weak reference to the target ItemState.
+     * // TODO: check correct?
+     */
+    private WeakReference target;
+
+    /**
+     * The name of the target item state.
+     */
+    protected QName name;
+
+    /**
+     * The parent <code>HierarchyEntry</code>.
+     */
+    protected NodeEntryImpl parent;
+
+    /**
+     * The item state factory to create the the item state.
+     */
+    protected final EntryFactory factory;
+
+    /**
+     * Creates a new <code>HierarchyEntryImpl</code> with the given parent
+     * <code>NodeState</code>.
+     *
+     * @param parent the <code>NodeEntry</code> that owns this child node
+     *               reference.
+     * @param name   the name of the child item.
+     * @param factory
+     */
+    HierarchyEntryImpl(NodeEntryImpl parent, QName name, EntryFactory factory) {
+        this.parent = parent;
+        this.name = name;
+        this.factory = factory;
+    }
+    
+    /**
+     * Resolves this <code>HierarchyEntryImpl</code> and returns the target
+     * <code>ItemState</code> of this reference. This method may return a
+     * cached <code>ItemState</code> if this method was called before already
+     * otherwise this method will forward the call to {@link #doResolve()}
+     * and cache its return value. If an existing state has been invalidated
+     * before, an attempt is made to reload it in order to make sure, that
+     * a call to {@link ItemState#isValid()} does not equivocally return false.
+     *
+     * @return the <code>ItemState</code> where this reference points to.
+     * @throws NoSuchItemStateException if the referenced <code>ItemState</code>
+     *                                  does not exist.
+     * @throws ItemStateException       if an error occurs.
+     */
+    ItemState resolve() throws NoSuchItemStateException, ItemStateException {
+        // check if already resolved
+        ItemState state = internalGetItemState();
+        // not yet resolved. retrieve and keep weak reference to state
+        if (state == null) {
+            state = doResolve();
+            target = new WeakReference(state);
+        } else if (state.getStatus() == Status.INVALIDATED) {
+            // completely reload this entry, but don't reload recursively
+            reload(false, false);
+        }
+        return state;
+    }
+
+    /**
+     * Resolves this <code>HierarchyEntryImpl</code> and returns the target
+     * <code>ItemState</code> of this reference.
+     *
+     * @return the <code>ItemState</code> where this reference points to.
+     * @throws NoSuchItemStateException if the referenced <code>ItemState</code>
+     *                                  does not exist.
+     * @throws ItemStateException       if an error occurs.
+     */
+    abstract ItemState doResolve() throws NoSuchItemStateException, ItemStateException;
+
+    /**
+     * 
+     * @return
+     */
+    ItemState internalGetItemState() {
+        if (target != null) {
+            ItemState state = (ItemState) target.get();
+            if (state != null) {
+                return state;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set the target of this HierarchyEntry to the given new ItemState.
+     *
+     * @throws IllegalStateException if this entry has already been resolved.
+     * @throws IllegalArgumentException if the given state is <code>null</code>
+     * or has another Status than {@link Status#NEW} or in case of class mismatch.
+     */
+    void internalSetItemState(ItemState newItemState) {
+        if (target != null || newItemState == null) {
+            throw new IllegalStateException();
+        }
+
+        if ((denotesNode() && newItemState.isNode()) || (!denotesNode() && !newItemState.isNode())) {
+            target = new WeakReference(newItemState);
+        } else {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    //-----------------------------------------------------< HierarchyEntry >---
+    /**
+     * @inheritDoc
+     * @see HierarchyEntry#getQName()
+     */
+    public QName getQName() {
+        return name;
+    }
+
+    /**
+     * @inheritDoc
+     * @see HierarchyEntry#getPath()
+     */
+    public Path getPath() throws RepositoryException {
+        // shortcut for root state
+        if (parent == null) {
+            return Path.ROOT;
+        }
+
+        // build path otherwise
+        try {
+            Path.PathBuilder builder = new Path.PathBuilder();
+            buildPath(builder, this);
+            return builder.getPath();
+        } catch (MalformedPathException e) {
+            String msg = "Failed to build path of " + this;
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * Adds the path element of an item id to the path currently being built.
+     * On exit, <code>builder</code> contains the path of <code>state</code>.
+     *
+     * @param builder builder currently being used
+     * @param hEntry HierarchyEntry of the state the path should be built for.
+     */
+    private void buildPath(Path.PathBuilder builder, HierarchyEntry hEntry) {
+        NodeEntry parentEntry = hEntry.getParent();
+        // shortcut for root state
+        if (parentEntry == null) {
+            builder.addRoot();
+            return;
+        }
+
+        // recursively build path of parent
+        buildPath(builder, parentEntry);
+
+        QName name = hEntry.getQName();
+        if (hEntry.denotesNode()) {
+            int index = ((NodeEntry) hEntry).getIndex();
+            // add to path
+            if (index == Path.INDEX_DEFAULT) {
+                builder.addLast(name);
+            } else {
+                builder.addLast(name, index);
+            }
+        } else {
+            // property-state: add to path
+            builder.addLast(name);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see HierarchyEntry#getParent()
+     */
+    public NodeEntry getParent() {
+        return parent;
+    }
+
+    /**
+     * @inheritDoc
+     * @see HierarchyEntry#getStatus()
+     */
+    public int getStatus() {
+        ItemState state = internalGetItemState();
+        if (state == null) {
+            return Status._UNDEFINED_;
+        } else {
+            return state.getStatus();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see HierarchyEntry#isAvailable()
+     */
+    public boolean isAvailable() {
+        ItemState state = null;
+        if (target != null) {
+            state = (ItemState) target.get();
+        }
+        return state != null;
+    }
+
+    /**
+     * {@inheritDoc}<br>
+     * @see HierarchyEntry#getItemState()
+     */
+    public ItemState getItemState() throws NoSuchItemStateException, ItemStateException {
+        ItemState state = resolve();
+        return state;
+    }
+
+    /**
+     * {@inheritDoc}<br>
+     * @see HierarchyEntry#invalidate(boolean)
+     */
+    public void invalidate(boolean recursive) {
+        ItemState state = internalGetItemState();
+        if (state != null) {
+            // session-state TODO: only invalidate if existing?
+            if (state.getStatus() == Status.EXISTING) {
+                state.setStatus(Status.INVALIDATED);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see HierarchyEntry#revert()
+     */
+    public void revert() throws ItemStateException {
+        ItemState state = internalGetItemState();
+        if (state == null) {
+            // nothing to do
+            return;
+        }
+
+        int oldStatus = state.getStatus();
+        switch (oldStatus) {
+            case Status.EXISTING_MODIFIED:
+            case Status.STALE_MODIFIED:
+                // revert state from overlayed
+                state.merge(state.getWorkspaceState(), false);
+                state.setStatus(Status.EXISTING);
+                break;
+            case Status.EXISTING_REMOVED:
+                // revert state from overlayed
+                state.merge(state.getWorkspaceState(), false);
+                state.setStatus(Status.EXISTING);
+                if (!denotesNode()) {
+                    parent.revertPropertyRemoval((PropertyEntry) this);
+                }
+                break;
+            case Status.NEW:
+                // reverting a NEW state is equivalent to its removal.
+                remove();
+                break;
+            case Status.STALE_DESTROYED:
+                // overlayed does not exist any more -> remove it
+                remove();
+                break;
+            default:
+                // Cannot revert EXISTING, REMOVED, INVALIDATED, MODIFIED states.
+                // State was implicitely reverted
+                log.debug("State with status " + oldStatus + " cannot be reverted.");
+        }
+    }
+
+     /**
+     * {@inheritDoc}
+     * @see HierarchyEntry#reload(boolean, boolean)
+     */
+    public void reload(boolean keepChanges, boolean recursive) {
+        ItemState state = internalGetItemState();
+        if (state == null) {
+            // nothing to do. entry will be validated upon resolution.
+            return;
+        }
+        /*
+        if keepChanges is true only existing or invalidated states must be
+        updated. otherwise the state gets updated and might be marked 'Stale'
+        if transient changes are present and the workspace-state is modified.
+        */
+        // TODO: check again if 'reconnect' is not possible for transiently-modified state
+        if (!keepChanges || state.getStatus() == Status.EXISTING
+            || state.getStatus() == Status.INVALIDATED) {
+            // reload the workspace state from the persistent layer
+            try {
+                state.reconnect(keepChanges);
+            } catch (NoSuchItemStateException e) {
+                // remove hierarchyEntry (including all children and set
+                // state-status to REMOVED (or STALE_DESTROYED)
+                remove();
+            } catch (ItemStateException e) {
+                // TODO: rather throw? remove from parent?
+                log.warn("Exception while reloading property state: " + e);
+                log.debug("Stacktrace: ", e);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see HierarchyEntry#transientRemove()
+     */
+    public void transientRemove() throws ItemStateException {
+        ItemState state = internalGetItemState();
+        if (state == null) {
+            // nothing to do -> correct status must be set upon resolution.
+            return;
+        } else {
+            state.checkIsSessionState();
+            if (!state.isValid()) {
+                throw new ItemStateException("Cannot remove an invalid ItemState");
+            }
+            int oldStatus = state.getStatus();
+            if (oldStatus == Status.NEW) {
+                remove();
+            } else {
+                state.setStatus(Status.EXISTING_REMOVED);
+                // NOTE: parent does not need to be informed. an transiently
+                // removed propertyEntry is automatically moved to the 'attic'
+                // if a conflict with a new entry occurs.
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see HierarchyEntry#collectStates(ChangeLog, boolean)
+     */
+    public void collectStates(ChangeLog changeLog, boolean throwOnStale) throws StaleItemStateException {
+        ItemState state = internalGetItemState();
+        if (state == null) {
+            // nothing to do
+            return;
+        }
+
+        if (throwOnStale && Status.isStale(state.getStatus())) {
+            String msg = "Cannot save changes: " + state + " has been modified externally.";
+            log.debug(msg);
+            throw new StaleItemStateException(msg);
+        }
+        // only interested in transient modifications or stale-modified states
+        switch (state.getStatus()) {
+            case Status.NEW:
+                changeLog.added(state);
+                break;
+            case Status.EXISTING_MODIFIED:
+            case Status.STALE_MODIFIED:
+                changeLog.modified(state);
+                break;
+            case Status.EXISTING_REMOVED:
+                changeLog.deleted(state);
+                break;
+            default:
+                log.debug("Collecting states: Ignored ItemState with status " + Status.getName(state.getStatus()));
+        }
+    }
+}

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEventListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEventListener.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEventListener.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEventListener.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,182 @@
+/*
+ * 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.hierarchy;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.jcr2spi.observation.InternalEventListener;
+import org.apache.jackrabbit.jcr2spi.WorkspaceManager;
+import org.apache.jackrabbit.jcr2spi.config.CacheBehaviour;
+import org.apache.jackrabbit.spi.EventFilter;
+import org.apache.jackrabbit.spi.Event;
+import org.apache.jackrabbit.spi.EventBundle;
+import org.apache.jackrabbit.spi.EventIterator;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.name.Path;
+
+import javax.jcr.RepositoryException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * <code>HierarchyEventListener</code>...
+ */
+public class HierarchyEventListener implements InternalEventListener {
+
+    private static Logger log = LoggerFactory.getLogger(HierarchyEventListener.class);
+
+    private final HierarchyManager hierarchyMgr;
+    private final Collection eventFilter;
+
+    public HierarchyEventListener(WorkspaceManager wspManager,
+                                  HierarchyManager hierarchyMgr,
+                                  CacheBehaviour cacheBehaviour) {
+        this.hierarchyMgr = hierarchyMgr;
+        if (cacheBehaviour == CacheBehaviour.OBSERVATION) {
+            EventFilter filter = null;
+            try {
+                // TODO: improve. for now listen to everything
+                filter = wspManager.createEventFilter(Event.ALL_TYPES, Path.ROOT, true, null, null, false);
+            } catch (RepositoryException e) {
+                // spi does not support observation, or another error occurred.
+            }
+            this.eventFilter = (filter == null) ? Collections.EMPTY_LIST : Collections.singletonList(filter);
+        } else {
+            this.eventFilter = Collections.EMPTY_LIST;
+        }
+        wspManager.addEventListener(this);
+    }
+
+    //----------------------------------------------< InternalEventListener >---
+    /**
+     * @see InternalEventListener#getEventFilters()
+     */
+    public Collection getEventFilters() {
+        return eventFilter;
+    }
+
+    /**
+     * Processes <code>events</code> and invalidates cached <code>ItemState</code>s
+     * accordingly. Note that this performed for both local and non-local changes,
+     * since workspace operations are reported as local changes as well and
+     * might have invoked changes (autocreated items etc.).
+     *
+     * @param eventBundle
+     * @see InternalEventListener#onEvent(EventBundle)
+     */
+    public void onEvent(EventBundle eventBundle) {
+        pushEvents(getEventCollection(eventBundle));
+    }
+
+    /**
+     * Retrieve the workspace state(s) affected by the given event and refresh
+     * them accordingly.
+     *
+     * @param events
+     */
+    private void pushEvents(Collection events) {
+        if (events.isEmpty()) {
+            return;
+        }
+        // collect set of removed node ids
+        Set removedEvents = new HashSet();
+        // separately collect the add events
+        Set addEvents = new HashSet();
+
+        for (Iterator it = events.iterator(); it.hasNext();) {
+            Event event = (Event) it.next();
+            int type = event.getType();
+            if (type == Event.NODE_REMOVED) {
+                // remember removed nodes separately for proper handling later on.
+                removedEvents.add(event.getItemId());
+            } else if (type == Event.NODE_ADDED || type == Event.PROPERTY_ADDED) {
+                addEvents.add(event);
+                it.remove();
+            }
+        }
+
+        /* Process ADD-events.
+           In case of persisting transients modifications, the event-set may
+           still contain events that are not covered by the changeLog such as
+           new version-history or other autocreated properties and nodes.
+
+           Add events need to be processed hierarchically, since its not possible
+           to add a new child reference to a state that is not yet present in
+           the state manager.
+           The 'progress' flag is used to make sure, that during each loop at
+           least one event has been processed and removed from the iterator.
+           If this is not the case, there are not parent states present in the
+           state manager that need to be updated and the remaining events may
+           be ignored.
+         */
+        boolean progress = true;
+        while (!addEvents.isEmpty() && progress) {
+            progress = false;
+            for (Iterator it = addEvents.iterator(); it.hasNext();) {
+                Event ev = (Event) it.next();
+                NodeEntry parent = (ev.getParentId() != null) ? (NodeEntry) hierarchyMgr.lookup(ev.getParentId()) : null;
+                if (parent != null) {
+                    parent.refresh(ev);
+                    it.remove();
+                    progress = true;
+                }
+            }
+        }
+
+        /* process all other events (removal, property changed) */
+        for (Iterator it = events.iterator(); it.hasNext(); ) {
+            Event event = (Event) it.next();
+            int type = event.getType();
+
+            NodeId parentId = event.getParentId();
+            NodeEntry parent = (parentId != null) ? (NodeEntry) hierarchyMgr.lookup(parentId) : null;
+            if (type == Event.NODE_REMOVED || type == Event.PROPERTY_REMOVED) {
+                // notify parent about removal if its child-entry.
+                // - if parent is 'null' (i.e. not yet loaded) the child-entry does
+                //   not exist either -> no need to inform child-entry
+                // - if parent got removed with the same event-bundle
+                //   only remove the parent an skip this event.
+                if (parent != null && !removedEvents.contains(parentId)) {
+                    parent.refresh(event);
+                }
+            } else if (type == Event.PROPERTY_CHANGED) {
+                // notify parent in case jcr:mixintypes or jcr:uuid was changed.
+                // if parent is 'null' (i.e. not yet loaded) the prop-entry does
+                // not exist either -> no need to inform propEntry
+                if (parent != null) {
+                    parent.refresh(event);
+                }
+            } else {
+                // should never occur
+                throw new IllegalArgumentException("Invalid event type: " + event.getType());
+            }
+        }
+    }
+
+    private static Collection getEventCollection(EventBundle eventBundle) {
+        List evs = new ArrayList();
+        for (EventIterator it = eventBundle.getEvents(); it.hasNext();) {
+           evs.add(it.nextEvent());
+        }
+        return evs;
+    }
+}
\ No newline at end of file

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

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