jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ang...@apache.org
Subject svn commit: r506927 [3/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...
Date Tue, 13 Feb 2007 09:31:53 GMT
Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManager.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManager.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManager.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,111 @@
+/*
+ * 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.Path;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.spi.ItemId;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.ItemNotFoundException;
+
+/**
+ * <code>HierarchyManager</code>...
+ */
+public interface HierarchyManager {
+
+    /**
+     * Dispose this <code>HierarchyManager</code>
+     */
+    public void dispose();
+
+    /**
+     * 
+     * @return
+     */
+    public NodeEntry getRootEntry();
+
+    /**
+     * If the Hierarchy already lists the entry with the given itemId it is
+     * returned otherwise <code>null</code>. See {@link #getHierarchyEntry(ItemId)}
+     * for a method that resolves the ItemId including lookup in the persistence
+     * layer if the entry has not been loaded yet.
+     *
+     * @param itemId
+     * @return
+     */
+    public HierarchyEntry lookup(ItemId itemId);
+
+    /**
+     * Resolves a itemId into a <code>HierarchyEntry</code>.
+     *
+     * @param itemId
+     * @return
+     * @throws PathNotFoundException
+     * @throws RepositoryException
+     */
+    public HierarchyEntry getHierarchyEntry(ItemId itemId) throws PathNotFoundException, RepositoryException;
+
+    /**
+     * Resolves a path into a <code>HierarchyEntry</code>.
+     *
+     * @param qPath
+     * @return
+     * @throws PathNotFoundException
+     * @throws RepositoryException
+     */
+    public HierarchyEntry getHierarchyEntry(Path qPath) throws PathNotFoundException, RepositoryException;
+
+    /**
+     * Retrieves the <code>HierarchyEntry</code> corresponding to the given
+     * path and resolves it to the underlying <code>ItemState</code>. 
+     *
+     * @param qPath
+     * @return
+     * @throws PathNotFoundException
+     * @throws RepositoryException
+     */
+    public ItemState getItemState(Path qPath) throws PathNotFoundException, RepositoryException;
+
+    /**
+     * Returns the depth of the specified item. The depth reflects the
+     * absolute hierarchy level.
+     *
+     * @param hierarchyEntry
+     * @return the depth of the specified item
+     * @throws RepositoryException if another error occurs
+     */
+    public int getDepth(HierarchyEntry hierarchyEntry) throws ItemNotFoundException, RepositoryException;
+
+    /**
+     * Returns the depth of the specified descendant relative to the given
+     * ancestor. If <code>ancestor</code> and <code>descendant</code>
+     * denote the same item 0 is returned. If <code>ancestor</code> does not
+     * denote an ancestor -1 is returned.
+     *
+     * @param ancestor NodeEntry that must be an ancestor of the descendant
+     * @param descendant HierarchyEntry
+     * @return the relative depth; -1 if <code>ancestor</code> does not
+     * denote an ancestor of the item denoted by <code>descendant</code>
+     * (or itself).
+     * @throws ItemNotFoundException If either of the specified id's does not
+     * denote an existing item.
+     * @throws RepositoryException If another error occurs.
+     */
+    public int getRelativeDepth(NodeEntry ancestor, HierarchyEntry descendant) throws ItemNotFoundException, RepositoryException;
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManagerImpl.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManagerImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyManagerImpl.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,177 @@
+/*
+ * $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.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.TransientItemStateFactory;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.IdFactory;
+import org.apache.jackrabbit.spi.NodeId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.ItemNotFoundException;
+
+/**
+ * <code>HierarchyManagerImpl</code> implements the <code>HierarchyManager</code>
+ * interface.
+ */
+public class HierarchyManagerImpl implements HierarchyManager {
+
+    private static Logger log = LoggerFactory.getLogger(org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManagerImpl.class);
+
+    private final NodeEntry rootEntry;
+    private final UniqueIdResolver uniqueIdResolver;
+
+    private final ItemStateFactory isf;
+    private final IdFactory idFactory;
+
+    public HierarchyManagerImpl(TransientItemStateFactory isf, IdFactory idFactory) {
+        uniqueIdResolver = new UniqueIdResolver(isf);
+        rootEntry = new EntryFactory(isf, idFactory, uniqueIdResolver).createRootEntry();
+
+        this.isf = isf;
+        this.idFactory = idFactory;
+    }
+
+    //---------------------------------------------------< HierarchyManager >---
+    /**
+     * @see HierarchyManager#dispose()
+     */
+    public void dispose() {
+        uniqueIdResolver.dispose();
+    }
+
+    /**
+     * @see HierarchyManager#getRootEntry()
+     */
+    public NodeEntry getRootEntry() {
+        return rootEntry;
+    }
+
+    /**
+     * @see HierarchyManager#lookup(ItemId)
+     */
+    public HierarchyEntry lookup(ItemId itemId) {
+        String uniqueID = itemId.getUniqueID();
+        if (uniqueID == null) {
+            return PathResolver.lookup(rootEntry, itemId.getPath());
+        } else {
+            NodeEntry nEntry = uniqueIdResolver.lookup(idFactory.createNodeId(uniqueID));
+            if (itemId.getPath() == null) {
+                return nEntry;
+            } else {
+                return PathResolver.lookup(nEntry, itemId.getPath());
+            }
+        }
+    }
+
+    /**
+     * @see HierarchyManager#getHierarchyEntry(ItemId)
+     */
+    public HierarchyEntry getHierarchyEntry(ItemId itemId) throws PathNotFoundException, RepositoryException {
+        String uniqueID = itemId.getUniqueID();
+        if (uniqueID == null) {
+            return getHierarchyEntry(itemId.getPath());
+        } else {
+            if (itemId.getPath() == null) {
+                NodeEntry nEntry = uniqueIdResolver.resolve((NodeId) itemId, rootEntry);
+                return nEntry;
+            } else {
+                NodeEntry nEntry = uniqueIdResolver.resolve(idFactory.createNodeId(uniqueID), rootEntry);
+                return nEntry.getDeepEntry(itemId.getPath());
+            }
+        }
+    }
+
+    /**
+     * @see HierarchyManager#getHierarchyEntry(Path)
+     */
+    public HierarchyEntry getHierarchyEntry(Path qPath) throws PathNotFoundException, RepositoryException {
+        NodeEntry rootEntry = getRootEntry();
+        // shortcut
+        if (qPath.denotesRoot()) {
+            return rootEntry;
+        }
+
+        if (!qPath.isCanonical()) {
+            String msg = "Path is not canonical";
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+
+        return rootEntry.getDeepEntry(qPath);
+    }
+
+    /**
+     * @see HierarchyManager#getItemState(Path)
+     */
+    public ItemState getItemState(Path qPath) throws PathNotFoundException, RepositoryException {
+        HierarchyEntry entry = getHierarchyEntry(qPath);
+        try {
+            ItemState state = entry.getItemState();
+            if (state.isValid()) {
+                return state;
+            } else {
+                throw new PathNotFoundException();
+            }
+        } catch (NoSuchItemStateException e) {
+            throw new PathNotFoundException(e);
+        } catch (ItemStateException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    /**
+     * @see HierarchyManager#getDepth(HierarchyEntry)
+     */
+    public int getDepth(HierarchyEntry hierarchyEntry) throws ItemNotFoundException, RepositoryException {
+        int depth = Path.ROOT_DEPTH;
+        NodeEntry parentEntry = hierarchyEntry.getParent();
+        while (parentEntry != null) {
+            depth++;
+            hierarchyEntry = parentEntry;
+            parentEntry = hierarchyEntry.getParent();
+        }
+        return depth;
+    }
+
+    /**
+     * @see HierarchyManager#getRelativeDepth(NodeEntry, HierarchyEntry)
+     */
+    public int getRelativeDepth(NodeEntry ancestor, HierarchyEntry descendant)
+            throws ItemNotFoundException, RepositoryException {
+        if (ancestor.equals(descendant)) {
+            return 0;
+        }
+        int depth = 1;
+        NodeEntry parent = descendant.getParent();
+        while (parent != null) {
+            if (parent.equals(ancestor)) {
+                return depth;
+            }
+            depth++;
+            descendant = parent;
+            parent = descendant.getParent();
+        }
+        // not an ancestor
+        return -1;
+    }
+}

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,242 @@
+/*
+ * 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.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.Event;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.RepositoryException;
+import javax.jcr.PathNotFoundException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Collection;
+
+/**
+ * <code>NodeEntry</code>...
+ */
+public interface NodeEntry extends HierarchyEntry {
+
+    /**
+     * @return the <code>NodeId</code> of this child node entry.
+     */
+    public NodeId getId();
+
+    /**
+     * @return the unique ID of the node state which is referenced by this
+     * child node entry or <code>null</code> if the node state cannot be
+     * identified with a unique ID.
+     */
+    public String getUniqueID();
+
+    /**
+     *
+     * @param uniqueID
+     */
+    public void setUniqueID(String uniqueID);
+
+    /**
+     * @return the index of this child node entry to suppport same-name siblings.
+     * If the index of this entry cannot be determined
+     * {@link org.apache.jackrabbit.name.Path#INDEX_UNDEFINED} is returned.
+     */
+    public int getIndex();
+
+    /**
+     * @return the referenced <code>NodeState</code>.
+     * @throws NoSuchItemStateException if the <code>NodeState</code> does not
+     * exist anymore.
+     * @throws ItemStateException If an error occurs while retrieving the
+     * <code>NodeState</code>.
+     */
+    public NodeState getNodeState() throws NoSuchItemStateException, ItemStateException;
+
+    /**
+     * Traverse the tree below this entry and return the child entry matching
+     * the given path. If that entry has not been loaded yet, try to do so.
+     * NOTE: In contrast to 'getNodeEntry', getNodeEntries, getPropertyEntry
+     * and getPropertyEntries this method may return invalid entries, i.e.
+     * entries connected to a removed or stale ItemState.
+     *
+     * @param path
+     * @return the entry at the given path.
+     */
+    public HierarchyEntry getDeepEntry(Path path) throws PathNotFoundException, RepositoryException;
+
+    /**
+     * Determines if there is a valid <code>NodeEntry</code> with the
+     * specified <code>nodeName</code>.
+     *
+     * @param nodeName <code>QName</code> object specifying a node name
+     * @return <code>true</code> if there is a <code>NodeEntry</code> with
+     * the specified <code>nodeName</code>.
+     */
+    public boolean hasNodeEntry(QName nodeName);
+
+    /**
+     * Determines if there is a valid <code>NodeEntry</code> with the
+     * specified <code>name</code> and <code>index</code>.
+     *
+     * @param nodeName  <code>QName</code> object specifying a node name.
+     * @param index 1-based index if there are same-name child node entries.
+     * @return <code>true</code> if there is a <code>NodeEntry</code> with
+     * the specified <code>name</code> and <code>index</code>.
+     */
+    public boolean hasNodeEntry(QName nodeName, int index);
+
+    /**
+     * Returns the valid <code>NodeEntry</code> with the specified name
+     * and index or <code>null</code> if there's no matching entry.
+     *
+     * @param nodeName <code>QName</code> object specifying a node name.
+     * @param index 1-based index if there are same-name child node entries.
+     * @return The <code>NodeEntry</code> with the specified name and index
+     * or <code>null</code> if there's no matching entry.
+     */
+    public NodeEntry getNodeEntry(QName nodeName, int index);
+
+    /**
+     * Returns the <code>NodeEntry</code> with the specified
+     * <code>NodeId</code> or <code>null</code> if there's no matching
+     * entry.
+     *
+     * @param childId the id of the child entry.
+     * @return the <code>NodeEntry</code> with the specified
+     * <code>NodeId</code> or <code>null</code> if there's no matching entry.
+     */
+    public NodeEntry getNodeEntry(NodeId childId);
+
+    /**
+     * Returns a unmodifiable iterator of <code>NodeEntry</code> objects
+     * denoting the the valid child NodeEntries present on this <code>NodeEntry</code>.
+     *
+     * @return iterator of <code>NodeEntry</code> objects
+     */
+    public Iterator getNodeEntries();
+
+    /**
+     * Returns a unmodifiable List of <code>NodeEntry</code>s with the
+     * specified name.
+     *
+     * @param nodeName name of the child node entries that should be returned
+     * @return list of <code>NodeEntry</code> objects
+     */
+    public List getNodeEntries(QName nodeName);
+
+    /**
+     * Adds a new child NodeEntry to this entry.
+     *
+     * @param nodeName
+     * @param uniqueID
+     * @return the new <code>NodeEntry</code>
+     */
+    public NodeEntry addNodeEntry(QName nodeName, String uniqueID, int index);
+
+    /**
+     * Adds a new, transient child <code>NodeEntry</code>
+     *
+     * @return
+     */
+    public NodeState addNewNodeEntry(QName nodeName, String uniqueID, QName primaryNodeType, QNodeDefinition definition) throws ItemExistsException;
+
+    /**
+     * @param newName
+     * @param newParent
+     * @return
+     */
+    public NodeEntry moveNodeEntry(NodeState childState, QName newName, NodeEntry newParent) throws RepositoryException;
+
+    /**
+     * Determines if there is a property entry with the specified <code>QName</code>.
+     *
+     * @param propName <code>QName</code> object specifying a property name
+     * @return <code>true</code> if there is a property entry with the specified
+     * <code>QName</code>.
+     */
+    public boolean hasPropertyEntry(QName propName);
+
+    /**
+     * Returns the valid <code>PropertyEntry</code> with the specified name
+     * or <code>null</code> if no matching entry exists.
+     *
+     * @param propName <code>QName</code> object specifying a property name.
+     * @return The <code>PropertyEntry</code> with the specified name or
+     * <code>null</code> if no matching entry exists.
+     */
+    public PropertyEntry getPropertyEntry(QName propName);
+
+    /**
+     * Returns an unmodifiable Iterator over those children that represent valid
+     * PropertyEntries.
+     *
+     * @return
+     */
+    public Iterator getPropertyEntries();
+
+    /**
+     *
+     * @param propName
+     * @return
+     */
+    public PropertyEntry addPropertyEntry(QName propName) throws ItemExistsException;
+
+    /**
+     * Adds property entries for the given <code>QName</code>s. It depends on
+     * the status of this <code>NodeEntry</code>, how conflicts are resolved
+     * and whether or not existing entries that are missing in the iterator
+     * get removed.
+     *
+     * @param propNames
+     * @throws ItemExistsException
+     */
+    public void addPropertyEntries(Collection propNames) throws ItemExistsException;
+
+    /**
+     *
+     * @param propName
+     * @return
+     */
+    public PropertyState addNewPropertyEntry(QName propName, QPropertyDefinition definition) throws ItemExistsException;
+
+    /**
+     * Reorders this NodeEntry before the sibling entry specified by the given
+     * <code>beforeEntry</code>.
+     *
+     * @param beforeEntry the child node where to insert the node before. If
+     * <code>null</code> this entry is moved to the end of its parents child node entries.
+     * @return true if the reordering was successful. False if either of the
+     * given entry does not exist in the listed child entries..
+     */
+    public boolean orderBefore(NodeEntry beforeEntry) ;
+
+    /**
+     * The parent entry of a external event gets informed about the modification.
+     * Note, that {@link Event#getParentId()} of the given childEvent must point
+     * to this <code>NodeEntry</code>.
+     *
+     * @param childEvent
+     */
+    public void refresh(Event childEvent) ;
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,1018 @@
+/*
+ * 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.spi.NodeId;
+import org.apache.jackrabbit.spi.Event;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.ChildInfo;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.IdFactory;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+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 org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.jcr2spi.util.StateUtility;
+import org.apache.commons.collections.iterators.IteratorChain;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.RepositoryException;
+import javax.jcr.PathNotFoundException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * <code>NodeEntryImpl</code> implements common functionality for child
+ * node entry implementations.
+ */
+public class NodeEntryImpl extends HierarchyEntryImpl implements NodeEntry {
+
+    // TODO: TOBEFIXED attic for transiently removed or moved child-node-entries required, in order 'know' that they are remove instead of retrieving them from the server again.
+
+    private static Logger log = LoggerFactory.getLogger(NodeEntryImpl.class);
+
+    /**
+     * UniqueID identifying this NodeEntry or <code>null</code> if either
+     * the underlying state has not been loaded yet or if it cannot be
+     * identified with a unique ID.
+     */
+    private String uniqueID;
+
+    /**
+     * insertion-ordered collection of NodeEntry objects
+     */
+    private ChildNodeEntries childNodeEntries;
+
+    /**
+     * Map of properties. Key = {@link QName} of property. Value = {@link
+     * PropertyEntry}.
+     */
+    private final HashMap properties = new HashMap();
+
+    /**
+     * Map of properties which are deleted and have been re-created as transient
+     * property with the same name.
+     */
+    private final HashMap propertiesInAttic = new HashMap();
+
+    /**
+     * Creates a new <code>NodeEntryImpl</code>
+     *
+     * @param parent    the <code>NodeEntry</code> that owns this child item
+     *                  reference.
+     * @param name      the name of the child node.
+     * @param factory   the entry factory.
+     */
+    NodeEntryImpl(NodeEntryImpl parent, QName name, String uniqueID, EntryFactory factory) {
+        super(parent, name, factory);
+        this.uniqueID = uniqueID; // NOTE: don't use setUniqueID (for mod only)
+    }
+
+    /**
+     *
+     * @return
+     */
+    static NodeEntry createRootEntry(EntryFactory factory) {
+        return new NodeEntryImpl(null, QName.ROOT, null, factory);
+    }
+
+    //-----------------------------------------------------< HierarchyEntry >---
+    /**
+     * Returns true.
+     *
+     * @inheritDoc
+     * @see HierarchyEntry#denotesNode()
+     */
+    public boolean denotesNode() {
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     * @see HierarchyEntry#invalidate(boolean)
+     */
+    public void invalidate(boolean recursive) {
+        if (recursive) {
+            // invalidate all child entries including properties present in the
+            // attic (removed props shadowed by a new property with the same name).
+            for (Iterator it = getAllChildEntries(false, true); it.hasNext();) {
+                HierarchyEntry ce = (HierarchyEntry) it.next();
+                ce.invalidate(recursive);
+            }
+        }
+        // ... and invalidate the resolved state (if available)
+        super.invalidate(recursive);
+    }
+
+    /**
+     * If 'recursive' is true, the complete hierarchy below this entry is
+     * traversed and reloaded. Otherwise only this entry and the direct
+     * decendants are reloaded.
+     *
+     * @see HierarchyEntry#reload(boolean, boolean)
+     */
+    public void reload(boolean keepChanges, boolean recursive) {
+        // reload this entry
+        super.reload(keepChanges, recursive);
+
+        // reload all children unless 'recursive' is false and the reload above
+        // did not cause this entry to be removed -> therefore check status.
+        if (recursive && !Status.isTerminal(getStatus())) {
+            // recursivly reload all entries including props that are in the attic.
+            for (Iterator it = getAllChildEntries(true, true); it.hasNext();) {
+                HierarchyEntry ce = (HierarchyEntry) it.next();
+                ce.reload(keepChanges, recursive);
+            }
+        }
+    }
+
+    /**
+     * Calls {@link HierarchyEntryImpl#revert()} and moves all properties from the
+     * attic back into th properties map. If this HierarchyEntry has been
+     * transiently moved, it is in addition moved back to its old parent.
+     *
+     * @inheritDoc
+     * @see HierarchyEntry#revert()
+     */
+    public void revert() throws ItemStateException {
+        // move all properties from attic back to properties map
+        if (!propertiesInAttic.isEmpty()) {
+            properties.putAll(propertiesInAttic);
+            propertiesInAttic.clear();
+        }
+        // if this entry has been moved before -> move back
+        NodeState state = (NodeState) internalGetItemState();
+        if (state != null && StateUtility.isMovedState(state)) {
+            // move NodeEntry back to its original parent
+            parent.childNodeEntries().remove(this);
+            NodeEntryImpl oldEntry = (NodeEntryImpl) state.getWorkspaceState().getHierarchyEntry();
+            oldEntry.parent.childNodeEntries().add(oldEntry);
+
+            factory.notifyEntryMoved(this, oldEntry);
+        }
+        // now make sure the underlying state is reverted to the original state
+        super.revert();
+    }
+
+    /**
+     * @see HierarchyEntry#transientRemove()
+     */
+    public void transientRemove() throws ItemStateException {
+        for (Iterator it = getAllChildEntries(true, false); it.hasNext();) {
+            HierarchyEntry ce = (HierarchyEntry) it.next();
+            ce.transientRemove();
+        }
+
+        if (!propertiesInAttic.isEmpty()) {
+            // move all properties from attic back to properties map
+            properties.putAll(propertiesInAttic);
+            propertiesInAttic.clear();
+        }
+
+        // execute for this entry as well
+        super.transientRemove();
+    }
+
+    /**
+     * @see HierarchyEntry#remove()
+     */
+    public void remove() {
+        ItemState state = internalGetItemState();
+        if (state != null) {
+            if (state.getStatus() == Status.NEW) {
+                state.setStatus(Status.REMOVED);
+            } else {
+                state.getWorkspaceState().setStatus(Status.REMOVED);
+            }
+        }
+        parent.childNodeEntries().remove(this);
+
+        // now traverse all child-entries and mark the attached states removed
+        // without removing the child-entries themselves. this is not required
+        // since this (i.e. the parent is removed as well).
+        for (Iterator it = getAllChildEntries(true, true); it.hasNext();) {
+            HierarchyEntryImpl ce = (HierarchyEntryImpl) it.next();
+            state = ce.internalGetItemState();
+            if (state != null) {
+                if (state.getStatus() == Status.NEW) {
+                    state.setStatus(Status.REMOVED);
+                } else {
+                    state.getWorkspaceState().setStatus(Status.REMOVED);
+                }
+            }
+        }
+    }
+
+    /**
+     * If the underlying state is available and transiently modified, new or
+     * stale, it gets added to the changeLog. Subsequently this call is repeated
+     * recursively to collect all child states that meet the condition,
+     * including those property states that have been moved to the attic.
+     *
+     * @inheritDoc
+     * @see HierarchyEntry#collectStates(ChangeLog, boolean)
+     */
+    public void collectStates(ChangeLog changeLog, boolean throwOnStale) throws StaleItemStateException {
+        super.collectStates(changeLog, throwOnStale);
+
+        // collect transient child states including properties in attic.
+        for (Iterator it = getAllChildEntries(false, true); it.hasNext();) {
+            HierarchyEntry ce = (HierarchyEntry) it.next();
+            ce.collectStates(changeLog, throwOnStale);
+        }
+    }
+
+    //----------------------------------------------------------< NodeEntry >---
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getId()
+     */
+    public NodeId getId() {
+        IdFactory idFactory = factory.getIdFactory();
+        if (uniqueID != null) {
+            return idFactory.createNodeId(uniqueID);
+        } else {
+            if (parent == null) {
+                // root node
+                return idFactory.createNodeId((String) null, Path.ROOT);
+            } else {
+                return factory.getIdFactory().createNodeId(parent.getId(), Path.create(getQName(), getIndex()));
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getUniqueID()
+     */
+    public String getUniqueID() {
+        return uniqueID;
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#setUniqueID(String)
+     */
+    public void setUniqueID(String uniqueID) {
+        String old = this.uniqueID;
+        boolean mod = (uniqueID == null) ? old != null : !uniqueID.equals(old);
+        if (mod) {
+            this.uniqueID = uniqueID;
+            factory.notifyIdChange(this, old);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getIndex()
+     */
+    public int getIndex() {
+        if (parent == null) {
+            // the root state may never have siblings
+            return Path.INDEX_DEFAULT;
+        }
+
+        NodeState state = (NodeState) internalGetItemState();
+        try {
+            if (state == null || state.getDefinition().allowsSameNameSiblings()) {
+                return parent.getChildIndex(this);
+            } else {
+                return Path.INDEX_DEFAULT;
+            }
+        } catch (RepositoryException e) {
+            log.error("Error while building Index. ", e);
+            return Path.INDEX_UNDEFINED;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getNodeState()
+     */
+    public NodeState getNodeState()
+            throws NoSuchItemStateException, ItemStateException {
+        return (NodeState) getItemState();
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getDeepEntry(Path)
+     */
+    public HierarchyEntry getDeepEntry(Path path) throws PathNotFoundException, RepositoryException {
+        NodeEntryImpl entry = this;
+        Path.PathElement[] elems = path.getElements();
+        for (int i = 0; i < elems.length; i++) {
+            Path.PathElement elem = elems[i];
+            // check for root element
+            if (elem.denotesRoot()) {
+                if (getParent() != null) {
+                    throw new RepositoryException("NodeEntry out of 'hierarchy'" + path.toString());
+                } else {
+                    continue;
+                }
+            }
+
+            int index = elem.getNormalizedIndex();
+            QName name = elem.getName();
+
+            // first try to resolve to nodeEntry or property entry
+            NodeEntry cne = (entry.childNodeEntries == null) ? null : entry.childNodeEntries.get(name, index);
+            if (cne != null) {
+                entry = (NodeEntryImpl) cne;
+            } else if (index == Path.INDEX_DEFAULT && entry.properties.containsKey(name)
+                && i == path.getLength() - 1) {
+                // property must not have index && must be final path element
+                PropertyEntry pe = (PropertyEntry) entry.properties.get(name);
+                return pe;
+            } else {
+                /*
+                * Unknown entry (not-existing or not yet loaded):
+                * Skip all intermediate entries and directly try to load the ItemState
+                * (including building the itermediate entries. If that fails
+                * NoSuchItemStateException is thrown.
+                *
+                * Since 'path' might be ambigous (Node or Property):
+                * 1) first try Node
+                * 2) if the NameElement does not have SNS-index => try Property
+                * 3) else throw
+                */
+                Path remainingPath;
+                try {
+                    Path.PathBuilder pb = new Path.PathBuilder();
+                    for (int j = i; j < elems.length; j++) {
+                        pb.addLast(elems[j]);
+                    }
+                    remainingPath = pb.getPath();
+                } catch (MalformedPathException e) {
+                    // should not get here
+                    throw new RepositoryException("Invalid path");
+                }
+
+                NodeId anyId = entry.getId();
+                IdFactory idFactory = entry.factory.getIdFactory();
+                NodeId nodeId = idFactory.createNodeId(anyId, remainingPath);
+                try {
+                    NodeState state = entry.factory.getItemStateFactory().createDeepNodeState(nodeId, entry);
+                    return state.getHierarchyEntry();
+                } catch (NoSuchItemStateException e) {
+                    if (index != Path.INDEX_DEFAULT) {
+                        throw new PathNotFoundException(path.toString(), e);
+                    }
+                    // possibly  propstate
+                    try {
+                        nodeId = (remainingPath.getLength() == 1) ? anyId : idFactory.createNodeId(anyId, remainingPath.getAncestor(1));
+                        PropertyId id = idFactory.createPropertyId(nodeId, remainingPath.getNameElement().getName());
+                        PropertyState state = entry.factory.getItemStateFactory().createDeepPropertyState(id, entry);
+                        return state.getHierarchyEntry();
+                    } catch (NoSuchItemStateException ise) {
+                        throw new PathNotFoundException(path.toString());
+                    } catch (ItemStateException ise) {
+                        throw new RepositoryException("Internal error", ise);
+                    }
+                } catch (ItemStateException e) {
+                    throw new RepositoryException("Internal error", e);
+                }
+            }
+        }
+        return entry;
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#hasNodeEntry(QName)
+     */
+    public synchronized boolean hasNodeEntry(QName nodeName) {
+        List namedEntries = childNodeEntries().get(nodeName);
+        if (namedEntries.isEmpty()) {
+            return false;
+        } else {
+            // copy list since during validation the childNodeEntries may be
+            // modified if upon NodeEntry.getItemState the entry is removed.
+            List l = new ArrayList(namedEntries.size());
+            l.addAll(namedEntries);
+            return EntryValidation.containsValidNodeEntry(l.iterator());
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#hasNodeEntry(QName, int)
+     */
+    public synchronized boolean hasNodeEntry(QName nodeName, int index) {
+        return EntryValidation.isValidNodeEntry(childNodeEntries().get(nodeName, index));
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getNodeEntry(QName, int)
+     */
+    public synchronized NodeEntry getNodeEntry(QName nodeName, int index) {
+        NodeEntry cne = childNodeEntries().get(nodeName, index);
+        if (EntryValidation.isValidNodeEntry(cne)) {
+            return cne;
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getNodeEntry(NodeId)
+     */
+    public synchronized NodeEntry getNodeEntry(NodeId childId) {
+        String uid = childId.getUniqueID();
+        Path path = childId.getPath();
+        NodeEntry cne;
+        if (uid != null && path == null) {
+            // retrieve child-entry by uid
+            cne = childNodeEntries().get(null, uid);
+        } else {
+           // retrieve child-entry by name and index
+            Path.PathElement nameElement = path.getNameElement();
+            cne = childNodeEntries().get(nameElement.getName(), nameElement.getIndex());
+        }
+
+        if (EntryValidation.isValidNodeEntry(cne)) {
+            return cne;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getNodeEntries()
+     */
+    public synchronized Iterator getNodeEntries() {
+        Collection entries = new ArrayList();
+        Object[] arr = childNodeEntries().toArray();
+        for (int i = 0; i < arr.length; i++) {
+            NodeEntry cne = (NodeEntry) arr[i];
+            if (EntryValidation.isValidNodeEntry(cne)) {
+                entries.add(cne);
+            }
+        }
+        return Collections.unmodifiableCollection(entries).iterator();
+    }
+
+    /**
+     * Returns a unmodifiable list of <code>NodeEntry</code>s with the
+     * specified name.
+     *
+     * @param nodeName name of the child node entries that should be returned
+     * @return list of <code>NodeEntry</code> objects
+     */
+    public synchronized List getNodeEntries(QName nodeName) {
+        List namedEntries = childNodeEntries().get(nodeName);
+        if (namedEntries.isEmpty()) {
+            return Collections.EMPTY_LIST;
+        } else {
+            List entries = new ArrayList();
+            // get array of the list, since during validation the childNodeEntries
+            // may be modified if upon NodeEntry.getItemState the entry gets removed.
+            Object[] arr = namedEntries.toArray();
+            for (int i = 0; i < arr.length; i++) {
+                NodeEntry cne = (NodeEntry) arr[i];
+                if (EntryValidation.isValidNodeEntry(cne)) {
+                    entries.add(cne);
+                }
+            }
+            return Collections.unmodifiableList(entries);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#addNodeEntry(QName, String, int)
+     */
+    public NodeEntry addNodeEntry(QName nodeName, String uniqueID, int index) {
+        return internalAddNodeEntry(nodeName, uniqueID, index, childNodeEntries());
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#addNewNodeEntry(QName, String, QName, QNodeDefinition)
+     */
+    public NodeState addNewNodeEntry(QName nodeName, String uniqueID, QName primaryNodeType, QNodeDefinition definition) throws ItemExistsException {
+        NodeEntryImpl entry = internalAddNodeEntry(nodeName, uniqueID, Path.INDEX_UNDEFINED, childNodeEntries());
+        NodeState state = factory.getItemStateFactory().createNewNodeState(entry, primaryNodeType, definition);
+        entry.internalSetItemState(state);
+        return state;
+    }
+
+    /**
+     *
+     * @param nodeName
+     * @param uniqueID
+     * @param index
+     * @param childEntries
+     * @return
+     */
+    private NodeEntryImpl internalAddNodeEntry(QName nodeName, String uniqueID, int index, ChildNodeEntries childEntries) {
+        NodeEntryImpl entry = new NodeEntryImpl(this, nodeName, uniqueID, factory);
+        childEntries.add(entry, index);
+        return entry;
+    }
+
+    /**
+     * @see NodeEntry#moveNodeEntry(NodeState, QName, NodeEntry)
+     */
+    public NodeEntry moveNodeEntry(NodeState childState, QName newName, NodeEntry newParent) throws RepositoryException {
+        NodeEntry oldEntry = childNodeEntries().remove(childState.getNodeEntry());
+        if (oldEntry != null) {
+            NodeEntryImpl movedEntry = (NodeEntryImpl) newParent.addNodeEntry(newName, oldEntry.getUniqueID(), Path.INDEX_UNDEFINED);
+            movedEntry.internalSetItemState(childState);
+
+            factory.notifyEntryMoved(oldEntry, movedEntry);
+            return movedEntry;
+        } else {
+            // should never occur
+            String msg = "Internal error. Attempt to move NodeEntry (" + childState + ") which is not connected to its parent.";
+            log.error(msg);
+            throw new RepositoryException(msg);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#hasPropertyEntry(QName)
+     */
+    public synchronized boolean hasPropertyEntry(QName propName) {
+        PropertyEntry entry = (PropertyEntry) properties.get(propName);
+        return EntryValidation.isValidPropertyEntry(entry);
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getPropertyEntry(QName)
+     */
+    public synchronized PropertyEntry getPropertyEntry(QName propName) {
+        PropertyEntry entry = (PropertyEntry) properties.get(propName);
+        if (EntryValidation.isValidPropertyEntry(entry)) {
+            return entry;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#getPropertyEntries()
+     */
+    public synchronized Iterator getPropertyEntries() {
+        Collection props;
+        ItemState state = internalGetItemState();
+        if (state != null && state.getStatus() == Status.EXISTING_MODIFIED) {
+            // filter out removed properties
+            props = new ArrayList();
+            // use array since upon validation the entry might be removed.
+            Object[] arr = properties.values().toArray();
+            for (int i = 0; i < arr.length; i++) {
+                PropertyEntry propEntry = (PropertyEntry) arr[i];
+                if (EntryValidation.isValidPropertyEntry(propEntry)) {
+                    props.add(propEntry);
+                }
+            }
+        } else {
+            // no need to filter out properties, there are no removed properties
+            props = properties.values();
+        }
+        return Collections.unmodifiableCollection(props).iterator();
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#addPropertyEntry(QName)
+     */
+    public PropertyEntry addPropertyEntry(QName propName) throws ItemExistsException {
+        // TODO: check if correct, that check for existing prop can be omitted.
+        PropertyEntry entry = PropertyEntryImpl.create(this, propName, factory);
+        properties.put(propName, entry);
+
+        // if property-name is jcr:uuid or jcr:mixin this affects this entry
+        // and the attached nodeState.
+        if (StateUtility.isUuidOrMixin(propName)) {
+            notifyUUIDorMIXINModified(entry);
+        }
+        return entry;
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#addPropertyEntries(Collection)
+     */
+    public void addPropertyEntries(Collection propNames) throws ItemExistsException {
+        Set diff = new HashSet();
+        diff.addAll(properties.keySet());
+        boolean containsExtra = diff.removeAll(propNames);
+
+        // add all entries that are missing
+        for (Iterator it = propNames.iterator(); it.hasNext();) {
+            QName propName = (QName) it.next();
+            if (!properties.containsKey(propName)) {
+                addPropertyEntry(propName);
+            }
+        }
+
+        // if this entry has not yet been resolved or if it is 'invalidated'
+        // all property entries, that are not contained within the specified
+        // collection of property names are removed from this NodeEntry.
+        ItemState state = internalGetItemState();
+        if (containsExtra && (state == null || state.getStatus() == Status.INVALIDATED)) {
+            for (Iterator it = diff.iterator(); it.hasNext();) {
+                QName propName = (QName) it.next();
+                PropertyEntry pEntry = (PropertyEntry) properties.get(propName);
+                pEntry.remove();
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#addNewPropertyEntry(QName, QPropertyDefinition)
+     */
+    public PropertyState addNewPropertyEntry(QName propName, QPropertyDefinition definition) throws ItemExistsException {
+        // check for an existing property
+        PropertyEntry existing = (PropertyEntry) properties.get(propName);
+        if (existing != null) {
+            try {
+                PropertyState existingState = existing.getPropertyState();
+                int status = existingState.getStatus();
+
+                if (Status.isTerminal(status)) {
+                    // an old property-entry that is not valid any more
+                    properties.remove(propName);
+                } else if (status == Status.EXISTING_REMOVED) {
+                    // transiently removed -> move it to the attic
+                    propertiesInAttic.put(propName, existing);
+                } else {
+                    // existing is still existing -> cannot add same-named property
+                    throw new ItemExistsException(propName.toString());
+                }
+            } catch (ItemStateException e) {
+                // entry probably does not exist on the persistent layer
+                // -> therefore remove from properties map
+                properties.remove(propName);
+            }
+        }
+
+        // add the property entry
+        PropertyEntry entry = PropertyEntryImpl.create(this, propName, factory);
+        properties.put(propName, entry);
+
+        PropertyState state = factory.getItemStateFactory().createNewPropertyState(entry, definition);
+        ((PropertyEntryImpl) entry).internalSetItemState(state);
+
+        return state;
+    }
+
+    /**
+     * @param propName
+     */
+    PropertyEntry internalRemovePropertyEntry(QName propName) {
+        PropertyEntry cpe = (PropertyEntry) properties.remove(propName);
+        if (cpe == null) {
+            cpe = (PropertyEntry) propertiesInAttic.remove(propName);
+        }
+        // special properties
+        if (StateUtility.isUuidOrMixin(propName)) {
+            notifyUUIDorMIXINRemoved(propName);
+        }
+        return cpe;
+    }
+
+    /**
+     * @inheritDoc
+     * @see NodeEntry#orderBefore(NodeEntry)
+     */
+    public boolean orderBefore(NodeEntry beforeEntry) {
+        return parent.childNodeEntries().reorder(this, beforeEntry);
+    }
+
+    /**
+     * @param childEvent
+     * @see NodeEntry#refresh(Event)
+     */
+    public synchronized void refresh(Event childEvent) {
+        boolean modified = false; // TODO: see todo below
+        QName eventName = childEvent.getQPath().getNameElement().getName();
+        switch (childEvent.getType()) {
+            case Event.NODE_ADDED:
+                int index = childEvent.getQPath().getNameElement().getNormalizedIndex();
+                String uniqueChildID = (childEvent.getItemId().getPath() == null) ? childEvent.getItemId().getUniqueID() : null;
+                // first check if no matching child entry exists.
+                // TODO: TOBEFIXED for SNSs
+                NodeEntry cne = (uniqueChildID != null) ? childNodeEntries().get(eventName, uniqueChildID) : childNodeEntries().get(eventName, index);
+                if (cne == null) {
+                    cne = internalAddNodeEntry(eventName, uniqueChildID, index, childNodeEntries());
+                    modified = true;
+                } else {
+                    // child already exists -> deal with NEW entries, that were
+                    // added by some other session.
+                    // TODO: TOBEFIXED
+
+                }
+                break;
+
+            case Event.PROPERTY_ADDED:
+                // create a new property reference if it has not been
+                // added by some earlier 'add' event
+                if (!hasPropertyEntry(eventName)) {
+                    try {
+                        addPropertyEntry(eventName);
+                        modified = true;
+                    } catch (ItemExistsException e) {
+                        log.warn("Internal error", e);
+                        // TODO
+                    }
+                } else {
+                    // TODO: TOBEFIXED deal with NEW entries
+                }
+                break;
+
+            case Event.NODE_REMOVED:
+            case Event.PROPERTY_REMOVED:
+                HierarchyEntry child = getEntryForExternalEvent(childEvent.getItemId(), childEvent.getQPath());
+                if (child != null) {
+                    child.remove();
+                    modified = true;
+                } // else: child-Entry has not been loaded yet -> ignore
+                break;
+
+            case Event.PROPERTY_CHANGED:
+                child = getEntryForExternalEvent(childEvent.getItemId(), childEvent.getQPath());
+                if (child != null) {
+                    // Reload data from server and try to merge them with the
+                    // current session-state. if the latter is transiently
+                    // modified and merge fails it must be marked STALE afterwards.
+                    child.reload(false, false);
+                    // special cases: jcr:uuid and jcr:mixinTypes affect the parent
+                    // (i.e. this NodeEntry) since both props are protected
+                    if (StateUtility.isUuidOrMixin(eventName)) {
+                        notifyUUIDorMIXINModified((PropertyEntry) child);
+                        modified = true;
+                    }
+                } else {
+                    // prop-Entry has not been loaded yet -> add propEntry
+                    try {
+                        addPropertyEntry(eventName);
+                        modified = true;
+                    } catch (ItemExistsException e) {
+                        log.warn("Internal error", e);
+                        // TODO
+                    }
+                }
+                break;
+            default:
+                // ILLEGAL
+                throw new IllegalArgumentException("Illegal event type " + childEvent.getType() + " for NodeState.");
+        }
+
+        // TODO: check if status of THIS_state must be marked modified...
+    }
+
+    //------------------------------------------------------< HierarchyEntryImpl >---
+    /**
+     * @inheritDoc
+     * @see HierarchyEntryImpl#doResolve()
+     * <p/>
+     * Returns a <code>NodeState</code>.
+     */
+    ItemState doResolve()
+        throws NoSuchItemStateException, ItemStateException {
+        return factory.getItemStateFactory().createNodeState(getId(), this);
+    }
+
+    //-----------------------------------------------< private || protected >---
+    /**
+     * @throws IllegalArgumentException if <code>this</code> is not the parent
+     * of the given <code>ItemState</code>.
+     */
+    synchronized void revertPropertyRemoval(PropertyEntry propertyEntry) {
+        if (propertyEntry.getParent() != this) {
+            throw new IllegalArgumentException("Internal error: Parent mismatch.");
+        }
+        QName propName = propertyEntry.getQName();
+        if (propertiesInAttic.containsKey(propName)) {
+            properties.put(propName, propertiesInAttic.remove(propName));
+        } // else: propEntry has never been moved to the attic (see 'addPropertyEntry')
+    }
+
+    /**
+     * Searches the child-entries of this NodeEntry for a matching child.
+     * Since {@link #refresh(Event)} must always be called on the parent
+     * NodeEntry, there is no need to check if a given event id would point
+     * to this NodeEntry itself.
+     *
+     * @param eventId
+     * @param eventPath
+     * @return
+     */
+    private HierarchyEntry getEntryForExternalEvent(ItemId eventId, Path eventPath) {
+        QName childName = eventPath.getNameElement().getName();
+        HierarchyEntry child = null;
+        if (eventId.denotesNode()) {
+            String uniqueChildID = (eventId.getPath() == null) ? eventId.getUniqueID() : null;
+            if (uniqueChildID != null) {
+                child = childNodeEntries().get(childName, uniqueID);
+            }
+            if (child == null) {
+                child = childNodeEntries().get(childName, eventPath.getNameElement().getNormalizedIndex());
+            }
+        } else {
+            // for external prop-removal the attic must be consulted first
+            // in order not access a NEW prop shadowing a transiently removed
+            // property with the same name.
+            child = (HierarchyEntry) propertiesInAttic.get(childName);
+            if (child == null) {
+                child = (HierarchyEntry) properties.get(childName);
+            }
+        }
+        if (child != null) {
+            // a NEW hierarchyEntry may never be affected by an external
+            // modification -> return null.
+            ItemState state = ((HierarchyEntryImpl) child).internalGetItemState();
+            if (state != null && state.getStatus() == Status.NEW) {
+                return null;
+            }
+        }
+        return child;
+    }
+
+    /**
+     * Deals with modified jcr:uuid and jcr:mixinTypes property.
+     * See {@link #notifyUUIDorMIXINRemoved(QName)}
+     *
+     * @param child
+     */
+    private void notifyUUIDorMIXINModified(PropertyEntry child) {
+        try {
+            if (QName.JCR_UUID.equals(child.getQName())) {
+                PropertyState ps = child.getPropertyState();
+                setUniqueID(ps.getValue().getString());
+            } else if (QName.JCR_MIXINTYPES.equals(child.getQName())) {
+                NodeState state = (NodeState) internalGetItemState();
+                if (state != null) {
+                    PropertyState ps = child.getPropertyState();
+                    state.setMixinTypeNames(StateUtility.getMixinNames(ps));
+                } // nodestate not yet loaded -> ignore change
+            }
+        } catch (ItemStateException e) {
+            log.error("Internal Error", e);
+        } catch (RepositoryException e) {
+            log.error("Internal Error", e);
+        }
+    }
+
+    /**
+     * Deals with removed jcr:uuid and jcr:mixinTypes property.
+     * See {@link #notifyUUIDorMIXINModified(PropertyEntry)}
+     *
+     * @param propName
+     */
+    private void notifyUUIDorMIXINRemoved(QName propName) {
+        if (QName.JCR_UUID.equals(propName)) {
+            setUniqueID(null);
+        } else if (QName.JCR_MIXINTYPES.equals(propName)) {
+            NodeState state = (NodeState) internalGetItemState();
+            if (state != null) {
+                state.setMixinTypeNames(QName.EMPTY_ARRAY);
+            }
+        }
+    }
+
+    /**
+     *
+     * @return
+     */
+    private ChildNodeEntries childNodeEntries() {
+        if (childNodeEntries == null) {
+            ItemState state = internalGetItemState();
+            if (state != null) {
+                if (state.getStatus() == Status.NEW) {
+                    childNodeEntries = new ChildNodeEntries(this);
+                } else if (StateUtility.isMovedState((NodeState) state)) {
+                    // TODO: TOBEFIXED need to retrieve the original id. currently this will fail in case of SNS
+                    // since, the index cannot be determined from the original parent any more
+                    NodeId originalID = ((NodeState) state.getWorkspaceState()).getNodeId();
+                    childNodeEntries = loadChildNodeEntries(originalID);
+                } else {
+                    childNodeEntries = loadChildNodeEntries(getId());
+                }
+            } else {
+                childNodeEntries = loadChildNodeEntries(getId());
+            }
+        }
+        return childNodeEntries;
+    }
+
+    private ChildNodeEntries loadChildNodeEntries(NodeId id) {
+        ChildNodeEntries cnes = new ChildNodeEntries(this);
+        try {
+            Iterator it = factory.getItemStateFactory().getChildNodeInfos(id);
+            while (it.hasNext()) {
+                ChildInfo ci = (ChildInfo) it.next();
+                internalAddNodeEntry(ci.getName(), ci.getUniqueID(), ci.getIndex(), cnes);
+            }
+        } catch (NoSuchItemStateException e) {
+            log.error("Cannot retrieve child node entries.", e);
+            // ignore (TODO correct?)
+        } catch (ItemStateException e) {
+            log.error("Cannot retrieve child node entries.", e);
+            // ignore (TODO correct?)
+        }
+        return cnes;
+    }
+
+    /**
+     * Returns an Iterator over all children entries, that currently are loaded
+     * with this NodeEntry. NOTE, that if the childNodeEntries have not been
+     * loaded yet, no attempt is made to do so.
+     *
+     * @param createNewList if true, both properties and childNodeEntries are
+     * copied to new list, since recursive calls may call this node state to
+     * inform the removal of a child entry.
+     * @param includeAttic
+     * @return
+     */
+    private Iterator getAllChildEntries(boolean createNewList, boolean includeAttic) {
+        Iterator[] its;
+        if (createNewList) {
+            List props = new ArrayList(properties.values());
+            List children = (childNodeEntries == null) ? Collections.EMPTY_LIST : new ArrayList(childNodeEntries);
+            if (includeAttic) {
+                List attic = new ArrayList(propertiesInAttic.values());
+                its = new Iterator[] {attic.iterator(), props.iterator(), children.iterator()};
+            } else {
+                its = new Iterator[] {props.iterator(), children.iterator()};
+            }
+        } else {
+            Iterator children = (childNodeEntries == null) ? Collections.EMPTY_LIST.iterator() : childNodeEntries.iterator();
+            if (includeAttic) {
+                its = new Iterator[] {propertiesInAttic.values().iterator(), properties.values().iterator(), children};
+            } else {
+                its = new Iterator[] {properties.values().iterator(), children};
+            }
+        }
+        IteratorChain chain = new IteratorChain(its);
+        return chain;
+    }
+
+    /**
+     * Returns the index of the given <code>NodeEntry</code>.
+     *
+     * @param cne  the <code>NodeEntry</code> instance.
+     * @return the index of the child node entry or <code>Path.INDEX_UNDEFINED</code>
+     * if the given entry isn't a valid child of this <code>NodeEntry</code>.
+     */
+    private int getChildIndex(NodeEntry cne) {
+        List sns = childNodeEntries().get(cne.getQName());
+        // index is one based
+        int index = Path.INDEX_DEFAULT;
+        for (Iterator it = sns.iterator(); it.hasNext(); ) {
+            NodeEntry entry = (NodeEntry) it.next();
+            if (entry == cne) {
+                return index;
+            }
+            // skip entries that belong to removed or invalid states.
+            // NOTE, that in this case the nodestate must be available from the cne.
+            if (EntryValidation.isValidNodeEntry(entry)) {
+                index++;
+            }
+        }
+        // not found (should not occur)
+        return Path.INDEX_UNDEFINED;
+    }
+}

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PathResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PathResolver.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PathResolver.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PathResolver.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,231 @@
+/*
+ * $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.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
+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.spi.IdFactory;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.PropertyId;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+
+/**
+ * <code>PathResolver</code> resolves a relative Path starting at a given
+ * ItemState and returns the Item where the path points to.
+ */
+public class PathResolver {
+
+    /**
+     * The starting point to resolve the path.
+     */
+    private final NodeEntry start;
+
+    /**
+     * The path to resolve.
+     */
+    private final Path path;
+
+    /**
+     * Internal constructor.
+     *
+     * @param start   the starting point.
+     * @param relPath the path to resolve.
+     * @throws IllegalArgumentException if not normalized or starts with a
+     *                                  parent ('..') path element.
+     */
+    private PathResolver(NodeEntry start, Path relPath) {
+        if (!relPath.isNormalized() || relPath.getElement(0).denotesParent()) {
+            throw new IllegalArgumentException("path must be relative and must " +
+                    "not contain parent path elements");
+        }
+        this.start = start;
+        this.path = relPath;
+    }
+
+    /**
+     * Resolves the path starting at <code>start</code>.
+     *
+     * @param start   the starting point.
+     * @param path the path to resolve.
+     * @return the resolved item state.
+     * @throws IllegalArgumentException if path is absolute or not normalized
+     * or starts with a parent ('..') path element.
+     * @throws PathNotFoundException the the path cannot be resolved.
+     * @throws RepositoryException if an error occurs while retrieving the item state.
+     */
+    public static HierarchyEntry resolve(NodeEntry start, Path path, ItemStateFactory isf, IdFactory idFactory) throws PathNotFoundException, RepositoryException {
+        return new PathResolver(start, path).resolve(isf, idFactory);
+    }
+
+    /**
+     * Looks up the <code>ItemState</code> at <code>path</code> starting at
+     * <code>start</code>.
+     *
+     * @param start   the starting point.
+     * @param path the path to resolve.
+     * @return the resolved HierarchyEntry or <code>null</code> if the item is not
+     * available.
+     * @throws IllegalArgumentException if path is absolute or not normalized
+     * or starts with a parent ('..') path element.
+     */
+    public static HierarchyEntry lookup(NodeEntry start, Path path) {
+        return new PathResolver(start, path).lookup();
+    }
+
+    /**
+     * Resolves the path.
+     *
+     * @return the hierarchy entry identified by the path.
+     * @throws PathNotFoundException the the path cannot be resolved.
+     * @throws RepositoryException if an error occurs while retrieving the item state.
+     */
+    private HierarchyEntry resolve(ItemStateFactory isf, IdFactory idFactory) throws PathNotFoundException, RepositoryException {
+        // TODO: check again.
+        NodeEntry entry = start;
+        Path.PathElement[] elems = path.getElements();
+        for (int i = 0; i < elems.length; i++) {
+            Path.PathElement elem = elems[i];
+            // check for root element
+            if (elem.denotesRoot()) {
+                if (start.getParent() != null) {
+                    throw new PathNotFoundException("NodeEntry out of 'hierarchy'" + path.toString());
+                } else {
+                    continue;
+                }
+            }
+
+            int index = elem.getNormalizedIndex();
+            QName name = elem.getName();
+
+            // first try to resolve to nodeEntry
+            if (entry.hasNodeEntry(name, index)) {
+                NodeEntry cne = entry.getNodeEntry(name, index);
+                entry = cne;
+            } else if (index == Path.INDEX_DEFAULT // property must not have index
+                    && entry.hasPropertyEntry(name)
+                    && i == path.getLength() - 1) { // property must be final path element
+                PropertyEntry pe = entry.getPropertyEntry(name);
+                return pe;
+            } else {
+                // try to resolve the HierarchyEntry corresponding to the given path
+                Path remaingPath;
+                try {
+                    Path.PathBuilder pb = new Path.PathBuilder();
+                    for (int j = i; j < elems.length; j++) {
+                        pb.addLast(elems[j]);
+                    }
+                    remaingPath = pb.getPath();
+                } catch (MalformedPathException e) {
+                    throw new RepositoryException(e);
+                }
+
+                try {
+                    ItemState state = retrieveItemState(remaingPath, index, entry, isf, idFactory);
+                    return state.getHierarchyEntry();
+                } catch (NoSuchItemStateException e) {
+                    throw new PathNotFoundException(path.toString(), e);
+                } catch (ItemStateException e) {
+                    throw new RepositoryException(e);
+                }
+            }
+        }
+        return entry;
+    }
+
+    /**
+     * Unknown entry (not-existing or not yet loaded):
+     * Skip all intermediate entries and directly try to load the ItemState
+     * (including building the itermediate entries.<br>
+     * If that fails NoSuchItemStateException is thrown.<br>
+     *
+     * Since 'path' might be ambigous (Node or Property), apply some logic:<br>
+     * 1) first try Node<br>
+     * 2) if the NameElement does not have SNS-index => try Property<br>
+     * 3) else throw directly
+     *
+     * @param remainingPath
+     * @param index
+     * @param anyParent
+     * @param isf
+     * @param idFactory
+     * @return
+     * @throws NoSuchItemStateException
+     * @throws ItemStateException
+     */
+    private ItemState retrieveItemState(Path remainingPath, int index,
+                                        NodeEntry anyParent, ItemStateFactory isf,
+                                        IdFactory idFactory)
+        throws NoSuchItemStateException, ItemStateException, PathNotFoundException {
+        NodeId anyId = anyParent.getId();
+        NodeId nodeId = idFactory.createNodeId(anyId, remainingPath);
+        try {
+            return isf.createDeepNodeState(nodeId, anyParent);
+        } catch (NoSuchItemStateException e) {
+            if (index == Path.INDEX_DEFAULT) {
+                // possibly  propstate
+                nodeId = (remainingPath.getLength() == 1) ? anyId : idFactory.createNodeId(anyId, remainingPath.getAncestor(1));
+                PropertyId id = idFactory.createPropertyId(nodeId, remainingPath.getNameElement().getName());
+                return isf.createDeepPropertyState(id, anyParent);
+            } else {
+                // rethrow
+                throw new NoSuchItemStateException(e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * Resolves the path but returns <code>null</code> if the entry has not yet
+     * been loaded.
+     *
+     * @return the HierarchyEntry or <code>null</code> if the entry does not exist.
+     */
+    private HierarchyEntry lookup() {
+        NodeEntry entry = start;
+        for (int i = 0; i < path.getLength(); i++) {
+            Path.PathElement elem = path.getElement(i);
+            // check for root element
+            if (elem.denotesRoot()) {
+                if (start.getParent() != null) {
+                    return null;
+                } else {
+                    continue;
+                }
+            }
+
+            int index = elem.getNormalizedIndex();
+            QName name = elem.getName();
+
+            // first try to resolve node
+            if (entry.hasNodeEntry(name, index)) {
+                NodeEntry cne = entry.getNodeEntry(name, index);
+                entry = cne;
+            } else if (index == Path.INDEX_DEFAULT // property must not have index
+                    && entry.hasPropertyEntry(name)
+                    && i == path.getLength() - 1) { // property must be final path element
+                PropertyEntry pe = entry.getPropertyEntry(name);
+                return pe;
+            } else {
+                return null;
+            }
+        }
+        return entry;
+    }
+}

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntry.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntry.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntry.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntry.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,43 @@
+/*
+ * 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.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.spi.PropertyId;
+
+/**
+ * <code>PropertyEntry</code>...
+ */
+public interface PropertyEntry extends HierarchyEntry {
+
+    /**
+     * @return the <code>NodeId</code> of this child node entry.
+     */
+    public PropertyId getId();
+
+    /**
+     * @return the referenced <code>PropertyState</code>.
+     * @throws NoSuchItemStateException if the <code>PropertyState</code> does not
+     * exist anymore.
+     * @throws ItemStateException if an error occurs while retrieving the
+     * <code>PropertyState</code>.
+     */
+    public PropertyState getPropertyState() throws NoSuchItemStateException, ItemStateException;
+
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,113 @@
+/*
+ * 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.spi.PropertyId;
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>PropertyEntryImpl</code> implements a reference to a property state.
+ */
+public class PropertyEntryImpl extends HierarchyEntryImpl implements PropertyEntry {
+
+    private static Logger log = LoggerFactory.getLogger(PropertyEntryImpl.class);
+
+    /**
+     * Creates a new <code>PropertyEntryImpl</code>.
+     *
+     * @param parent    the parent <code>NodeEntry</code> where the property
+     *                  belongs to.
+     * @param name      the name of the property.
+     * @param factory
+     */
+    private PropertyEntryImpl(NodeEntryImpl parent, QName name, EntryFactory factory) {
+        super(parent, name, factory);
+    }
+
+    /**
+     * Creates a new <code>PropertyEntry</code>.
+     *
+     * @param parent
+     * @param name
+     * @param factory
+     * @return new <code>PropertyEntry</code>
+     */
+    static PropertyEntry create(NodeEntryImpl parent, QName name, EntryFactory factory) {
+        return new PropertyEntryImpl(parent, name, factory);
+    }
+
+    //------------------------------------------------------< HierarchyEntryImpl >---
+    /**
+     * @inheritDoc
+     * @see HierarchyEntryImpl#doResolve()
+     * <p/>
+     * Returns a <code>PropertyState</code>.
+     */
+    ItemState doResolve()
+            throws NoSuchItemStateException, ItemStateException {
+        return factory.getItemStateFactory().createPropertyState(getId(), this);
+    }
+
+    //------------------------------------------------------< PropertyEntry >---
+    /**
+     * @inheritDoc
+     */
+    public PropertyId getId() {
+        return factory.getIdFactory().createPropertyId(parent.getId(), getQName());
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public PropertyState getPropertyState() throws NoSuchItemStateException, ItemStateException {
+        return (PropertyState) getItemState();
+    }
+
+    //-----------------------------------------------------< HierarchyEntry >---
+    /**
+     * Returns false.
+     *
+     * @inheritDoc
+     * @see HierarchyEntry#denotesNode()
+     */
+    public boolean denotesNode() {
+        return false;
+    }
+
+    /**
+     * @inheritDoc
+     * @see HierarchyEntry#remove()
+     */
+    public void remove() {
+        ItemState state = internalGetItemState();
+        if (state != null) {
+            if (state.getStatus() == Status.NEW) {
+                state.setStatus(Status.REMOVED);
+            } else {
+                state.getWorkspaceState().setStatus(Status.REMOVED);
+            }
+        }
+        parent.internalRemovePropertyEntry(getQName());
+    }
+}

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

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

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/UniqueIdResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/UniqueIdResolver.java?view=auto&rev=506927
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/UniqueIdResolver.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/UniqueIdResolver.java Tue Feb 13 01:31:36 2007
@@ -0,0 +1,175 @@
+/*
+ * $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.apache.jackrabbit.jcr2spi.state.ItemStateCreationListener;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateLifeCycleListener;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import java.util.Map;
+import java.util.Iterator;
+
+/**
+ * <code>UniqueIdResolver</code> allows to retrieve <code>NodeEntry</code> instances
+ * that are identified by a uniqueID.
+ */
+public class UniqueIdResolver implements ItemStateCreationListener, EntryFactory.NodeEntryListener {
+
+    private static Logger log = LoggerFactory.getLogger(UniqueIdResolver.class);
+
+    private final ItemStateFactory isf;
+
+    /**
+     * Maps a String uniqueID to a {@link NodeEntry}.
+     */
+    private final Map lookUp;
+
+    /**
+     * Creates a new <code>UniqueIdResolver</code>.
+     */
+    public UniqueIdResolver(ItemStateFactory isf) {
+        this.lookUp = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
+        this.isf = isf;
+        isf.addCreationListener(this);
+    }
+
+    public void dispose() {
+        isf.removeCreationListener(this);
+        lookUp.clear();
+    }
+
+    public NodeEntry lookup(NodeId nodeId) {
+        if (nodeId.getPath() != null) {
+            throw new IllegalArgumentException();
+        }
+        NodeEntry entry = (NodeEntry) lookUp.get(nodeId.getUniqueID());
+        return (entry != null) ? entry : null;
+    }
+
+    public NodeEntry resolve(NodeId nodeId, NodeEntry rootEntry) throws PathNotFoundException, RepositoryException {
+        NodeEntry entry = lookup(nodeId);
+        if (entry == null) {
+            try {
+                NodeState state = isf.createDeepNodeState(nodeId, rootEntry);
+                entry = state.getNodeEntry();
+            } catch (NoSuchItemStateException e) {
+                throw new PathNotFoundException(e);
+            } catch (ItemStateException e) {
+                throw new RepositoryException(e);
+            }
+        }
+        return entry;
+    }
+
+    //-----------------------------------------< ItemStateLifeCycleListener >---
+    /**
+     * Updates the internal id-lookup if the given state
+     * - is identify by a uniqueID and got removed
+     * - was modified and now is identified by a uniqueID
+     * - was modified and is not identified by a uniqueID any more
+     *
+     * @param state
+     * @param previousStatus
+     * @see ItemStateLifeCycleListener#statusChanged(ItemState, int)
+     */
+    public void statusChanged(ItemState state, int previousStatus) {
+        if (Status.isTerminal(state.getStatus())) {
+            if (state.isNode()) {
+                NodeState nodeState = (NodeState) state;
+                String uniqueID = nodeState.getUniqueID();
+                if (uniqueID != null) {
+                    lookUp.remove(uniqueID);
+                }
+            }
+            state.removeListener(this);
+        } else {
+            putToCache(state);
+        }
+    }
+
+    //------------------------------------------< ItemStateCreationListener >---
+    /**
+     * Updates the internal id-lookup if the created state is a NodeState that
+     * is identified by a uniqueID.
+     *
+     * @param state
+     * @see ItemStateCreationListener#created(ItemState)
+     */
+    public void created(ItemState state) {
+        putToCache(state);
+    }
+
+    //-------------------------------------< EntryFactory.NodeEntryListener >---
+    /**
+     * @param entry
+     * @see EntryFactory.NodeEntryListener#uniqueIdChanged(NodeEntry, String)
+     */
+    public void uniqueIdChanged(NodeEntry entry, String previousUniqueID) {
+        synchronized (lookUp) {
+            if (previousUniqueID != null) {
+                lookUp.remove(previousUniqueID);
+            }
+            String uniqueID = entry.getUniqueID();
+            if (uniqueID != null) {
+                lookUp.put(uniqueID, entry);
+            }
+        }
+    }
+    //------------------------------------------------------------< private >---
+    /**
+     * Put the given <code>ItemState</code> in the internal cache.
+     *
+     * @param state
+     */
+    private void putToCache(ItemState state) {
+        if (!state.isNode()) {
+            return;
+        }
+
+        if (state.getStatus() == Status.EXISTING || state.getStatus() == Status.MODIFIED) {
+            NodeEntry entry = ((NodeState)state).getNodeEntry();
+            // NOTE: uniqueID is retrieved from the state and not from the NodeId.
+            String uniqueID = entry.getUniqueID();
+            synchronized (lookUp) {
+                if (uniqueID != null) {
+                    if (lookUp.get(uniqueID) != entry) {
+                        lookUp.put(uniqueID, entry);
+                    }
+                } else {
+                    // ev. uniqueID was removed -> remove the entry from the lookUp
+                    if (lookUp.containsValue(entry)) {
+                        for (Iterator it = lookUp.entrySet().iterator(); it.hasNext();) {
+                            Map.Entry next = (Map.Entry) it.next();
+                            if (next.getValue() == entry) {
+                                it.remove();
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}



Mime
View raw message