Index: src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (revision 805688) +++ src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (working copy) @@ -562,7 +562,13 @@ if (n.getParentId().equals(parentId)) { continue; } - NodeState parent = (NodeState) changes.get(parentId); + NodeState parent = null; + try { + parent = (NodeState) changes.get(parentId); + } catch (NoSuchItemStateException e) { + // parent has been removed as well + // ignore and retrieve from stateMgr + } if (parent == null) { // happens when mix:shareable is removed from an existing // node. Usually the parent node state is in the change log Index: src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (revision 805688) +++ src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (working copy) @@ -413,13 +413,14 @@ // the depth is used as array index List[] la = new List[10]; try { + HierarchyManager atticAware = getAtticAwareHierarchyMgr(); Iterator iter = transientStore.values().iterator(); while (iter.hasNext()) { ItemState state = (ItemState) iter.next(); // determine relative depth: > 0 means it's a descendant int depth; try { - depth = hierMgr.getShareRelativeDepth(parentId, state.getId()); + depth = atticAware.getShareRelativeDepth(parentId, state.getId()); } catch (ItemNotFoundException infe) { /** * one of the parents of the specified item has been Index: src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java (revision 805688) +++ src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java (working copy) @@ -18,6 +18,9 @@ import javax.jcr.Node; import javax.jcr.Workspace; +import javax.jcr.NodeIterator; +import javax.jcr.query.QueryManager; +import javax.jcr.query.Query; import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.ObservationManager; @@ -119,4 +122,32 @@ superuser.getWorkspace().getObservationManager().removeEventListener(el); assertEquals(1, el.getEventCount()); } + + /** + * Verify that a shared node is removed when the ancestor is removed. + * See also JCR-2257. + */ + public void testRemoveAncestorOfSharedNode() throws Exception { + Node a1 = testRootNode.addNode("a1"); + Node a2 = a1.addNode("a2"); + Node b1 = a1.addNode("b1"); + ensureMixinType(b1, mixShareable); + testRootNode.save(); + //now we have a shareable node N with path a1/b1 + + Workspace workspace = testRootNode.getSession().getWorkspace(); + String path = a2.getPath() + "/b2"; + workspace.clone(workspace.getName(), b1.getPath(), path, false); + testRootNode.save(); + //now we have another shareable node N' in the same shared set as N with path a1/a2/b2 + + a2.remove(); + testRootNode.save(); + + // assert b2 is removed from index + QueryManager qm = superuser.getWorkspace().getQueryManager(); + String stmt = testPath + "//b2"; + NodeIterator it = qm.createQuery(stmt, Query.XPATH).execute().getNodes(); + assertFalse(it.hasNext()); + } }