You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2009/04/15 21:43:02 UTC

svn commit: r765328 [1/2] - in /jackrabbit/trunk: jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/observation/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/obse...

Author: mreutegg
Date: Wed Apr 15 19:43:01 2009
New Revision: 765328

URL: http://svn.apache.org/viewvc?rev=765328&view=rev
Log:
JCR-2074: JSR 283: New Event Types

Added:
    jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/observation/Event.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/AbstractObservationTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/EventJournalTest.java   (contents, props changed)
      - copied, changed from r765154, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/EventJournalTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetDateTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetIdentifierTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetInfoTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetUserDataTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeMovedTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeReorderTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/TestAll.java   (with props)
Removed:
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/AbstractObservationTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/EventJournalTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/UserDataTest.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ShareableNodesTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java
    jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/AbstractObservationTest.java

Added: jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/observation/Event.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/observation/Event.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/observation/Event.java (added)
+++ jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/observation/Event.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.jsr283.observation;
+
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * <code>Event</code> is a preliminary interface that contains the new methods
+ * introduce in JSR 283.
+ * <p/>
+ * <b>This interface will be removed once JSR 283 is final.</b>
+ */
+public interface Event extends javax.jcr.observation.Event {
+
+    /**
+     * Generated on persist when a node is moved.
+     * <ul>
+     * <li>{@link #getPath} returns the absolute path of the destination of the move.</li>
+     * <li>{@link #getIdentifier} returns the identifier of the moved node.
+     * <li>
+     *   {@link #getInfo} If the method that caused this event was
+     *   a {@link javax.jcr.Session#move Session.move} or {@link javax.jcr.Workspace#move Workspace.move}
+     *   then the returned {@link java.util.Map Map} has keys <code>srcAbsPath</code> and <code>destAbsPath</code>
+     *   with values corresponding to the parameters passed to the <code>move</code> method.
+     *   <p>
+     *   If the method that caused this event was a {@link javax.jcr.Node#orderBefore Node.orderBefore}
+     *   then the returned <code>Map</code> has keys <code>srcChildRelPath</code> and <code>destChildRelPath</code>
+     *   with values corresponding to the parameters passed to the <code>orderBefore</code> method.
+     * </li>
+     * </ul>
+     *
+     * @since JCR 2.0
+     */
+    public static final int NODE_MOVED = 0x20;
+
+    /**
+     * If event bundling is supported, this event is used to indicate a
+     * bundle boundary within the event journal.
+     * <ul>
+     * <li>{@link #getPath} returns <code>null</code>.</li>
+     * <li>{@link #getIdentifier} returns <code>null</code>.</li>
+     * <li>{@link #getInfo} returns an empty <code>Map</code> object.</li>
+     * </ul>
+     *
+     * @since JCR 2.0
+     */
+    public static final int PERSIST = 0x40;
+
+    /**
+     * Returns the identifier associated with this event or <code>null</code>
+     * if this event has no associated identifier. The meaning of the associated
+     * identifier depends upon the type of the event.
+     * See event type constants above.
+     *
+     * @return the identifier associated with this event or <code>null</code>.
+     * @throws RepositoryException if an error occurs.
+     * @since JCR 2.0
+     */
+    public String getIdentifier() throws RepositoryException;
+
+    /**
+     * Returns the information map associated with this event.
+     * The meaning of the map depends upon the type of the event.
+     * See event type constants above.
+     *
+     * @return A <code>Map</code> containing parameter information
+     * for instances of a <code>NODE_MOVED</code> event.
+     *
+     * @throws RepositoryException if an error occurs.
+     * @since JCR 2.0
+     */
+    public Map getInfo() throws RepositoryException;
+
+    /**
+     * Returns the user data set through <code>ObservationManager.setUserData()</code>
+     * on the <code>ObservationManager</code> bound to the <code>Session</code> that caused
+     * the event.
+     *
+     * @return String
+     * @throws RepositoryException if an error occurs.
+     * @since JCR 2.0
+     */
+    public String getUserData() throws RepositoryException;
+
+    /**
+     * Returns the date when the change was persisted that caused this event.
+     * The date is represented as a millisecond value that is an offset from the
+     * Epoch, January 1, 1970 00:00:00.000 GMT (Gregorian). The granularity of
+     * the returned value is implementation dependent.
+     *
+     * @return the date when the change was persisted that caused this event.
+     * @throws RepositoryException if an error occurs.
+     * @since JCR 2.0
+     */
+    public long getDate() throws RepositoryException;
+}

Propchange: jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/observation/Event.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java?rev=765328&r1=765327&r2=765328&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java Wed Apr 15 19:43:01 2009
@@ -22,11 +22,14 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.Map;
+import java.util.HashMap;
 
 import javax.jcr.Session;
-import javax.jcr.observation.Event;
+import javax.jcr.PropertyType;
 
 import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.core.journal.JournalException;
 import org.apache.jackrabbit.core.journal.Record;
 import org.apache.jackrabbit.core.observation.EventState;
@@ -36,6 +39,7 @@
 import org.apache.jackrabbit.core.state.PropertyState;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.api.jsr283.observation.Event;
 
 /**
  * Cluster record representing a workspace or version update.
@@ -278,8 +282,32 @@
             mixins.add(record.readQName());
         }
         String userId = record.readString();
-        events.add(createEventState(type, parentId, parentPath, childId,
-                childRelPath, ntName, mixins, userId));
+
+        Map info = null;
+        if (type == Event.NODE_MOVED) {
+            info = new HashMap();
+            // read info map
+            int infoSize = record.readInt();
+            for (int i = 0; i < infoSize; i++) {
+                String key = record.readString();
+                int propType = record.readInt();
+                InternalValue value;
+                if (propType == PropertyType.UNDEFINED) {
+                    // indicates null value
+                    value = null;
+                } else {
+                    value = InternalValue.valueOf(record.readString(), propType);
+                }
+                info.put(key, value);
+            }
+        }
+
+        EventState es = createEventState(type, parentId, parentPath, childId,
+                childRelPath, ntName, mixins, userId);
+        if (info != null) {
+            es.setInfo(info);
+        }
+        events.add(es);
     }
 
     /**
@@ -299,24 +327,27 @@
                                         NodeId childId, Path.Element childRelPath,
                                         Name ntName, Set mixins, String userId) {
         switch (type) {
-        case Event.NODE_ADDED:
-            return EventState.childNodeAdded(parentId, parentPath, childId, childRelPath,
-                    ntName, mixins, getOrCreateSession(userId), true);
-        case Event.NODE_REMOVED:
-            return EventState.childNodeRemoved(parentId, parentPath, childId, childRelPath,
-                    ntName, mixins, getOrCreateSession(userId), true);
-        case Event.PROPERTY_ADDED:
-            return EventState.propertyAdded(parentId, parentPath, childRelPath,
-                    ntName, mixins, getOrCreateSession(userId), true);
-        case Event.PROPERTY_CHANGED:
-            return EventState.propertyChanged(parentId, parentPath, childRelPath,
-                    ntName, mixins, getOrCreateSession(userId), true);
-        case Event.PROPERTY_REMOVED:
-            return EventState.propertyRemoved(parentId, parentPath, childRelPath,
-                    ntName, mixins, getOrCreateSession(userId), true);
-        default:
-            String msg = "Unexpected event type: " + type;
-            throw new IllegalArgumentException(msg);
+            case Event.NODE_ADDED:
+                return EventState.childNodeAdded(parentId, parentPath, childId, childRelPath,
+                        ntName, mixins, getOrCreateSession(userId), true);
+            case Event.NODE_MOVED:
+                return EventState.nodeMoved(parentId, parentPath, childId, childRelPath,
+                        ntName, mixins, getOrCreateSession(userId), true);
+            case Event.NODE_REMOVED:
+                return EventState.childNodeRemoved(parentId, parentPath, childId, childRelPath,
+                        ntName, mixins, getOrCreateSession(userId), true);
+            case Event.PROPERTY_ADDED:
+                return EventState.propertyAdded(parentId, parentPath, childRelPath,
+                        ntName, mixins, getOrCreateSession(userId), true);
+            case Event.PROPERTY_CHANGED:
+                return EventState.propertyChanged(parentId, parentPath, childRelPath,
+                        ntName, mixins, getOrCreateSession(userId), true);
+            case Event.PROPERTY_REMOVED:
+                return EventState.propertyRemoved(parentId, parentPath, childRelPath,
+                        ntName, mixins, getOrCreateSession(userId), true);
+            default:
+                String msg = "Unexpected event type: " + type;
+                throw new IllegalArgumentException(msg);
         }
     }
 
@@ -448,6 +479,25 @@
             record.writeQName((Name) iter.next());
         }
         record.writeString(event.getUserId());
+
+        if (event.getType() == Event.NODE_MOVED) {
+            // write info map
+            Map info = event.getInfo();
+            record.writeInt(info.size());
+            for (Iterator it = info.entrySet().iterator(); it.hasNext(); ) {
+                Map.Entry entry = (Map.Entry) it.next();
+                String key = (String) entry.getKey();
+                InternalValue value = (InternalValue) entry.getValue();
+                record.writeString(key);
+                if (value == null) {
+                    // use undefined for null value
+                    record.writeInt(PropertyType.UNDEFINED);
+                } else {
+                    record.writeInt(value.getType());
+                    record.writeString(value.toString());
+                }
+            }
+        }
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java?rev=765328&r1=765327&r2=765328&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java Wed Apr 15 19:43:01 2009
@@ -16,9 +16,15 @@
  */
 package org.apache.jackrabbit.core.observation;
 
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+
 import org.apache.jackrabbit.api.observation.JackrabbitEvent;
+import org.apache.jackrabbit.api.jsr283.observation.Event;
 import org.apache.jackrabbit.core.NodeId;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
@@ -31,7 +37,7 @@
  * Implementation of the {@link javax.jcr.observation.Event} and
  * the {@link JackrabbitEvent} interface.
  */
-public final class EventImpl implements JackrabbitEvent {
+public final class EventImpl implements JackrabbitEvent, Event {
 
     /**
      * Logger instance for this class
@@ -119,6 +125,35 @@
         return userData;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public String getIdentifier() throws RepositoryException {
+        NodeId id = eventState.getChildId();
+        if (id == null) {
+            // property event
+            id = eventState.getParentId();
+        }
+        return id.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map getInfo() throws RepositoryException {
+        Map info = new HashMap();
+        for (Iterator it = eventState.getInfo().entrySet().iterator(); it.hasNext(); ) {
+            Map.Entry entry = (Map.Entry) it.next();
+            InternalValue value = (InternalValue) entry.getValue();
+            String strValue = null;
+            if (value != null) {
+                strValue = value.toJCRValue(session).getString();
+            }
+            info.put(entry.getKey(), strValue);
+        }
+        return info;
+    }
+
     //-----------------------------------------------------------< EventImpl >
 
     /**
@@ -205,6 +240,7 @@
             sb.append(", UserId: ").append(getUserID());
             sb.append(", Timestamp: ").append(timestamp);
             sb.append(", UserData: ").append(userData);
+            sb.append(", Info: ").append(eventState.getInfo());
             stringValue = sb.toString();
         }
         return stringValue;

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java?rev=765328&r1=765327&r2=765328&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java Wed Apr 15 19:43:01 2009
@@ -20,18 +20,25 @@
 import org.apache.jackrabbit.core.ItemId;
 import org.apache.jackrabbit.core.PropertyId;
 import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.PathBuilder;
+import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
+import org.apache.jackrabbit.api.jsr283.observation.Event;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 
 import javax.jcr.Session;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.observation.Event;
 import java.util.Set;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
 
 /**
  * The <code>EventState</code> class encapsulates the session
@@ -45,6 +52,26 @@
     private static final Logger log = LoggerFactory.getLogger(EventState.class);
 
     /**
+     * The key <code>srcAbsPath</code> in the info map.
+     */
+    static final String SRC_ABS_PATH = "srcAbsPath";
+
+    /**
+     * The key <code>destAbsPath</code> in the info map.
+     */
+    static final String DEST_ABS_PATH = "destAbsPath";
+
+    /**
+     * The key <code>srcChildRelPath</code> in the info map.
+     */
+    static final String SRC_CHILD_REL_PATH = "srcChildRelPath";
+
+    /**
+     * The key <code>destChildRelPath</code> in the info map.
+     */
+    static final String DEST_CHILD_REL_PATH = "destChildRelPath";
+
+    /**
      * The {@link javax.jcr.observation.Event} of this event.
      */
     private final int type;
@@ -113,6 +140,11 @@
     private final boolean external;
 
     /**
+     * The info Map associated with this event.
+     */
+    private Map info = Collections.EMPTY_MAP;
+
+    /**
      * If set to <code>true</code>, indicates that the child node of a node
      * added or removed event is a shareable node.
      */
@@ -276,6 +308,128 @@
 
     /**
      * Creates a new {@link javax.jcr.observation.Event} of type
+     * <code>NODE_MOVED</code>. The parent node associated with this event type
+     * is the parent node of the destination of the move!
+     * This method creates an event state without an info map. A caller of this
+     * method must ensure that it is properly set afterwards.
+     *
+     * @param parentId   the id of the parent node associated with
+     *                   this <code>EventState</code>.
+     * @param parentPath the path of the parent node associated with
+     *                   this <code>EventState</code>.
+     * @param childId    the id of the child node associated with this event.
+     * @param childPath  the relative path of the child node that was moved.
+     * @param nodeType   the node type of the parent node.
+     * @param mixins     mixins assigned to the parent node.
+     * @param session    the session that moved the node.
+     * @param external   flag indicating whether this is an external event
+     * @return an <code>EventState</code> instance.
+     */
+    public static EventState nodeMoved(NodeId parentId,
+                                       Path parentPath,
+                                       NodeId childId,
+                                       Path.Element childPath,
+                                       Name nodeType,
+                                       Set mixins,
+                                       Session session,
+                                       boolean external) {
+
+        return new EventState(Event.NODE_MOVED, parentId, parentPath,
+                childId, childPath, nodeType, mixins, session, external);
+    }
+
+    /**
+     * Creates a new {@link javax.jcr.observation.Event} of type
+     * <code>NODE_MOVED</code>. The parent node associated with this event type
+     * is the parent node of the destination of the move!
+     *
+     * @param parentId the id of the parent node associated with this
+     *                 <code>EventState</code>.
+     * @param destPath the path of the destination of the move.
+     * @param childId  the id of the child node associated with this event.
+     * @param srcPath  the path of the source of the move.
+     * @param nodeType the node type of the parent node.
+     * @param mixins   mixins assigned to the parent node.
+     * @param session  the session that removed the node.
+     * @param external flag indicating whether this is an external event
+     * @return an <code>EventState</code> instance.
+     * @throws ItemStateException if <code>destPath</code> does not have a
+     *                            parent.
+     */
+    public static EventState nodeMoved(NodeId parentId,
+                                       Path destPath,
+                                       NodeId childId,
+                                       Path srcPath,
+                                       Name nodeType,
+                                       Set mixins,
+                                       Session session,
+                                       boolean external)
+            throws ItemStateException {
+        try {
+            EventState es = nodeMoved(parentId, destPath.getAncestor(1),
+                    childId, destPath.getNameElement(), nodeType, mixins,
+                    session, external);
+            Map info = new HashMap();
+            info.put(SRC_ABS_PATH, InternalValue.create(srcPath));
+            info.put(DEST_ABS_PATH, InternalValue.create(destPath));
+            es.setInfo(info);
+            return es;
+        } catch (PathNotFoundException e) {
+            // should never happen actually
+            String msg = "Unable to resolve parent for path: " + destPath;
+            log.error(msg);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * Creates a new {@link javax.jcr.observation.Event} of type
+     * <code>NODE_MOVED</code>. The parent node associated with this event type
+     * is the parent node of the destination of the reorder!
+     *
+     * @param parentId      the id of the parent node associated with this
+     *                      <code>EventState</code>.
+     * @param parentPath    the path of the parent node associated with
+     *                      this <code>EventState</code>.
+     * @param childId       the id of the child node associated with this
+     *                      event.
+     * @param destChildPath the name element of the node before it was reordered.
+     * @param srcChildPath  the name element of the reordered node before the
+     *                      reorder operation.
+     * @param beforeChildPath the name element of the node before which the
+     *                      reordered node is placed. (may be <code>null</code>
+     *                      if reordered to the end.
+     * @param nodeType      the node type of the parent node.
+     * @param mixins        mixins assigned to the parent node.
+     * @param session       the session that removed the node.
+     * @param external      flag indicating whether this is an external event
+     * @return an <code>EventState</code> instance.
+     */
+    public static EventState nodeReordered(NodeId parentId,
+                                           Path parentPath,
+                                           NodeId childId,
+                                           Path.Element destChildPath,
+                                           Path.Element srcChildPath,
+                                           Path.Element beforeChildPath,
+                                           Name nodeType,
+                                           Set mixins,
+                                           Session session,
+                                           boolean external) {
+        EventState es = nodeMoved(parentId, parentPath, childId, destChildPath,
+               nodeType, mixins, session, external);
+        Map info = new HashMap();
+        info.put(SRC_CHILD_REL_PATH, createValue(srcChildPath));
+        InternalValue value = null;
+        if (beforeChildPath != null) {
+            value = createValue(beforeChildPath);
+        }
+        info.put(DEST_CHILD_REL_PATH, value);
+        es.setInfo(info);
+        return es;
+    }
+
+    /**
+     * Creates a new {@link javax.jcr.observation.Event} of type
      * {@link javax.jcr.observation.Event#PROPERTY_ADDED}.
      *
      * @param parentId   the id of the parent node associated with
@@ -569,6 +723,22 @@
     }
 
     /**
+     * @return an unmodifiable info Map.
+     */
+    public Map getInfo() {
+        return info;
+    }
+
+    /**
+     * Sets a new info map for this event.
+     *
+     * @param info the new info map.
+     */
+    public void setInfo(Map info) {
+        this.info = Collections.unmodifiableMap(new HashMap(info));
+    }
+
+    /**
      * Returns a flag indicating whether the child node of this event is a
      * shareable node. Only applies to node added/removed events.
      *
@@ -601,6 +771,7 @@
             sb.append(", Parent: ").append(parentId);
             sb.append(", Child: ").append(childRelPath);
             sb.append(", UserId: ").append(session.getUserID());
+            sb.append(", Info: ").append(info);
             stringValue = sb.toString();
         }
         return stringValue;
@@ -619,6 +790,7 @@
             h = 37 * h + parentId.hashCode();
             h = 37 * h + childRelPath.hashCode();
             h = 37 * h + session.hashCode();
+            h = 37 * h + info.hashCode();
             hashCode = h;
         }
         return hashCode;
@@ -641,7 +813,8 @@
             return this.type == other.type
                     && this.parentId.equals(other.parentId)
                     && this.childRelPath.equals(other.childRelPath)
-                    && this.session.equals(other.session);
+                    && this.session.equals(other.session)
+                    && this.info.equals(other.info);
         }
         return false;
     }
@@ -655,6 +828,8 @@
     public static String valueOf(int eventType) {
         if (eventType == Event.NODE_ADDED) {
             return "NodeAdded";
+        } else if (eventType == Event.NODE_MOVED) {
+            return "NodeMoved";
         } else if (eventType == Event.NODE_REMOVED) {
             return "NodeRemoved";
         } else if (eventType == Event.PROPERTY_ADDED) {
@@ -668,4 +843,20 @@
         }
     }
 
+    /**
+     * Creates an internal path value from the given path <code>element</code>.
+     *
+     * @param element the path element.
+     * @return an internal value wrapping the path element.
+     */
+    private static InternalValue createValue(Path.Element element) {
+        PathBuilder builder = new PathBuilder();
+        builder.addFirst(element);
+        try {
+            return InternalValue.create(builder.getPath());
+        } catch (MalformedPathException e) {
+            // this exception is only thrown when number of element is zero
+            throw new InternalError();
+        }
+    }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java?rev=765328&r1=765327&r2=765328&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java Wed Apr 15 19:43:01 2009
@@ -171,7 +171,7 @@
                 // 2) property removed
                 // 3) child node added
                 // 4) child node removed
-                // 5) node moved
+                // 5) node moved/reordered
                 // 6) node reordered
                 // 7) shareable node added
                 // 8) shareable node removed
@@ -213,23 +213,24 @@
                         Path newPath = getPath(n.getNodeId(), hmgr);
                         Path oldPath = getZombiePath(n.getNodeId(), hmgr);
                         events.add(EventState.childNodeRemoved(oldParentId,
-                                getParent(oldPath),
-                                n.getNodeId(),
+                                getParent(oldPath), n.getNodeId(),
                                 oldPath.getNameElement(),
                                 oldParentNodeType.getQName(),
-                                mixins,
-                                session));
+                                mixins, session));
 
                         NodeState newParent = (NodeState) changes.get(newParentId);
                         NodeTypeImpl newParentNodeType = getNodeType(newParent, session);
                         mixins = newParent.getMixinTypeNames();
                         events.add(EventState.childNodeAdded(newParentId,
-                                getParent(newPath),
-                                n.getNodeId(),
+                                getParent(newPath), n.getNodeId(),
                                 newPath.getNameElement(),
                                 newParentNodeType.getQName(),
-                                mixins,
-                                session));
+                                mixins, session));
+
+                        events.add(EventState.nodeMoved(newParentId,
+                                newPath, n.getNodeId(), oldPath,
+                                newParentNodeType.getQName(), mixins,
+                                session, false));
                     } else {
                         // a moved node always has a modified parent node
                         NodeState parent = null;
@@ -274,20 +275,20 @@
                                     log.error(msg);
                                     throw new ItemStateException(msg, e);
                                 }
-                                events.add(EventState.childNodeRemoved(parent.getNodeId(),
-                                        parentPath,
-                                        n.getNodeId(),
-                                        oldPath.getNameElement(),
-                                        nodeType.getQName(),
-                                        mixins,
-                                        session));
-                                events.add(EventState.childNodeAdded(parent.getNodeId(),
-                                        parentPath,
-                                        n.getNodeId(),
-                                        newPath.getNameElement(),
-                                        nodeType.getQName(),
-                                        mixins,
-                                        session));
+                                events.add(EventState.childNodeRemoved(
+                                        parent.getNodeId(), parentPath,
+                                        n.getNodeId(), oldPath.getNameElement(),
+                                        nodeType.getQName(), mixins, session));
+
+                                events.add(EventState.childNodeAdded(
+                                        parent.getNodeId(), parentPath,
+                                        n.getNodeId(), newPath.getNameElement(),
+                                        nodeType.getQName(), mixins, session));
+
+                                events.add(EventState.nodeMoved(
+                                        parent.getNodeId(), newPath, n.getNodeId(),
+                                        oldPath, nodeType.getQName(), mixins,
+                                        session, false));
                             }
                         }
                     }
@@ -302,34 +303,37 @@
                     // reorder
                     for (Iterator ro = reordered.iterator(); ro.hasNext();) {
                         ChildNodeEntry child = (ChildNodeEntry) ro.next();
-                        Name name = child.getName();
-                        int index = (child.getIndex() != 1) ? child.getIndex() : 0;
+                        Path.Element addedElem = getPathElement(child);
                         Path parentPath = getPath(n.getNodeId(), hmgr);
-                        Path.Element addedElem = PathFactoryImpl.getInstance().createElement(name, index);
                         // get removed index
                         NodeState overlayed = (NodeState) n.getOverlayedState();
                         ChildNodeEntry entry = overlayed.getChildNodeEntry(child.getId());
                         if (entry == null) {
                             throw new ItemStateException("Unable to retrieve old child index for item: " + child.getId());
                         }
-                        int oldIndex = (entry.getIndex() != 1) ? entry.getIndex() : 0;
-                        Path.Element removedElem = PathFactoryImpl.getInstance().createElement(name, oldIndex);
+                        Path.Element removedElem = getPathElement(entry);
 
                         events.add(EventState.childNodeRemoved(n.getNodeId(),
-                                parentPath,
-                                child.getId(),
-                                removedElem,
-                                nodeType.getQName(),
-                                mixins,
-                                session));
+                                parentPath, child.getId(), removedElem,
+                                nodeType.getQName(), mixins, session));
 
                         events.add(EventState.childNodeAdded(n.getNodeId(),
-                                parentPath,
-                                child.getId(),
-                                addedElem,
-                                nodeType.getQName(),
-                                mixins,
-                                session));
+                                parentPath, child.getId(), addedElem,
+                                nodeType.getQName(), mixins, session));
+
+                        List cne = n.getChildNodeEntries();
+                        // index of the child node entry before which this
+                        // child node entry was reordered
+                        int idx = cne.indexOf(child) + 1;
+                        Path.Element beforeElem = null;
+                        if (idx < cne.size()) {
+                            beforeElem = getPathElement((ChildNodeEntry) cne.get(idx));
+                        }
+
+                        events.add(EventState.nodeReordered(n.getNodeId(),
+                                parentPath, child.getId(), addedElem,
+                                removedElem, beforeElem, nodeType.getQName(), mixins,
+                                session, false));
                     }
                 }
 
@@ -342,11 +346,8 @@
                 NodeTypeImpl nodeType = getNodeType(parent, session);
                 Set mixins = parent.getMixinTypeNames();
                 events.add(EventState.propertyChanged(state.getParentId(),
-                        getParent(path),
-                        path.getNameElement(),
-                        nodeType.getQName(),
-                        mixins,
-                        session));
+                        getParent(path), path.getNameElement(),
+                        nodeType.getQName(), mixins, session));
             }
         }
 
@@ -753,6 +754,18 @@
     }
 
     /**
+     * Returns the path element for the given child node <code>entry</code>.
+     *
+     * @param entry a child node entry.
+     * @return the path element for the given entry.
+     */
+    private Path.Element getPathElement(ChildNodeEntry entry) {
+        Name name = entry.getName();
+        int index = (entry.getIndex() != 1) ? entry.getIndex() : 0;
+        return PathFactoryImpl.getInstance().createElement(name, index);
+    }
+
+    /**
      * Get the longest common path of all event state paths. 
      * 
      * @return the longest common path

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/AbstractObservationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/AbstractObservationTest.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/AbstractObservationTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/AbstractObservationTest.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,119 @@
+/*
+ * 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.api.jsr283.observation;
+
+import java.util.Map;
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+
+import org.apache.jackrabbit.test.api.observation.EventResult;
+import org.apache.jackrabbit.core.observation.ObservationManagerImpl;
+
+/**
+ * <code>AbstractObservationTest</code> is a base class with utility methods
+ * for observation related tests.
+ */
+public abstract class AbstractObservationTest
+        extends org.apache.jackrabbit.test.api.observation.AbstractObservationTest {
+
+    /**
+     * TODO: remove when JSR 283 is final
+     */
+    protected static final int NODE_MOVED = org.apache.jackrabbit.api.jsr283.observation.Event.NODE_MOVED;
+
+    protected static final int ALL_TYPES = Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED | org.apache.jackrabbit.api.jsr283.observation.Event.NODE_MOVED;
+
+    protected void setUserData(String userData) throws RepositoryException {
+        // TODO: remove when JCR 2.0 is final
+        ((ObservationManagerImpl) obsMgr).setUserData(userData);
+    }
+
+    protected static String getUserData(Event e) throws RepositoryException {
+        // TODO: remove when JCR 2.0 is final
+        return ((org.apache.jackrabbit.api.jsr283.observation.Event) e).getUserData();
+    }
+
+    protected String getIdentifier(Node node) throws RepositoryException {
+        // TODO: remove when JSR 283 is final
+        return ((org.apache.jackrabbit.api.jsr283.Node) node).getIdentifier();
+    }
+
+    protected String getIdentifier(Event event) throws RepositoryException {
+        // TODO: remove when JSR 283 is final
+        return ((org.apache.jackrabbit.api.jsr283.observation.Event) event).getIdentifier();
+    }
+
+    protected Map getInfo(Event event) throws RepositoryException {
+        // TODO: remove when JSR 283 is final
+        return ((org.apache.jackrabbit.api.jsr283.observation.Event) event).getInfo();
+    }
+
+    protected long getDate(Event event) throws RepositoryException {
+        // TODO: remove when JSR 283 is final
+        return ((org.apache.jackrabbit.api.jsr283.observation.Event) event).getDate();
+    }
+
+    /**
+     * Returns the first event with the given <code>path</code>.
+     *
+     * @param events the events.
+     * @param path   the path.
+     * @return the event with the given <code>path</code> or {@link #fail()}s if
+     *         no such event exists.
+     * @throws RepositoryException if an error occurs while reading from the
+     *                             repository.
+     */
+    protected Event getEventByPath(Event[] events, String path)
+            throws RepositoryException {
+        for (int i = 0; i < events.length; i++) {
+            if (events[i].getPath().equals(path)) {
+                return events[i];
+            }
+        }
+        fail("no event with path: " + path + " in " + Arrays.asList(events));
+        return null;
+    }
+
+    /**
+     * Registers an event listener for the passed <code>eventTypes</code> and
+     * calls the callable.
+     *
+     * @param call       the callable.
+     * @param eventTypes the types of the events to listen for.
+     * @return the events that were generated during execution of the callable.
+     * @throws RepositoryException if an error occurs.
+     */
+    protected Event[] getEvents(Callable call, int eventTypes)
+            throws RepositoryException {
+        EventResult result = new EventResult(log);
+        addEventListener(result, eventTypes);
+        call.call();
+        Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
+        removeEventListener(result);
+        return events;
+    }
+
+    /**
+     * Helper interface.
+     */
+    protected interface Callable {
+        public void call() throws RepositoryException;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/AbstractObservationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Copied: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/EventJournalTest.java (from r765154, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/EventJournalTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/EventJournalTest.java?p2=jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/EventJournalTest.java&p1=jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/EventJournalTest.java&r1=765154&r2=765328&rev=765328&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/EventJournalTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/EventJournalTest.java Wed Apr 15 19:43:01 2009
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.core.observation;
+package org.apache.jackrabbit.api.jsr283.observation;
 
 import java.util.Set;
 import java.util.HashSet;
@@ -26,7 +26,6 @@
 import javax.jcr.observation.Event;
 
 import org.apache.jackrabbit.core.WorkspaceImpl;
-import org.apache.jackrabbit.api.jsr283.observation.EventJournal;
 
 /**
  * <code>EventJournalTest</code> performs EventJournal tests.
@@ -178,7 +177,7 @@
     public void testUserData() throws RepositoryException {
         testRootNode.addNode(nodeName1);
         String data = createRandomString(5);
-        getObservationManager().setUserData(data);
+        setUserData(data);
 
         journal = getEventJournal(ALL_TYPES, testRoot, true, null, null);
         journal.skipTo(System.currentTimeMillis());

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/EventJournalTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetDateTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetDateTest.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetDateTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetDateTest.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,59 @@
+/*
+ * 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.api.jsr283.observation;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Collections;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+
+/**
+ * <code>GetDateTest</code> checks if the dates returned by events are
+ * monotonically increasing.
+ */
+public class GetDateTest extends AbstractObservationTest {
+
+    public void testLinearTime() throws RepositoryException {
+        List names = Arrays.asList(new String[]{nodeName1, nodeName2, nodeName3});
+        List dates = new ArrayList();
+        for (Iterator it = names.iterator(); it.hasNext(); ) {
+            final String name = (String) it.next();
+            Event[] events = getEvents(new Callable() {
+                public void call() throws RepositoryException {
+                    testRootNode.addNode(name, testNodeType);
+                    testRootNode.save();
+                }
+            }, Event.NODE_ADDED);
+            for (int i = 0; i < events.length; i++) {
+                dates.add(new Long(getDate(events[i])));
+            }
+            try {
+                // wait for a moment
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+        List sortedDates = new ArrayList(dates);
+        Collections.sort(sortedDates);
+        assertEquals(sortedDates, dates);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetDateTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetIdentifierTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetIdentifierTest.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetIdentifierTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetIdentifierTest.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,106 @@
+/*
+ * 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.api.jsr283.observation;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.observation.Event;
+
+/**
+ * <code>IdentifierTest</code> checks if the identifier of an event is correct.
+ */
+public class GetIdentifierTest extends AbstractObservationTest {
+
+    public void testNodeAdded() throws RepositoryException {
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                testRootNode.addNode(nodeName1, testNodeType);
+                testRootNode.save();
+            }
+        }, Event.NODE_ADDED);
+        Node n = testRootNode.getNode(nodeName1);
+        assertEquals(getIdentifier(n), getIdentifier(getEventByPath(events, n.getPath())));
+    }
+
+    public void testNodeMoved() throws RepositoryException {
+        final Node n = testRootNode.addNode(nodeName1, testNodeType);
+        String id = getIdentifier(n);
+        testRootNode.save();
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                superuser.getWorkspace().move(n.getPath(), testRoot + "/" + nodeName2);
+            }
+        }, NODE_MOVED);
+        String path = testRootNode.getNode(nodeName2).getPath();
+        assertEquals(id, getIdentifier(getEventByPath(events, path)));
+    }
+
+    public void testNodeRemoved() throws RepositoryException {
+        final Node n = testRootNode.addNode(nodeName1, testNodeType);
+        String path = n.getPath();
+        String id = getIdentifier(n);
+        testRootNode.save();
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                n.remove();
+                testRootNode.save();
+            }
+        }, Event.NODE_REMOVED);
+        assertEquals(id, getIdentifier(getEventByPath(events, path)));
+    }
+
+    public void testPropertyAdded() throws RepositoryException {
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                testRootNode.addNode(nodeName1, testNodeType).setProperty(propertyName1, "test");
+                testRootNode.save();
+            }
+        }, Event.PROPERTY_ADDED);
+        Node n = testRootNode.getNode(nodeName1);
+        Property prop = n.getProperty(propertyName1);
+        assertEquals(getIdentifier(n), getIdentifier(getEventByPath(events, prop.getPath())));
+    }
+
+    public void testPropertyChanged() throws RepositoryException {
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        final Property prop = n.setProperty(propertyName1, "test");
+        testRootNode.save();
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                prop.setValue("modified");
+                testRootNode.save();
+            }
+        }, Event.PROPERTY_CHANGED);
+        assertEquals(getIdentifier(n), getIdentifier(getEventByPath(events, prop.getPath())));
+    }
+
+    public void testPropertyRemoved() throws RepositoryException {
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        String id = getIdentifier(n);
+        final Property prop = n.setProperty(propertyName1, "test");
+        String propPath = prop.getPath();
+        testRootNode.save();
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                prop.remove();
+                testRootNode.save();
+            }
+        }, Event.PROPERTY_REMOVED);
+        assertEquals(id, getIdentifier(getEventByPath(events, propPath)));
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetIdentifierTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetInfoTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetInfoTest.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetInfoTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetInfoTest.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,97 @@
+/*
+ * 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.api.jsr283.observation;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.observation.Event;
+
+/**
+ * <code>GetInfoTest</code> checks that the info map is empty for event types:
+ * {@link Event#NODE_ADDED}, {@link Event#NODE_REMOVED},
+ * {@link Event#PROPERTY_ADDED}, {@link Event#PROPERTY_CHANGED} and
+ * {@link Event#PROPERTY_REMOVED}.
+ */
+public class GetInfoTest extends AbstractObservationTest {
+
+    public void testNodeAdded() throws RepositoryException {
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                testRootNode.addNode(nodeName1, testNodeType);
+                testRootNode.save();
+            }
+        }, Event.NODE_ADDED);
+        for (int i = 0; i < events.length; i++) {
+            assertEquals("info map must be empty", 0, getInfo(events[i]).size());
+        }
+    }
+
+    public void testNodeRemoved() throws RepositoryException {
+        final Node n = testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.save();
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                n.remove();
+                testRootNode.save();
+            }
+        }, Event.NODE_REMOVED);
+        for (int i = 0; i < events.length; i++) {
+            assertEquals("info map must be empty", 0, getInfo(events[i]).size());
+        }
+    }
+
+    public void testPropertyAdded() throws RepositoryException {
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                testRootNode.addNode(nodeName1, testNodeType).setProperty(propertyName1, "test");
+                testRootNode.save();
+            }
+        }, Event.PROPERTY_ADDED);
+        for (int i = 0; i < events.length; i++) {
+            assertEquals("info map must be empty", 0, getInfo(events[i]).size());
+        }
+    }
+
+    public void testPropertyChanged() throws RepositoryException {
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        final Property prop = n.setProperty(propertyName1, "test");
+        testRootNode.save();
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                prop.setValue("modified");
+            }
+        }, Event.PROPERTY_CHANGED);
+        for (int i = 0; i < events.length; i++) {
+            assertEquals("info map must be empty", 0, getInfo(events[i]).size());
+        }
+    }
+
+    public void testPropertyRemoved() throws RepositoryException {
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        final Property prop = n.setProperty(propertyName1, "test");
+        testRootNode.save();
+        Event[] events = getEvents(new Callable(){
+            public void call() throws RepositoryException {
+                prop.remove();
+            }
+        }, Event.PROPERTY_REMOVED);
+        for (int i = 0; i < events.length; i++) {
+            assertEquals("info map must be empty", 0, getInfo(events[i]).size());
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetInfoTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetUserDataTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetUserDataTest.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetUserDataTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetUserDataTest.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,85 @@
+/*
+ * 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.api.jsr283.observation;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.observation.Event;
+
+import org.apache.jackrabbit.test.NotExecutableException;
+
+/**
+ * <code>GetUserDataTest</code> performs observation tests with user data set
+ * on the observation manager.
+ */
+public class GetUserDataTest extends AbstractObservationTest {
+
+    public void testSave() throws RepositoryException {
+        runWithUserData(new Callable() {
+            public void call() throws RepositoryException {
+                testRootNode.addNode(nodeName1, testNodeType);
+                testRootNode.save();
+            }
+        }, ALL_TYPES);
+    }
+
+    public void testWorkspaceOperation() throws RepositoryException {
+        testRootNode.addNode(nodeName1);
+        testRootNode.save();
+
+        runWithUserData(new Callable() {
+            public void call() throws RepositoryException {
+                String src = testRoot + "/" + nodeName1;
+                String dest = testRoot + "/" + nodeName2;
+                superuser.getWorkspace().move(src, dest);
+            }
+        }, ALL_TYPES);
+    }
+
+    public void testVersioning()
+            throws RepositoryException, NotExecutableException {
+        checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+
+        final Node n1 = testRootNode.addNode(nodeName1);
+        n1.addMixin(mixVersionable);
+        testRootNode.save();
+
+        runWithUserData(new Callable() {
+            public void call() throws RepositoryException {
+                n1.checkin();
+            }
+        }, Event.NODE_ADDED); // get events for added version node
+    }
+
+    protected void runWithUserData(final Callable c, int eventTypes)
+            throws RepositoryException {
+        final String data = createRandomString(5);
+        Event[] events = getEvents(new Callable() {
+            public void call() throws RepositoryException {
+                setUserData(data);
+                c.call();
+            }
+        }, eventTypes);
+
+        assertTrue("no events returned", events.length > 0);
+        for (int i = 0; i < events.length; i++) {
+            assertEquals("Wrong user data", data, getUserData(events[i]));
+        }
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/GetUserDataTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeMovedTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeMovedTest.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeMovedTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeMovedTest.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,212 @@
+/*
+ * 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.api.jsr283.observation;
+
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.observation.Event;
+
+import org.apache.jackrabbit.test.api.observation.EventResult;
+
+/**
+ * TODO: sync with NodeMovedTest in jackrabbit-jcr-tests once JSR 283 is final.
+ *
+ * Tests if {@link javax.jcr.Session#move} operations trigger the appropriate
+ * observation events.
+ * <p/>
+ * Configuration requirements are:<br/>
+ * The {@link #testRoot} must allow child nodes of type {@link #testNodeType}.
+ * The child nodes that are created will be named {@link #nodeName1},
+ * {@link #nodeName2}, {@link #nodeName3} and {@link #nodeName4}. Furthermore
+ * {@link #testNodeType} must allow to add child nodes of the same type
+ * ({@link #testNodeType}).
+ *
+ * @test
+ * @sources NodeMovedTest.java
+ * @executeClass org.apache.jackrabbit.test.api.observation.NodeMovedTest
+ * @keywords observation
+ */
+public class NodeMovedTest extends AbstractObservationTest {
+
+    /**
+     * The key <code>srcAbsPath</code> in the info map.
+     */
+    private static final String SRC_ABS_PATH = "srcAbsPath";
+
+    /**
+     * The key <code>destAbsPath</code> in the info map.
+     */
+    private static final String DEST_ABS_PATH = "destAbsPath";
+
+    /**
+     * Tests if node removed and node added event is triggered when a tree
+     * is moved.
+     */
+    public void testMoveTree() throws RepositoryException {
+        /**
+         * Initial tree:
+         *  + testroot
+         *      + nodename1
+         *          + nodename2
+         *
+         * After move:
+         *  + testroot
+         *      + nodename3
+         *          + nodename2
+         */
+
+        Node n1 = testRootNode.addNode(nodeName1, testNodeType);
+        n1.addNode(nodeName2, testNodeType);
+        testRootNode.save();
+        EventResult addNodeListener = new EventResult(log);
+        EventResult removeNodeListener = new EventResult(log);
+        EventResult moveNodeListener = new EventResult(log);
+        addEventListener(addNodeListener, Event.NODE_ADDED);
+        addEventListener(removeNodeListener, Event.NODE_REMOVED);
+        addEventListener(moveNodeListener, NODE_MOVED);
+        superuser.move(n1.getPath(), testRoot + "/" + nodeName3);
+        testRootNode.save();
+        Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        removeEventListener(addNodeListener);
+        removeEventListener(removeNodeListener);
+        removeEventListener(moveNodeListener);
+        checkNodeAdded(added, new String[]{nodeName3}, new String[]{nodeName3 + "/" + nodeName2});
+        checkNodeRemoved(removed, new String[]{nodeName1}, new String[]{nodeName1 + "/" + nodeName2});
+        checkNodeMoved(moved, nodeName1, nodeName3);
+    }
+
+    /**
+     * Tests if node removed and node added event is triggered when a node
+     * is moved.
+     */
+    public void testMoveNode() throws RepositoryException {
+        /**
+         * Initial tree:
+         *  + testroot
+         *      + nodename1
+         *          + nodename2
+         *
+         * After move:
+         *  + testroot
+         *      + nodename1
+         *      + nodename2
+         */
+
+        Node n1 = testRootNode.addNode(nodeName1, testNodeType);
+        Node n2 = n1.addNode(nodeName2, testNodeType);
+        testRootNode.save();
+        EventResult addNodeListener = new EventResult(log);
+        EventResult removeNodeListener = new EventResult(log);
+        EventResult moveNodeListener = new EventResult(log);
+        addEventListener(addNodeListener, Event.NODE_ADDED);
+        addEventListener(removeNodeListener, Event.NODE_REMOVED);
+        addEventListener(moveNodeListener, NODE_MOVED);
+        superuser.move(n2.getPath(), testRoot + "/" + nodeName2);
+        testRootNode.save();
+        Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        removeEventListener(addNodeListener);
+        removeEventListener(removeNodeListener);
+        removeEventListener(moveNodeListener);
+        checkNodeAdded(added, new String[]{nodeName2}, null);
+        checkNodeRemoved(removed, new String[]{nodeName1 + "/" + nodeName2}, null);
+        checkNodeMoved(moved, nodeName1 + "/" + nodeName2, nodeName2);
+    }
+
+    /**
+     * Tests if a node moved triggers the correct events when the former parent
+     * node is removed at the same time.
+     */
+    public void testMoveWithRemove() throws RepositoryException {
+        /**
+         * Initial tree:
+         *  + testroot
+         *      + nodename1
+         *          + nodename2
+         *      + nodename3
+         *
+         * After move and remove:
+         *  + testroot
+         *      + nodename3
+         *          + nodename2
+         */
+        Node n1 = testRootNode.addNode(nodeName1, testNodeType);
+        Node n2 = n1.addNode(nodeName2, testNodeType);
+        Node n3 = testRootNode.addNode(nodeName3, testNodeType);
+        testRootNode.save();
+        EventResult addNodeListener = new EventResult(log);
+        EventResult removeNodeListener = new EventResult(log);
+        EventResult moveNodeListener = new EventResult(log);
+        addEventListener(addNodeListener, Event.NODE_ADDED);
+        addEventListener(removeNodeListener, Event.NODE_REMOVED);
+        addEventListener(moveNodeListener, NODE_MOVED);
+        // move n2
+        superuser.move(n2.getPath(), n3.getPath() + "/" + nodeName2);
+        // remove n1
+        n1.remove();
+        testRootNode.save();
+        Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        removeEventListener(addNodeListener);
+        removeEventListener(removeNodeListener);
+        removeEventListener(moveNodeListener);
+        checkNodeAdded(added, new String[]{nodeName3 + "/" + nodeName2}, null);
+        checkNodeRemoved(removed, new String[]{nodeName1 + "/" + nodeName2, nodeName1}, null);
+        checkNodeMoved(moved, nodeName1 + "/" + nodeName2, nodeName3 + "/" + nodeName2);
+    }
+
+    /**
+     * TODO: move to base class once JSR 283 is final
+     * Checks <code>Events</code> for paths. All <code>relPaths</code> are
+     * relative to {@link #testRoot}.
+     *
+     * @param events the <code>Event</code>s.
+     * @param from   the source path where the node was moved from.
+     * @param to     the destination path where the node was moved to.
+     * @throws RepositoryException if an error occurs while retrieving the nodes
+     *                             from event instances.
+     */
+    protected void checkNodeMoved(Event[] events, String from, String to)
+            throws RepositoryException {
+        checkNodes(events, new String[]{to}, null, NODE_MOVED);
+        assertEquals("Wrong number of events", 1, events.length);
+        Map info = getInfo(events[0]);
+        checkInfoEntry(info, SRC_ABS_PATH, testRoot + "/" + from);
+        checkInfoEntry(info, DEST_ABS_PATH, testRoot + "/" + to);
+    }
+
+    /**
+     * TODO: move to base class once JSR 283 is final
+     * Checks if the info map contains the given <code>key</code> with the
+     * <code>expected</code> value.
+     *
+     * @param info the event info map.
+     * @param key the name of the key.
+     * @param expected the expected value.
+     */
+    protected void checkInfoEntry(Map info, String key, String expected) {
+        String value = (String) info.get(key);
+        assertNotNull("Missing event info key: " + key, value);
+        assertEquals("Wrong event info value for: " + key, expected, value);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeMovedTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeReorderTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeReorderTest.java?rev=765328&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeReorderTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeReorderTest.java Wed Apr 15 19:43:01 2009
@@ -0,0 +1,318 @@
+/*
+ * 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.api.jsr283.observation;
+
+import java.util.Map;
+
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.test.api.observation.EventResult;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.observation.Event;
+
+/**
+ * TODO: sync with NodeReorderTest in jackrabbit-jcr-tests once JSR 283 is final.
+ *
+ * Tests if {@link javax.jcr.Node#orderBefore(String, String)} operations trigger
+ * the appropriate observation events.
+ * <p/>
+ * @tck.config testroot must allow orderable child nodes of type
+ * <code>nodetype</code>, otherwise the test cases throw a
+ * {@link NotExecutableException}. Some tests are only executed if the node
+ * at <code>testroot</code> support same name sibling child nodes.
+ * @tck.config nodetype node type that allows child nodes of the same type.
+ * @tck.config nodename1 child node name of type <code>nodetype</code>
+ * @tck.config nodename2 child node name of type <code>nodetype</code>
+ * @tck.config nodename3 child node name of type <code>nodetype</code>
+ *
+ * @test
+ * @sources NodeReorderTest.java
+ * @executeClass org.apache.jackrabbit.test.api.observation.NodeReorderTest
+ * @keywords observation
+ */
+public class NodeReorderTest extends AbstractObservationTest {
+
+    /**
+     * The key <code>srcChildRelPath</code> in the info map.
+     */
+    private static final String SRC_CHILD_REL_PATH = "srcChildRelPath";
+
+    /**
+     * The key <code>destChildRelPath</code> in the info map.
+     */
+    private static final String DEST_CHILD_REL_PATH = "destChildRelPath";
+
+    /**
+     * Tests if reordering a child node triggers a {@link Event#NODE_REMOVED}
+     * and a {@link Event#NODE_ADDED} event.
+     */
+    public void testNodeReorder()
+            throws RepositoryException, NotExecutableException {
+        if (!testRootNode.getDefinition().getDeclaringNodeType().hasOrderableChildNodes()) {
+            throw new NotExecutableException("Node at '" + testRoot + "' does not support orderable child nodes.");
+        }
+
+        /**
+         * Initial tree:
+         *  + testroot
+         *      + nodename1
+         *      + nodename2
+         *      + nodename3
+         *
+         * After reorder:
+         *  + testroot
+         *      + nodename1
+         *      + nodename3
+         *      + nodename2
+         */
+        testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.addNode(nodeName2, testNodeType);
+        testRootNode.addNode(nodeName3, testNodeType);
+        testRootNode.save();
+        EventResult addNodeListener = new EventResult(log);
+        EventResult removeNodeListener = new EventResult(log);
+        EventResult moveNodeListener = new EventResult(log);
+        addEventListener(addNodeListener, Event.NODE_ADDED);
+        addEventListener(removeNodeListener, Event.NODE_REMOVED);
+        addEventListener(moveNodeListener, NODE_MOVED);
+        testRootNode.orderBefore(nodeName3, nodeName2);
+        testRootNode.save();
+        Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        removeEventListener(addNodeListener);
+        removeEventListener(removeNodeListener);
+        removeEventListener(moveNodeListener);
+        // either
+        // 1) nodename2 has been reordered to the end
+        // or:
+        // 2) nodename3 has been reordered before nodename2
+        // that is, the following event sets are correct:
+        // 1) nodename2:remove, nodename2:add
+        // or:
+        // 2) nodename3:remove, nodename3:add
+
+        // if true, check for option 1)
+        boolean reorderEnd = false;
+        for (int i = 0; i < added.length; i++) {
+            if (added[i].getPath().endsWith(nodeName2)) {
+                reorderEnd = true;
+                break;
+            }
+        }
+        if (reorderEnd) {
+            checkNodeAdded(added, new String[]{nodeName2}, null);
+            checkNodeRemoved(removed, new String[]{nodeName2}, null);
+            checkNodeReordered(moved, nodeName2, nodeName2, null);
+        } else {
+            checkNodeAdded(added, new String[]{nodeName3}, null);
+            checkNodeRemoved(removed, new String[]{nodeName3}, null);
+            checkNodeReordered(moved, nodeName3, nodeName3, nodeName2);
+        }
+    }
+
+    /**
+     * Tests if reordering a child node triggers a {@link Event#NODE_REMOVED}
+     * and a {@link Event#NODE_ADDED} event with same name siblings.
+     */
+    public void testNodeReorderSameName()
+            throws RepositoryException, NotExecutableException {
+        if (!testRootNode.getDefinition().getDeclaringNodeType().hasOrderableChildNodes()) {
+            throw new NotExecutableException("Node at '" + testRoot + "' does not support orderable child nodes.");
+        }
+
+        /**
+         * Initial tree:
+         *  + testroot
+         *      + nodename1[1]
+         *      + nodename1[2]
+         *      + nodename1[3]
+         *
+         * After reorder:
+         *  + testroot
+         *      + nodename1[1]
+         *      + nodename1[2] (was 3)
+         *      + nodename1[3] (was 2)
+         */
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        if (!n.getDefinition().allowsSameNameSiblings()) {
+            throw new NotExecutableException("Node at " + testRoot + " does not allow same name siblings with name " + nodeName1);
+        }
+        testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.save();
+        EventResult addNodeListener = new EventResult(log);
+        EventResult removeNodeListener = new EventResult(log);
+        EventResult moveNodeListener = new EventResult(log);
+        addEventListener(addNodeListener, Event.NODE_ADDED);
+        addEventListener(removeNodeListener, Event.NODE_REMOVED);
+        addEventListener(moveNodeListener, NODE_MOVED);
+        testRootNode.orderBefore(nodeName1 + "[3]", nodeName1 + "[2]");
+        //testRootNode.orderBefore(nodeName1 + "[2]", null);
+        testRootNode.save();
+        Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        removeEventListener(addNodeListener);
+        removeEventListener(removeNodeListener);
+        removeEventListener(moveNodeListener);
+        // either
+        // 1) nodename1[2] has been reordered to the end
+        // or:
+        // 2) nodename1[3] has been reordered before nodename1[2]
+        // that is, the following event sets are correct:
+        // 1) nodename1[2]:remove, nodename1[3]:add
+        // or:
+        // 2) nodename1[3]:remove, nodename1[2]:add
+
+        // if true, check for option 1)
+        boolean reorderEnd = false;
+        for (int i = 0; i < added.length; i++) {
+            if (added[i].getPath().endsWith(nodeName1 + "[3]")) {
+                reorderEnd = true;
+                break;
+            }
+        }
+        if (reorderEnd) {
+            checkNodeAdded(added, new String[]{nodeName1 + "[3]"}, null);
+            checkNodeRemoved(removed, new String[]{nodeName1 + "[2]"}, null);
+            checkNodeReordered(moved, nodeName1 + "[2]", nodeName1 + "[3]", null);
+        } else {
+            checkNodeAdded(added, new String[]{nodeName1 + "[2]"}, null);
+            checkNodeRemoved(removed, new String[]{nodeName1 + "[3]"}, null);
+            checkNodeReordered(moved, nodeName1 + "[3]", nodeName1 + "[2]", nodeName1 + "[2]");
+        }
+    }
+
+    /**
+     * Tests if reordering a child node triggers a {@link Event#NODE_REMOVED}
+     * and a {@link Event#NODE_ADDED} event with same name siblings. Furthermore
+     * a node is removed in the same save scope.
+     */
+    public void testNodeReorderSameNameWithRemove()
+            throws RepositoryException, NotExecutableException {
+        if (!testRootNode.getDefinition().getDeclaringNodeType().hasOrderableChildNodes()) {
+            throw new NotExecutableException("Node at '" + testRoot + "' does not support orderable child nodes.");
+        }
+
+        /**
+         * Initial tree:
+         *  + testroot
+         *      + nodename1[1]
+         *      + nodename2
+         *      + nodename1[2]
+         *      + nodename1[3]
+         *      + nodename3
+         *
+         * After reorder:
+         *  + testroot
+         *      + nodename1[1]
+         *      + nodename2
+         *      + nodename1[2] (was 3)
+         *      + nodename1[3] (was 2)
+         */
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        if (!n.getDefinition().allowsSameNameSiblings()) {
+            throw new NotExecutableException("Node at " + testRoot + " does not allow same name siblings with name " + nodeName1);
+        }
+        testRootNode.addNode(nodeName2, testNodeType);
+        testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.addNode(nodeName3, testNodeType);
+        testRootNode.save();
+        EventResult addNodeListener = new EventResult(log);
+        EventResult removeNodeListener = new EventResult(log);
+        EventResult moveNodeListener = new EventResult(log);
+        addEventListener(addNodeListener, Event.NODE_ADDED);
+        addEventListener(removeNodeListener, Event.NODE_REMOVED);
+        addEventListener(moveNodeListener, NODE_MOVED);
+        testRootNode.orderBefore(nodeName1 + "[2]", null);
+        testRootNode.getNode(nodeName3).remove();
+        testRootNode.save();
+        Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        removeEventListener(addNodeListener);
+        removeEventListener(removeNodeListener);
+        removeEventListener(moveNodeListener);
+        // either
+        // 1) nodename1[2] has been reordered to the end
+        // or:
+        // 2) nodename1[3] has been reordered before nodename1[2]
+        //
+        // that is, the following event sets are correct:
+        // 1) nodename1[2]:remove, nodename1[3]:add, nodename3:remove
+        // or:
+        // 2) nodename1[3]:remove, nodename1[2]:add, nodename3:remove
+
+        // if true, check for option 1)
+        boolean reorderEnd = false;
+        for (int i = 0; i < added.length; i++) {
+            if (added[i].getPath().endsWith(nodeName1 + "[3]")) {
+                reorderEnd = true;
+                break;
+            }
+        }
+        if (reorderEnd) {
+            checkNodeAdded(added, new String[]{nodeName1 + "[3]"}, null);
+            checkNodeRemoved(removed, new String[]{nodeName1 + "[2]", nodeName3}, null);
+            checkNodeReordered(moved, nodeName1 + "[2]", nodeName1 + "[3]", null);
+        } else {
+            checkNodeAdded(added, new String[]{nodeName1 + "[2]"}, null);
+            checkNodeRemoved(removed, new String[]{nodeName1 + "[3]", nodeName3}, null);
+            checkNodeReordered(moved, nodeName1 + "[3]", nodeName1 + "[2]", nodeName1 + "[2]");
+        }
+    }
+
+    /**
+     * TODO: move to base class once JSR 283 is final
+     * Checks <code>Events</code> for paths. All <code>relPaths</code> are
+     * relative to {@link #testRoot}.
+     *
+     * @param events the <code>Event</code>s.
+     * @param src    the source child path where the node was reordered from.
+     * @param dest   the destination child path where the node was reordered to.
+     * @param before the destination child path where the node was reordered before.
+     * @throws RepositoryException if an error occurs while retrieving the nodes
+     *                             from event instances.
+     */
+    protected void checkNodeReordered(Event[] events, String src,
+                                      String dest, String before)
+            throws RepositoryException {
+        checkNodes(events, new String[]{dest}, null, NODE_MOVED);
+        assertEquals("Wrong number of events", 1, events.length);
+        Map info = getInfo(events[0]);
+        checkInfoEntry(info, SRC_CHILD_REL_PATH, src);
+        checkInfoEntry(info, DEST_CHILD_REL_PATH, before);
+    }
+
+    /**
+     * TODO: move to base class once JSR 283 is final
+     * Checks if the info map contains the given <code>key</code> with the
+     * <code>expected</code> value.
+     *
+     * @param info the event info map.
+     * @param key the name of the key.
+     * @param expected the expected value.
+     */
+    protected void checkInfoEntry(Map info, String key, String expected) {
+        assertTrue("Missing event info key: " + key, info.containsKey(key));
+        assertEquals("Wrong event info value for: " + key,
+                expected, (String) info.get(key));
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/observation/NodeReorderTest.java
------------------------------------------------------------------------------
    svn:eol-style = native