Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 84175 invoked from network); 13 Feb 2007 09:32:39 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 13 Feb 2007 09:32:39 -0000 Received: (qmail 6136 invoked by uid 500); 13 Feb 2007 09:32:46 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 6106 invoked by uid 500); 13 Feb 2007 09:32:46 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 5987 invoked by uid 99); 13 Feb 2007 09:32:46 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 13 Feb 2007 01:32:46 -0800 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 13 Feb 2007 01:32:32 -0800 Received: by eris.apache.org (Postfix, from userid 65534) id 148C51A9824; Tue, 13 Feb 2007 01:32:10 -0800 (PST) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit 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 -0000 To: commits@jackrabbit.apache.org From: angela@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20070213093211.148C51A9824@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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; + +/** + * HierarchyManager... + */ +public interface HierarchyManager { + + /** + * Dispose this HierarchyManager + */ + public void dispose(); + + /** + * + * @return + */ + public NodeEntry getRootEntry(); + + /** + * If the Hierarchy already lists the entry with the given itemId it is + * returned otherwise null. 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 HierarchyEntry. + * + * @param itemId + * @return + * @throws PathNotFoundException + * @throws RepositoryException + */ + public HierarchyEntry getHierarchyEntry(ItemId itemId) throws PathNotFoundException, RepositoryException; + + /** + * Resolves a path into a HierarchyEntry. + * + * @param qPath + * @return + * @throws PathNotFoundException + * @throws RepositoryException + */ + public HierarchyEntry getHierarchyEntry(Path qPath) throws PathNotFoundException, RepositoryException; + + /** + * Retrieves the HierarchyEntry corresponding to the given + * path and resolves it to the underlying ItemState. + * + * @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 ancestor and descendant + * denote the same item 0 is returned. If ancestor 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 ancestor does not + * denote an ancestor of the item denoted by descendant + * (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; + +/** + * HierarchyManagerImpl implements the HierarchyManager + * 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; + +/** + * NodeEntry... + */ +public interface NodeEntry extends HierarchyEntry { + + /** + * @return the NodeId 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 null 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 NodeState. + * @throws NoSuchItemStateException if the NodeState does not + * exist anymore. + * @throws ItemStateException If an error occurs while retrieving the + * NodeState. + */ + 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 NodeEntry with the + * specified nodeName. + * + * @param nodeName QName object specifying a node name + * @return true if there is a NodeEntry with + * the specified nodeName. + */ + public boolean hasNodeEntry(QName nodeName); + + /** + * Determines if there is a valid NodeEntry with the + * specified name and index. + * + * @param nodeName QName object specifying a node name. + * @param index 1-based index if there are same-name child node entries. + * @return true if there is a NodeEntry with + * the specified name and index. + */ + public boolean hasNodeEntry(QName nodeName, int index); + + /** + * Returns the valid NodeEntry with the specified name + * and index or null if there's no matching entry. + * + * @param nodeName QName object specifying a node name. + * @param index 1-based index if there are same-name child node entries. + * @return The NodeEntry with the specified name and index + * or null if there's no matching entry. + */ + public NodeEntry getNodeEntry(QName nodeName, int index); + + /** + * Returns the NodeEntry with the specified + * NodeId or null if there's no matching + * entry. + * + * @param childId the id of the child entry. + * @return the NodeEntry with the specified + * NodeId or null if there's no matching entry. + */ + public NodeEntry getNodeEntry(NodeId childId); + + /** + * Returns a unmodifiable iterator of NodeEntry objects + * denoting the the valid child NodeEntries present on this NodeEntry. + * + * @return iterator of NodeEntry objects + */ + public Iterator getNodeEntries(); + + /** + * Returns a unmodifiable List of NodeEntrys with the + * specified name. + * + * @param nodeName name of the child node entries that should be returned + * @return list of NodeEntry objects + */ + public List getNodeEntries(QName nodeName); + + /** + * Adds a new child NodeEntry to this entry. + * + * @param nodeName + * @param uniqueID + * @return the new NodeEntry + */ + public NodeEntry addNodeEntry(QName nodeName, String uniqueID, int index); + + /** + * Adds a new, transient child NodeEntry + * + * @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 QName. + * + * @param propName QName object specifying a property name + * @return true if there is a property entry with the specified + * QName. + */ + public boolean hasPropertyEntry(QName propName); + + /** + * Returns the valid PropertyEntry with the specified name + * or null if no matching entry exists. + * + * @param propName QName object specifying a property name. + * @return The PropertyEntry with the specified name or + * null 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 QNames. It depends on + * the status of this NodeEntry, 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 + * beforeEntry. + * + * @param beforeEntry the child node where to insert the node before. If + * null 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 NodeEntry. + * + * @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; + +/** + * NodeEntryImpl 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 null 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 NodeEntryImpl + * + * @param parent the NodeEntry 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 NodeEntrys with the + * specified name. + * + * @param nodeName name of the child node entries that should be returned + * @return list of NodeEntry 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() + *

+ * Returns a NodeState. + */ + ItemState doResolve() + throws NoSuchItemStateException, ItemStateException { + return factory.getItemStateFactory().createNodeState(getId(), this); + } + + //-----------------------------------------------< private || protected >--- + /** + * @throws IllegalArgumentException if this is not the parent + * of the given ItemState. + */ + 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 NodeEntry. + * + * @param cne the NodeEntry instance. + * @return the index of the child node entry or Path.INDEX_UNDEFINED + * if the given entry isn't a valid child of this NodeEntry. + */ + 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; + +/** + * PathResolver 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 start. + * + * @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 ItemState at path starting at + * start. + * + * @param start the starting point. + * @param path the path to resolve. + * @return the resolved HierarchyEntry or null 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.
+ * If that fails NoSuchItemStateException is thrown.
+ * + * Since 'path' might be ambigous (Node or Property), apply some logic:
+ * 1) first try Node
+ * 2) if the NameElement does not have SNS-index => try Property
+ * 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 null if the entry has not yet + * been loaded. + * + * @return the HierarchyEntry or null 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; + +/** + * PropertyEntry... + */ +public interface PropertyEntry extends HierarchyEntry { + + /** + * @return the NodeId of this child node entry. + */ + public PropertyId getId(); + + /** + * @return the referenced PropertyState. + * @throws NoSuchItemStateException if the PropertyState does not + * exist anymore. + * @throws ItemStateException if an error occurs while retrieving the + * PropertyState. + */ + 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; + +/** + * PropertyEntryImpl 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 PropertyEntryImpl. + * + * @param parent the parent NodeEntry 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 PropertyEntry. + * + * @param parent + * @param name + * @param factory + * @return new PropertyEntry + */ + static PropertyEntry create(NodeEntryImpl parent, QName name, EntryFactory factory) { + return new PropertyEntryImpl(parent, name, factory); + } + + //------------------------------------------------------< HierarchyEntryImpl >--- + /** + * @inheritDoc + * @see HierarchyEntryImpl#doResolve() + *

+ * Returns a PropertyState. + */ + 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; + +/** + * UniqueIdResolver allows to retrieve NodeEntry 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 UniqueIdResolver. + */ + 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 ItemState 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; + } + } + } + } + } + } + } +}