Author: mreutegg Date: Thu Mar 3 06:05:35 2005 New Revision: 156039 URL: http://svn.apache.org/viewcvs?view=rev&rev=156039 Log: - Extended observation test cases - fixed observation bug that caused ItemStateException in case a property is removed and added again with the same name Added: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java (with props) Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/EventStateCollection.java incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerFactory.java incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java (original) +++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java Thu Mar 3 06:05:35 2005 @@ -954,7 +954,7 @@ if (obsMgr == null) { try { - obsMgr = rep.getObservationManagerFactory(wspConfig.getName()).createObservationManager(session, session.hierMgr, session.getItemManager()); + obsMgr = rep.getObservationManagerFactory(wspConfig.getName()).createObservationManager(session, session.getItemManager()); } catch (NoSuchWorkspaceException nswe) { // should never get here String msg = "internal error: failed to instantiate observation manager"; Added: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java?view=auto&rev=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java (added) +++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java Thu Mar 3 06:05:35 2005 @@ -0,0 +1,207 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * Licensed 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.core.observation; + +import org.apache.jackrabbit.core.HierarchyManager; +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.HierarchyManagerImpl; +import org.apache.jackrabbit.core.NamespaceResolver; +import org.apache.jackrabbit.core.state.ChangeLog; +import org.apache.jackrabbit.core.state.ItemStateManager; +import org.apache.jackrabbit.core.state.ItemState; +import org.apache.jackrabbit.core.state.NoSuchItemStateException; +import org.apache.jackrabbit.core.state.ItemStateException; +import org.apache.jackrabbit.core.state.NodeReferences; +import org.apache.jackrabbit.core.state.NodeReferencesId; + +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; + +/** + * Implements a {@link HierarchyManager} that uses a {@link ChangeLog} for + * the 'transient' changes on an underlying {@link ItemStateManager}. + * {@link ItemState}s in attic are provided from the removed {@link ItemState}s + * in the {@link ChangeLog}. The modified and added {@link ItemState}s in + * the {@link ChangeLog} overlay the {@link ItemState}s in the + * {@link ItemStateManager}. + */ +class ChangeLogBasedHierarchyMgr extends HierarchyManagerImpl { + + /** + * Creates a new ChangeLogBasedHierarchyMgr that overlays + * manager with changes and uses the deleted + * map of the changes as an attic ItemStateManager. + * @param rootNodeUUID the uuid of the root node. + * @param manager the item state manager. + * @param changes the changes that will be applied on the item state manager. + * @param resolver the namespace resolver of the current session. + */ + ChangeLogBasedHierarchyMgr(String rootNodeUUID, + ItemStateManager manager, + ChangeLog changes, + NamespaceResolver resolver) { + super(rootNodeUUID, + new ChangeLogItemStateManager(manager, changes), + resolver, + new AtticItemStateManager(changes)); + } + + /** + * Implements an ItemStateManager that is overlayed by a ChangeLog. + */ + private static class ChangeLogItemStateManager implements ItemStateManager { + + /** + * The changes that will be applied to the {@link #base}. + */ + private final ChangeLog changes; + + /** + * The underlying {@link ItemStateManager}. + */ + private final ItemStateManager base; + + /** + * Creates a new ChangeLogItemStateManager that overlays + * the {@link ItemState}s in base with the one found in + * changes. + * @param base the underlying {@link ItemStateManager}. + * @param changes + */ + private ChangeLogItemStateManager(ItemStateManager base, ChangeLog changes) { + this.base = base; + this.changes = changes; + } + + /** + * Returns the {@link ItemState} with the id. This + * ItemState manager first looks up the ChangeLog and then + * tries to find the ItemState in the base {@link ItemStateManager}. + * @param id the id of the {@link ItemState}. + * @return the {@link ItemState} with id. + * @throws NoSuchItemStateException if there is no ItemState with + * id. + * @throws ItemStateException if any other error occurs. + */ + public ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { + // check ChangeLog first + try { + ItemState state = changes.get(id); + if (state != null) { + return state; + } + } catch (NoSuchItemStateException e) { + // item has been deleted, but we still return it by asking base + } + return base.getItemState(id); + } + + /** + * Returns true if there exists a {@link ItemState} either + * in the {@link ChangeLog} or the base {@link ItemStateManager}; + * otherwise false is returned. + * @param id the id of the {@link ItemState}. + * @return true if there exists a {@link ItemState} either + * in the {@link ChangeLog} or the base {@link ItemStateManager}; + * otherwise false. + */ + public boolean hasItemState(ItemId id) { + // check ChangeLog first + try { + ItemState state = changes.get(id); + if (state != null) { + return true; + } + } catch (NoSuchItemStateException e) { + // item has been deleted, but we still might return true by asking base + } + return base.hasItemState(id); + } + + /** + * Always throws a {@link UnsupportedOperationException}. + */ + public NodeReferences getNodeReferences(NodeReferencesId id) + throws NoSuchItemStateException, ItemStateException { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns the removed {@link ItemState}s from the ChangeLog. + */ + private static class AtticItemStateManager implements ItemStateManager { + + /** + * Map of deleted {@link ItemState}s indexed by {@link ItemId}. + */ + private final Map deleted = new HashMap(); + + /** + * Creates a new AtticItemStateManager based on + * changes. + * @param changes deleted {@link ItemState} are retrieved from this + * ChangeLog. + */ + private AtticItemStateManager(ChangeLog changes) { + for (Iterator it = changes.deletedStates(); it.hasNext();) { + ItemState state = (ItemState) it.next(); + deleted.put(state.getId(), state); + } + } + + /** + * Returns an {@link ItemState} it is found in the deleted map of the + * {@link ChangeLog}. + * @param id the id of the {@link ItemState}. + * @return the deleted {@link ItemState}. + * @throws NoSuchItemStateException if the {@link ItemState} cannot + * be found in the deleted map. + * @throws ItemStateException if any other error occurs. + */ + public ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { + ItemState state = (ItemState) deleted.get(id); + if (state != null) { + return state; + } else { + throw new NoSuchItemStateException(); + } + } + + /** + * Returns true if an {@link ItemState} with id + * is found in the deleted map of the {@link ChangeLog}; false + * otherwise. + * @param id the id of the {@link ItemState}. + * @return true if an {@link ItemState} with id + * is found in the deleted map of the {@link ChangeLog}; false + * otherwise. + */ + public boolean hasItemState(ItemId id) { + return deleted.containsKey(id); + } + + /** + * Always throws a {@link UnsupportedOperationException}. + */ + public NodeReferences getNodeReferences(NodeReferencesId id) + throws NoSuchItemStateException, ItemStateException { + throw new UnsupportedOperationException(); + } + } +} Propchange: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/EventStateCollection.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/EventStateCollection.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (original) +++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/EventStateCollection.java Thu Mar 3 06:05:35 2005 @@ -66,33 +66,32 @@ private final SessionImpl session; /** - * The HierarchyManager of the session that creates the events. - */ - private final HierarchyManager hmgr; - - /** * Creates a new empty EventStateCollection. * * @param session the session that created these events. */ EventStateCollection(ObservationManagerFactory dispatcher, - SessionImpl session, - HierarchyManager hmgr) { + SessionImpl session) { this.dispatcher = dispatcher; this.session = session; - this.hmgr = hmgr; } /** * Creates {@link EventState} instances from ItemState * changes. + * @param rootNodeUUID the UUID of the root node. * @param changes the changes on ItemStates. * @param provider an ItemStateProvider to provide ItemState * of items that are not contained in the changes collection. * @throws ItemStateException if an error occurs while creating events * states for the item state changes. */ - public void createEventStates(ChangeLog changes, ItemStateManager provider) throws ItemStateException { + public void createEventStates(String rootNodeUUID, ChangeLog changes, ItemStateManager provider) throws ItemStateException { + // create a hierarchy manager, that is based on the ChangeLog and + // the ItemStateProvider + // todo use CachingHierarchyManager ? + HierarchyManager hmgr = new ChangeLogBasedHierarchyMgr(rootNodeUUID, provider, changes, session.getNamespaceResolver()); + for (Iterator it = changes.modifiedStates(); it.hasNext();) { ItemState state = (ItemState) it.next(); if (state.isNode()) { Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerFactory.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerFactory.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerFactory.java (original) +++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerFactory.java Thu Mar 3 06:05:35 2005 @@ -21,7 +21,6 @@ import org.apache.commons.collections.UnboundedFifoBuffer; import org.apache.log4j.Logger; import org.apache.jackrabbit.core.SessionImpl; -import org.apache.jackrabbit.core.HierarchyManager; import org.apache.jackrabbit.core.ItemManager; import java.util.Collections; @@ -141,9 +140,8 @@ * @return an ObservationManager. */ public ObservationManagerImpl createObservationManager(SessionImpl session, - HierarchyManager hmgr, ItemManager itemMgr) { - return new ObservationManagerImpl(this, session, hmgr, itemMgr); + return new ObservationManagerImpl(this, session, itemMgr); } /** Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java (original) +++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java Thu Mar 3 06:05:35 2005 @@ -11,14 +11,12 @@ * accordance with the terms of the license agreement you entered into * with Day. */ - package org.apache.jackrabbit.core.observation; import org.apache.jackrabbit.core.SessionImpl; import org.apache.jackrabbit.core.ItemManager; import org.apache.jackrabbit.core.Path; import org.apache.jackrabbit.core.MalformedPathException; -import org.apache.jackrabbit.core.HierarchyManager; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.log4j.Logger; @@ -45,11 +43,6 @@ private final SessionImpl session; /** - * The HierarchyManager of the session. - */ - private final HierarchyManager hmgr; - - /** * The ItemManager for this ObservationManager. */ private final ItemManager itemMgr; @@ -62,7 +55,6 @@ * * @param session the Session this ObservationManager * belongs to. - * @param hmgr the HierarchyManager of the session. * @param itemMgr {@link org.apache.jackrabbit.core.ItemManager} of the passed * Session. * @throws NullPointerException if session or itemMgr @@ -70,7 +62,6 @@ */ ObservationManagerImpl(ObservationManagerFactory obsMgrFactory, SessionImpl session, - HierarchyManager hmgr, ItemManager itemMgr) { if (session == null) { throw new NullPointerException("session"); @@ -81,7 +72,6 @@ this.obsMgrFactory = obsMgrFactory; this.session = session; - this.hmgr = hmgr; this.itemMgr = itemMgr; } @@ -159,7 +149,7 @@ * @return a new EventStateCollection. */ public EventStateCollection createEventStateCollection() { - return new EventStateCollection(obsMgrFactory, session, hmgr); + return new EventStateCollection(obsMgrFactory, session); } } Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original) +++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Thu Mar 3 06:05:35 2005 @@ -440,7 +440,7 @@ EventStateCollection events = null; if (obsMgr != null) { events = obsMgr.createEventStateCollection(); - events.createEventStates(local, this); + events.createEventStates(root.getUUID(), local, this); events.prepare(); } Modified: incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java (original) +++ incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java Thu Mar 3 06:05:35 2005 @@ -43,7 +43,7 @@ public void testSingleNodeRemoved() throws RepositoryException { EventResult result = new EventResult(log); addEventListener(result, Event.NODE_REMOVED); - Node foo = testRootNode.addNode(nodeName1); + Node foo = testRootNode.addNode(nodeName1, testNodeType); testRootNode.save(); foo.remove(); testRootNode.save(); @@ -59,7 +59,7 @@ public void testMultiNodesRemoved() throws RepositoryException { EventResult result = new EventResult(log); addEventListener(result, Event.NODE_REMOVED); - Node n1 = testRootNode.addNode(nodeName1); + Node n1 = testRootNode.addNode(nodeName1, testNodeType); n1.addNode(nodeName2); testRootNode.save(); n1.remove(); Modified: incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java (original) +++ incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java Thu Mar 3 06:05:35 2005 @@ -46,7 +46,7 @@ public void testSystemGenerated() throws RepositoryException { EventResult result = new EventResult(log); addEventListener(result, Event.PROPERTY_ADDED); - testRootNode.addNode(nodeName1); + testRootNode.addNode(nodeName1, testNodeType); testRootNode.save(); removeEventListener(result); Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT); @@ -64,7 +64,7 @@ * @throws RepositoryException */ public void testSinglePropertyAdded() throws RepositoryException { - Node foo = testRootNode.addNode(nodeName1); + Node foo = testRootNode.addNode(nodeName1, testNodeType); testRootNode.save(); EventResult result = new EventResult(log); addEventListener(result, Event.PROPERTY_ADDED); @@ -80,7 +80,7 @@ * when multiple properties are added. */ public void testMultiPropertyAdded() throws RepositoryException { - Node foo = testRootNode.addNode(nodeName1); + Node foo = testRootNode.addNode(nodeName1, testNodeType); testRootNode.save(); EventResult result = new EventResult(log); addEventListener(result, Event.PROPERTY_ADDED); Modified: incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java?view=diff&r1=156038&r2=156039 ============================================================================== --- incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java (original) +++ incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java Thu Mar 3 06:05:35 2005 @@ -16,8 +16,14 @@ */ package org.apache.jackrabbit.test.api.observation; +import org.apache.jackrabbit.test.NotExecutableException; + import javax.jcr.Node; import javax.jcr.RepositoryException; +import javax.jcr.PropertyType; +import javax.jcr.StringValue; +import javax.jcr.LongValue; +import javax.jcr.nodetype.NodeType; import javax.jcr.observation.Event; /** @@ -42,7 +48,7 @@ * triggered when a single property is changed. */ public void testSinglePropertyChanged() throws RepositoryException { - Node node = testRootNode.addNode(nodeName1); + Node node = testRootNode.addNode(nodeName1, testNodeType); node.setProperty(propertyName1, "foo"); testRootNode.save(); EventResult result = new EventResult(log); @@ -60,7 +66,7 @@ * @throws RepositoryException */ public void testMultiPropertyChanged() throws RepositoryException { - Node node = testRootNode.addNode(nodeName1); + Node node = testRootNode.addNode(nodeName1, testNodeType); node.setProperty(propertyName1, "foo"); node.setProperty(propertyName2, "bar"); testRootNode.save(); @@ -80,7 +86,7 @@ * triggered only for changed properties and not for new properties. */ public void testSinglePropertyChangedWithAdded() throws RepositoryException { - Node node = testRootNode.addNode(nodeName1); + Node node = testRootNode.addNode(nodeName1, testNodeType); node.setProperty(propertyName1, "foo"); testRootNode.save(); EventResult result = new EventResult(log); @@ -93,4 +99,49 @@ checkPropertyChanged(events, new String[]{nodeName1 + "/" + propertyName1}); } + /** + * Tests if either a + * + * is triggered if a property is transiently removed and set again with + * the same name but different type and then saved. + *

+ * If the node type {@link #testNodeType} does not suppport a property with + * name {@link #propertyName1} of type {@link PropertyType#UNDEFINED} a + * {@link NotExecutableException} is thrown. + */ + public void testPropertyRemoveCreate() + throws RepositoryException, NotExecutableException { + Node n = testRootNode.addNode(nodeName1, testNodeType); + NodeType nt = superuser.getWorkspace().getNodeTypeManager().getNodeType(testNodeType); + StringValue v1 = new StringValue("foo"); + LongValue v2 = new LongValue(System.currentTimeMillis()); + if (!nt.canSetProperty(propertyName1, v1) || !nt.canSetProperty(propertyName1, v2)) { + throw new NotExecutableException("Property " + propertyName1 + " is not of type UNDEFINED"); + } + n.setProperty(propertyName1, v1); + testRootNode.save(); + EventResult result = new EventResult(log); + addEventListener(result, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED); + n.getProperty(propertyName1).remove(); + n.setProperty(propertyName1, v2); + testRootNode.save(); + removeEventListener(result); + Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT); + + if (events.length == 1) { + checkPropertyChanged(events, new String[]{nodeName1 + "/" + propertyName1}); + } else { + // prop remove and add event + assertEquals("Expected 2 events for a property type change.", 2, events.length); + int type = Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED; + String path = testRoot + "/" + nodeName1 + "/" + propertyName1; + for (int i = 0; i < events.length; i++) { + assertTrue("Event is not of type PROPERTY_REMOVED or PROPERTY_ADDED", (events[i].getType() & type) > 0); + assertEquals("Path for event is wrong.", path, events[i].getPath()); + } + } + } }