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 2006/10/27 16:16:15 UTC

svn commit: r468388 [1/2] - in /jackrabbit/trunk/contrib/spi: jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/ jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ jcr2spi/src/ma...

Author: mreutegg
Date: Fri Oct 27 07:16:13 2006
New Revision: 468388

URL: http://svn.apache.org/viewvc?view=rev&rev=468388
Log:
- replace all EventIterator return values in RepositoryService with EventBundle[]
- remove RepositoryService.addEventListener()/removeEventListener() and introduce getEvents() instead
- added quick description of new design: see org/apache/jackrabbit/spi/package.html
- update spi/project.properties with correct links to external javadoc
- fix package in spi/project.xml

Added:
    jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/EventBundle.java   (with props)
    jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/package.html   (with props)
    jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventBundleImpl.java   (with props)
Removed:
    jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/EventListener.java
    jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/SubscriptionManager.java
Modified:
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateManager.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionManagerImpl.java
    jackrabbit/trunk/contrib/spi/spi/project.properties
    jackrabbit/trunk/contrib/spi/spi/project.xml
    jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java
    jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java
    jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventIteratorImpl.java
    jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java
    jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/SessionInfoImpl.java

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java Fri Oct 27 07:16:13 2006
@@ -56,25 +56,22 @@
 import org.apache.jackrabbit.jcr2spi.operation.RemoveLabel;
 import org.apache.jackrabbit.jcr2spi.security.AccessManager;
 import org.apache.jackrabbit.jcr2spi.observation.InternalEventListener;
-import org.apache.jackrabbit.util.IteratorHelper;
 import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.MalformedPathException;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.NodeId;
-import org.apache.jackrabbit.spi.EventListener;
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.LockInfo;
 import org.apache.jackrabbit.spi.QueryInfo;
 import org.apache.jackrabbit.spi.QNodeDefinition;
 import org.apache.jackrabbit.spi.QNodeTypeDefinitionIterator;
-import org.apache.jackrabbit.spi.EventIterator;
-import org.apache.jackrabbit.spi.Event;
 import org.apache.jackrabbit.spi.ItemId;
 import org.apache.jackrabbit.spi.QNodeTypeDefinition;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.Batch;
+import org.apache.jackrabbit.spi.EventBundle;
 import org.apache.jackrabbit.value.QValue;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
@@ -114,6 +111,11 @@
 
     private static Logger log = LoggerFactory.getLogger(WorkspaceManager.class);
 
+    /**
+     * TODO: make configurable
+     */
+    private static final int EXTERNAL_EVENT_POLLING_INTERVAL = 3 * 1000;
+
     private final RepositoryService service;
     private final SessionInfo sessionInfo;
 
@@ -123,11 +125,17 @@
     private final NodeTypeRegistry ntRegistry;
 
     /**
-     * This is the event listener that listens on the repository service
-     * for external changes. If <code>null</code> then the underlying repository
-     * service does not support observation.
+     * Monitor object to synchronize the external feed thread with client
+     * threads that call {@link #execute(Operation)} or {@link
+     * #execute(ChangeLog)}.
+     */
+    private final Object updateMonitor = new Object();
+
+    /**
+     * This is the event polling for external changes. If <code>null</code>
+     * then the underlying repository service does not support observation.
      */
-    private final EventListener externalChangeListener;
+    private final Thread externalChangeFeed;
 
     /**
      * List of event listener that are set on this WorkspaceManager to get
@@ -145,7 +153,7 @@
 
         nsRegistry = createNamespaceRegistry(repositoryDescriptors);
         ntRegistry = createNodeTypeRegistry(nsRegistry, repositoryDescriptors);
-        externalChangeListener = createChangeListener(repositoryDescriptors);
+        externalChangeFeed = createChangeFeed(repositoryDescriptors, EXTERNAL_EVENT_POLLING_INTERVAL);
     }
 
     public NamespaceRegistryImpl getNamespaceRegistryImpl() {
@@ -278,30 +286,24 @@
     }
 
     /**
-     * Creates and registers an EventListener on the RepositoryService that
-     * listens for external changes.
+     * Creates a background thread which polls for external changes on the
+     * RepositoryService.
      *
-     * @param descriptors the repository descriptors
-     * @return the listener or <code>null</code> if the underlying
+     * @param descriptors the repository descriptors.
+     * @param pollingInterval the polling interval in milliseconds.
+     * @return the background polling thread or <code>null</code> if the underlying
      *         <code>RepositoryService</code> does not support observation.
-     * @throws RepositoryException if an error occurs while registering the
-     *                             event listener.
      */
-    private EventListener createChangeListener(Properties descriptors) throws RepositoryException {
+    private Thread createChangeFeed(Properties descriptors, int pollingInterval) {
         String desc = descriptors.getProperty(Repository.OPTION_OBSERVATION_SUPPORTED);
-        EventListener l = null;
+        Thread t = null;
         if (Boolean.valueOf(desc).booleanValue()) {
-            l = new EventListener() {
-                public void onEvent(EventIterator events) {
-                    // external change
-                    onEventReceived(events, false, null);
-                }
-            };
-            // register for all non-local events
-            service.addEventListener(sessionInfo, service.getRootId(sessionInfo),
-                l, Event.ALL_TYPES, true, null, null);
+            t = new Thread(new ExternalChangePolling(pollingInterval));
+            t.setName("External Change Polling");
+            t.setDaemon(true);
+            t.start();
         }
-        return l;
+        return t;
     }
 
     //---------------------------------------------------< ItemStateManager >---
@@ -366,7 +368,9 @@
      * @see UpdatableItemStateManager#execute(Operation)
      */
     public void execute(Operation operation) throws RepositoryException {
-        new OperationVisitorImpl(sessionInfo).execute(operation);
+        synchronized (updateMonitor) {
+            new OperationVisitorImpl(sessionInfo).execute(operation);
+        }
     }
 
     /**
@@ -376,15 +380,18 @@
      * @throws RepositoryException
      */
     public void execute(ChangeLog changes) throws RepositoryException {
-        new OperationVisitorImpl(sessionInfo).execute(changes);
+        synchronized (updateMonitor) {
+            new OperationVisitorImpl(sessionInfo).execute(changes);
+        }
     }
 
     public void dispose() {
-        if (externalChangeListener != null) {
+        if (externalChangeFeed != null) {
+            externalChangeFeed.interrupt();
             try {
-                service.removeEventListener(sessionInfo, service.getRootId(sessionInfo), externalChangeListener);
-            } catch (RepositoryException e) {
-                log.warn("Exception while disposing workspace manager: " + e);
+                externalChangeFeed.join();
+            } catch (InterruptedException e) {
+                log.warn("Interrupted while waiting for external change thread to terminate.");
             }
         }
         try {
@@ -537,6 +544,7 @@
      * </ul>
      *
      * @param events    the events generated by the repository service as a
+     *                  response to the executed operation(s).
      * @param changeLog the local <code>ChangeLog</code> which contains the
      *                  affected transient <code>ItemState</code>s and the
      *                  relevant {@link Operation}s that lead to the
@@ -545,27 +553,21 @@
      *                  of a workspace operation. In that case there are no
      *                  local transient changes.
      */
-    private void onEventReceived(EventIterator events, boolean isLocal,
-                                 ChangeLog changeLog) {
+    private void onEventReceived(EventBundle[] events, ChangeLog changeLog) {
         // notify listener
-        // need to copy events into a list because we notify multiple listeners
-        List eventList = new ArrayList();
-        while (events.hasNext()) {
-            Event e = events.nextEvent();
-            eventList.add(e);
-        }
-        if (eventList.isEmpty()) {
-            return;
-        }
-
         InternalEventListener[] lstnrs = (InternalEventListener[]) listeners.toArray(new InternalEventListener[listeners.size()]);
-        if (changeLog == null) {
-            for (int i = 0; i < lstnrs.length; i++) {
-                lstnrs[i].onEvent(new EventIteratorImpl(eventList), isLocal);
-            }
-        } else {
-            for (int i = 0; i < lstnrs.length; i++) {
-                lstnrs[i].onEvent(new EventIteratorImpl(eventList), changeLog);
+        for (int i = 0; i < events.length; i++) {
+            EventBundle bundle = events[i];
+            if (bundle.isLocal() && changeLog != null) {
+                // local change from batch operation
+                for (int j = 0; j < lstnrs.length; j++) {
+                    lstnrs[j].onEvent(bundle, changeLog);
+                }
+            } else {
+                // external change or workspace operation
+                for (int j = 0; j < lstnrs.length; j++) {
+                    lstnrs[j].onEvent(bundle);
+                }
             }
         }
     }
@@ -582,7 +584,7 @@
         private final SessionInfo sessionInfo;
 
         private Batch batch;
-        private EventIterator events;
+        private EventBundle[] events;
 
         private OperationVisitorImpl(SessionInfo sessionInfo) {
             this.sessionInfo = sessionInfo;
@@ -604,7 +606,7 @@
             } finally {
                 if (batch != null) {
                     events = service.submit(batch);
-                    onEventReceived(events, true, changeLog);
+                    onEventReceived(events, changeLog);
                     // reset batch field
                     batch = null;
                 }
@@ -625,7 +627,7 @@
                     // a workspace operation is like an external change: there
                     // is no changelog to persist. but still the events must
                     // be reported as local changes.
-                    onEventReceived(events, true, null);
+                    onEventReceived(events, null);
                 }
             }
         }
@@ -782,8 +784,20 @@
         public void visit(Merge operation) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException {
             NodeId nId = operation.getNodeState().getNodeId();
             events = service.merge(sessionInfo, nId, operation.getSourceWorkspaceName(), operation.bestEffort());
-            // todo: improve.... inform operation about modified items (build mergefailed iterator)
-            operation.getEventListener().onEvent(events, true);
+            List externalEventBundles = new ArrayList();
+            for (int i = 0; i < events.length; i++) {
+                if (events[i].isLocal()) {
+                    // todo: improve.... inform operation about modified items (build mergefailed iterator)
+                    operation.getEventListener().onEvent(events[i]);
+                } else {
+                    // otherwise dispatch as external event
+                    externalEventBundles.add(events[i]);
+                }
+            }
+            if (!externalEventBundles.isEmpty()) {
+                EventBundle[] bundles = (EventBundle[]) externalEventBundles.toArray(new EventBundle[externalEventBundles.size()]);
+                onEventReceived(bundles, null);
+            }
         }
 
         public void visit(ResolveMergeConflict operation) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
@@ -859,14 +873,45 @@
         }
     }
 
-    private static final class EventIteratorImpl extends IteratorHelper implements EventIterator {
+    /**
+     * Implements the polling for external changes on the repository service.
+     */
+    private final class ExternalChangePolling implements Runnable {
 
-        public EventIteratorImpl(Collection c) {
-            super(c);
+        /**
+         * The polling interval in milliseconds.
+         */
+        private final int pollingInterval;
+
+        /**
+         * Creates a new external change polling with a given polling interval.
+         *
+         * @param pollingInterval the interval in milliseconds.
+         */
+        private ExternalChangePolling(int pollingInterval) {
+            this.pollingInterval = pollingInterval;
         }
 
-        public Event nextEvent() {
-            return (Event) next();
+        public void run() {
+            while (!Thread.interrupted()) {
+                try {
+                    Thread.sleep(pollingInterval);
+                } catch (InterruptedException e) {
+                    // terminate
+                    break;
+                }
+                try {
+                    synchronized (updateMonitor) {
+                        EventBundle[] bundles = service.getEvents(sessionInfo, 0);
+                        if (bundles.length > 0) {
+                            onEventReceived(bundles, null);
+                        }
+                    }
+                } catch (RepositoryException e) {
+                    log.warn("Exception while retrieving event bundles: " + e);
+                    log.debug("Dump:", e);
+                }
+            }
         }
     }
 }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java Fri Oct 27 07:16:13 2006
@@ -31,6 +31,7 @@
 import org.apache.jackrabbit.spi.EventIterator;
 import org.apache.jackrabbit.spi.Event;
 import org.apache.jackrabbit.spi.LockInfo;
+import org.apache.jackrabbit.spi.EventBundle;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 
@@ -261,7 +262,7 @@
         // remove any session scoped locks:
         NodeState[] lhStates = (NodeState[]) lockMap.keySet().toArray(new NodeState[lockMap.size()]);
         for (int i = 0; i < lhStates.length; i++) {
-            NodeState nState = (NodeState) lhStates[i];
+            NodeState nState = lhStates[i];
             LockImpl l = (LockImpl) lockMap.get(nState);
             if (l.isSessionScoped()) {
                 try {
@@ -531,7 +532,7 @@
         }
 
         //------------------------------------------< InternalEventListener >---
-        public void onEvent(EventIterator events, boolean isLocal) {
+        public void onEvent(EventBundle events) {
             if (!isLive) {
                 // since we only monitor the removal of the lock (by means
                 // of deletion of the jcr:lockIsDeep property, we are not interested
@@ -539,8 +540,8 @@
                 return;
             }
 
-            while (events.hasNext()) {
-                Event ev = events.nextEvent();
+            for (EventIterator it = events.getEvents(); it.hasNext(); ) {
+                Event ev = it.nextEvent();
                 // if the jcr:lockIsDeep property related to this Lock got removed,
                 // we assume that the lock has been released.
                 // TODO: not correct to compare nodeIds
@@ -557,7 +558,7 @@
             }
         }
 
-        public void onEvent(EventIterator events, ChangeLog changeLog) {
+        public void onEvent(EventBundle events, ChangeLog changeLog) {
             // nothing to do. not interested in transient modifications
         }
     }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java Fri Oct 27 07:16:13 2006
@@ -18,6 +18,7 @@
 
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
+import org.apache.jackrabbit.spi.EventBundle;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.observation.Event;
@@ -25,7 +26,6 @@
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
-import java.util.Collection;
 
 /**
  */
@@ -65,17 +65,15 @@
     /**
      * Creates a new <code>FilteredEventIterator</code>.
      *
-     * @param c      an unmodifiable Collection of {@link org.apache.jackrabbit.spi.Event}s.
+     * @param events the {@link org.apache.jackrabbit.spi.Event}s as a bundle.
      * @param filter only event that pass the filter will be dispatched to the
      *               event listener.
-     * @param isLocal if <code>true</code> these are local events.
      */
-    public FilteredEventIterator(Collection c,
-                                 EventFilter filter,
-                                 boolean isLocal) {
-        actualEvents = c.iterator();
+    public FilteredEventIterator(EventBundle events,
+                                 EventFilter filter) {
+        actualEvents = events.getEvents();
         this.filter = filter;
-        this.isLocal = isLocal;
+        this.isLocal = events.isLocal();
         fetchNext();
     }
 

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java Fri Oct 27 07:16:13 2006
@@ -16,13 +16,12 @@
  */
 package org.apache.jackrabbit.jcr2spi.observation;
 
-import org.apache.jackrabbit.spi.EventIterator;
+import org.apache.jackrabbit.spi.EventBundle;
 import org.apache.jackrabbit.jcr2spi.state.ChangeLog;
 
 /**
- * <code>InternalEventListener</code> is similar to {@link org.apache.jackrabbit.spi.EventListener}
- * but adds information about the location of the events. Whether they are local
- * or external.
+ * <code>InternalEventListener</code> receives changes as a result of a local
+ * or an external modification.
  */
 public interface InternalEventListener {
 
@@ -30,9 +29,8 @@
      * Gets called when an event occurs.
      *
      * @param events the event set received.
-     * @param isLocal <code>true</code> if these are local changes.
      */
-    public void onEvent(EventIterator events, boolean isLocal);
+    public void onEvent(EventBundle events);
 
-    public void onEvent(EventIterator events, ChangeLog changeLog);
+    public void onEvent(EventBundle events, ChangeLog changeLog);
 }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java Fri Oct 27 07:16:13 2006
@@ -25,7 +25,7 @@
 import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.name.PathFormat;
 import org.apache.jackrabbit.name.QName;
-import org.apache.jackrabbit.spi.EventIterator;
+import org.apache.jackrabbit.spi.EventBundle;
 import org.apache.jackrabbit.util.IteratorHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,12 +34,9 @@
 import javax.jcr.observation.EventListener;
 import javax.jcr.observation.EventListenerIterator;
 import javax.jcr.observation.ObservationManager;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -152,16 +149,7 @@
 
     //-----------------------< InternalEventListener >--------------------------
 
-    public void onEvent(EventIterator events, boolean isLocal) {
-        List eventList = new ArrayList();
-        while (events.hasNext()) {
-            eventList.add(events.nextEvent());
-        }
-        if (eventList.isEmpty()) {
-            return;
-        }
-        eventList = Collections.unmodifiableList(eventList);
-
+    public void onEvent(EventBundle events) {
         // get active listeners
         Map activeListeners;
         synchronized (subscriptions) {
@@ -171,7 +159,7 @@
         for (Iterator it = activeListeners.keySet().iterator(); it.hasNext(); ) {
             EventListener listener = (EventListener) it.next();
             EventFilter filter = (EventFilter) activeListeners.get(listener);
-            FilteredEventIterator eventIter = new FilteredEventIterator(eventList, filter, isLocal);
+            FilteredEventIterator eventIter = new FilteredEventIterator(events, filter);
             if (eventIter.hasNext()) {
                 try {
                     listener.onEvent(eventIter);
@@ -185,14 +173,14 @@
     }
 
     /**
-     * Same as {@link #onEvent(EventIterator, boolean)} with the boolean flag
-     * set to <code>true</code>.
+     * Same as {@link #onEvent(EventBundle)} but only used for local changes
+     * with a <code>ChangeLog</code>.
      * 
      * @param events
      * @param changeLog
      */
-    public void onEvent(EventIterator events, ChangeLog changeLog) {
-        onEvent(events, true);
+    public void onEvent(EventBundle events, ChangeLog changeLog) {
+        onEvent(events);
     }
 
     //-------------------------< internal >-------------------------------------

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateManager.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateManager.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateManager.java Fri Oct 27 07:16:13 2006
@@ -20,6 +20,7 @@
 import org.apache.jackrabbit.spi.EventIterator;
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.Event;
+import org.apache.jackrabbit.spi.EventBundle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,10 +52,9 @@
      * might have invoked changes (autocreated items etc.).
      *
      * @param events
-     * @param isLocal
      */
-    public void onEvent(EventIterator events, boolean isLocal) {
-        pushEvents(getEventCollection(events));
+    public void onEvent(EventBundle events) {
+        pushEvents(getEventCollection(events.getEvents()));
     }
 
     /**
@@ -62,11 +62,11 @@
      * @param events
      * @param changeLog
      */
-    public void onEvent(EventIterator events, ChangeLog changeLog) {
+    public void onEvent(EventBundle events, ChangeLog changeLog) {
         if (changeLog == null) {
             throw new IllegalArgumentException("ChangeLog must not be null.");
         }
-        Collection evs = getEventCollection(events);
+        Collection evs = getEventCollection(events.getEvents());
         // TODO: make sure, that events only contain events related to the modifications submitted with the changelog.
 
         // inform the changelog target state about the transient modifications

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionManagerImpl.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionManagerImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionManagerImpl.java Fri Oct 27 07:16:13 2006
@@ -42,6 +42,7 @@
 import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.spi.EventIterator;
 import org.apache.jackrabbit.spi.Event;
+import org.apache.jackrabbit.spi.EventBundle;
 
 import java.util.Collection;
 import java.util.List;
@@ -158,17 +159,18 @@
         // TODO find better solution to build the mergeFailed-collection
         final List failedIds = new ArrayList();
         InternalEventListener mergeFailedCollector = new InternalEventListener() {
-            public void onEvent(EventIterator events, boolean isLocal) {
-                if (isLocal) {
-                    while (events.hasNext()) {
-                        Event ev = events.nextEvent();
+            public void onEvent(EventBundle events) {
+                if (events.isLocal()) {
+                    EventIterator it = events.getEvents();
+                    while (it.hasNext()) {
+                        Event ev = it.nextEvent();
                         if (ev.getType() == Event.PROPERTY_ADDED && QName.JCR_MERGEFAILED.equals(ev.getQPath().getNameElement().getName())) {
                             failedIds.add(ev.getParentId());
                         }
                     }
                 }
             }
-            public void onEvent(EventIterator events, ChangeLog changeLog) {
+            public void onEvent(EventBundle events, ChangeLog changeLog) {
                 // nothing to do. we are not interested in transient modifications
             }
         };

Modified: jackrabbit/trunk/contrib/spi/spi/project.properties
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi/project.properties?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi/project.properties (original)
+++ jackrabbit/trunk/contrib/spi/spi/project.properties Fri Oct 27 07:16:13 2006
@@ -1,2 +1,2 @@
-maven.javadoc.links=http://java.sun.com/j2se/1.4.2/docs/api/,http://www.day.com/maven/jsr170/javadocs/jcr-0.16.1-pfd/
+maven.javadoc.links=http://java.sun.com/j2se/1.4.2/docs/api/,http://www.day.com/maven/jsr170/javadocs/jcr-1.0/
 maven.repo.remote = http://www.ibiblio.org/maven/,http://www.day.com/maven/

Modified: jackrabbit/trunk/contrib/spi/spi/project.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi/project.xml?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi/project.xml (original)
+++ jackrabbit/trunk/contrib/spi/spi/project.xml Fri Oct 27 07:16:13 2006
@@ -22,7 +22,7 @@
     <extend>${basedir}/../project.xml</extend>
     <artifactId>jackrabbit-spi</artifactId>
     <name>Service Provider Interface (SPI)</name>
-    <package>org.apache.jackrabbit.spi.*</package>
+    <package>org.apache.jackrabbit.spi</package>
 
     <!-- ====================================================================== -->
     <!-- D E P E N D E N C I E S                                                -->

Added: jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/EventBundle.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/EventBundle.java?view=auto&rev=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/EventBundle.java (added)
+++ jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/EventBundle.java Fri Oct 27 07:16:13 2006
@@ -0,0 +1,51 @@
+/*
+ * 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.spi;
+
+/**
+ * An <code>EventBundle</code> is similar to the
+ * {@link javax.jcr.observation.EventIterator} interface. Other than the
+ * <code>EventIterator</code> an <code>EventBundle</code> allows to retrieve
+ * the events multiple times using the {@link #getEvents} method.
+ */
+public interface EventBundle {
+
+    /**
+     * Returns the events of this bundle.
+     *
+     * @return the events of this bundle.
+     */
+    public EventIterator getEvents();
+
+    /**
+     * Returns the identifier for this <code>EventBundle</code>.
+     *
+     * @return the identifier for this <code>EventBundle</code>.
+     */
+    public String getBundleId();
+
+    /**
+     * Returns <code>true</code> if this event bundle is associated with a
+     * change that was initiated by a local session info. Event bundles for
+     * external changes will aways return <code>false</code>.
+     *
+     * @return <code>true</code> if this event bundle is associated with a local
+     *         change, <code>false</code> if this event bundle contains external
+     *         changes.
+     */
+    public boolean isLocal();
+}

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

Modified: jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java (original)
+++ jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java Fri Oct 27 07:16:13 2006
@@ -212,7 +212,7 @@
      * Completes the this Batch or discard all the previous modifications.
      *
      * @param batch
-     * @return EventIterator
+     * @return EventBundle
      * @throws PathNotFoundException
      * @throws ItemNotFoundException
      * @throws NoSuchNodeTypeException
@@ -224,7 +224,7 @@
      * @throws UnsupportedRepositoryOperationException
      * @throws RepositoryException
      */
-    public EventIterator submit(Batch batch) throws PathNotFoundException, ItemNotFoundException, NoSuchNodeTypeException, ValueFormatException, VersionException, LockException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException;
+    public EventBundle[] submit(Batch batch) throws PathNotFoundException, ItemNotFoundException, NoSuchNodeTypeException, ValueFormatException, VersionException, LockException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException;
 
     //-------------------------------------------------------------< Import >---
     /**
@@ -244,7 +244,7 @@
      * @throws RepositoryException
      * @see javax.jcr.Workspace#importXML(String, java.io.InputStream, int)
      */
-    public EventIterator importXml(SessionInfo sessionInfo, NodeId parentId, InputStream xmlStream, int uuidBehaviour) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException;
+    public EventBundle[] importXml(SessionInfo sessionInfo, NodeId parentId, InputStream xmlStream, int uuidBehaviour) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException;
 
     //---------------------------------------------------------< Copy, Move >---
     /**
@@ -262,7 +262,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Workspace#move(String, String)
      */
-    public EventIterator move(SessionInfo sessionInfo, NodeId srcNodeId, NodeId destParentNodeId, QName destName) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException;
+    public EventBundle[] move(SessionInfo sessionInfo, NodeId srcNodeId, NodeId destParentNodeId, QName destName) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -282,7 +282,7 @@
      * @see javax.jcr.Workspace#copy(String, String)
      * @see javax.jcr.Workspace#copy(String, String, String)
      */
-    public EventIterator copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, QName destName) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, UnsupportedRepositoryOperationException, RepositoryException;
+    public EventBundle[] copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, QName destName) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, UnsupportedRepositoryOperationException, RepositoryException;
 
     //------------------------------------------------------< Update, Clone >---
     /**
@@ -296,7 +296,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Node#update(String)
      */
-    public EventIterator update(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException;
+    public EventBundle[] update(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -316,7 +316,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Workspace#clone(String, String, String, boolean)
      */
-    public EventIterator clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, QName destName, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, UnsupportedRepositoryOperationException, RepositoryException;
+    public EventBundle[] clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, QName destName, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, UnsupportedRepositoryOperationException, RepositoryException;
 
     //------------------------------------------------------------< Locking >---
 
@@ -344,7 +344,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Node#lock(boolean, boolean)
      */
-    public EventIterator lock(SessionInfo sessionInfo, NodeId nodeId, boolean deep, boolean sessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException;
+    public EventBundle[] lock(SessionInfo sessionInfo, NodeId nodeId, boolean deep, boolean sessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException;
 
     /**
      * Explicit refresh of an existing lock. Existing locks should be refreshed
@@ -356,7 +356,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.lock.Lock#refresh()
      */
-    public EventIterator refreshLock(SessionInfo sessionInfo, NodeId nodeId) throws LockException, RepositoryException;
+    public EventBundle[] refreshLock(SessionInfo sessionInfo, NodeId nodeId) throws LockException, RepositoryException;
 
     /**
      * Releases the lock on the given node.<p/>
@@ -372,7 +372,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Node#unlock()
      */
-    public EventIterator unlock(SessionInfo sessionInfo, NodeId nodeId) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException;
+    public EventBundle[] unlock(SessionInfo sessionInfo, NodeId nodeId) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException;
 
     //---------------------------------------------------------< Versioning >---
     /**
@@ -385,7 +385,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Node#checkin()
      */
-    public EventIterator checkin(SessionInfo sessionInfo, NodeId nodeId) throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException;
+    public EventBundle[] checkin(SessionInfo sessionInfo, NodeId nodeId) throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -395,7 +395,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Node#checkout()
      */
-    public EventIterator checkout(SessionInfo sessionInfo, NodeId nodeId) throws UnsupportedRepositoryOperationException, LockException, RepositoryException;
+    public EventBundle[] checkout(SessionInfo sessionInfo, NodeId nodeId) throws UnsupportedRepositoryOperationException, LockException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -414,7 +414,7 @@
      * @see javax.jcr.Node#restore(javax.jcr.version.Version, String, boolean)
      * @see javax.jcr.Node#restoreByLabel(String, boolean)
      */
-    public EventIterator restore(SessionInfo sessionInfo, NodeId nodeId, NodeId versionId, boolean removeExisting) throws VersionException, PathNotFoundException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException;
+    public EventBundle[] restore(SessionInfo sessionInfo, NodeId nodeId, NodeId versionId, boolean removeExisting) throws VersionException, PathNotFoundException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -428,7 +428,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Workspace#restore(javax.jcr.version.Version[], boolean)
      */
-    public EventIterator restore(SessionInfo sessionInfo, NodeId[] versionIds, boolean removeExisting) throws ItemExistsException, UnsupportedRepositoryOperationException, VersionException, LockException, InvalidItemStateException, RepositoryException;
+    public EventBundle[] restore(SessionInfo sessionInfo, NodeId[] versionIds, boolean removeExisting) throws ItemExistsException, UnsupportedRepositoryOperationException, VersionException, LockException, InvalidItemStateException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -443,7 +443,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Node#merge(String, boolean)
      */
-    public EventIterator merge(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName, boolean bestEffort) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException;
+    public EventBundle[] merge(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName, boolean bestEffort) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -461,7 +461,7 @@
      * @see javax.jcr.Node#cancelMerge(javax.jcr.version.Version)
      * @see javax.jcr.Node#doneMerge(javax.jcr.version.Version)
      */
-    public EventIterator resolveMergeConflict(SessionInfo sessionInfo, NodeId nodeId, NodeId[] mergeFailedIds, NodeId[] predecessorIds) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException;
+    public EventBundle[] resolveMergeConflict(SessionInfo sessionInfo, NodeId nodeId, NodeId[] mergeFailedIds, NodeId[] predecessorIds) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -472,7 +472,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.version.VersionHistory#addVersionLabel(String, String, boolean)
      */
-    public EventIterator addVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, NodeId versionId, QName label, boolean moveLabel) throws VersionException, RepositoryException;
+    public EventBundle[] addVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, NodeId versionId, QName label, boolean moveLabel) throws VersionException, RepositoryException;
 
     /**
      * @param sessionInfo
@@ -483,7 +483,7 @@
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.version.VersionHistory#removeVersionLabel(String)
      */
-    public EventIterator removeVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, NodeId versionId, QName label) throws VersionException, RepositoryException;
+    public EventBundle[] removeVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, NodeId versionId, QName label) throws VersionException, RepositoryException;
 
     //----------------------------------------------------------< Searching >---
     /**
@@ -507,41 +507,31 @@
     //--------------------------------------------------------< Observation >---
 
     /**
-     * Registers a listener to receive events about changes that were applied
-     * by other sessions. In contrast to {@link javax.jcr.observation.ObservationManager#addEventListener)}
-     * this method does not have a <code>noLocal</code> flag.
-     * </p>
-     * The implementation must ensure that {@link EventIterator}s issued to
-     * potential listeners and the ones returned by the individual methods
-     * are in a proper sequence.
+     * Retrieves the external events that occurred since the last call to this
+     * method or any of the other methods of this interface that return {@link
+     * EventBundle}s (e.g. {@link RepositoryService#submit(Batch)}). When
+     * this method returns without an exception the bundle identfier in
+     * <code>sessionInfo</code> will be updated to reference the most recent
+     * event bundle returned by this call.
      *
-     * @param sessionInfo
-     * @param nodeId
-     * @param listener
-     * @param eventTypes
-     * @param isDeep
-     * @param uuid
-     * @param nodeTypeIds
-     * @throws javax.jcr.RepositoryException
-     * @see javax.jcr.observation.ObservationManager#addEventListener(javax.jcr.observation.EventListener, int, String, boolean, String[], String[], boolean)
-     */
-    public void addEventListener(SessionInfo sessionInfo, NodeId nodeId, EventListener listener, int eventTypes, boolean isDeep, String[] uuid, QName[] nodeTypeIds) throws RepositoryException;
-
-    /**
-     * Removes the registration of the specified <code>EventListener</code>. If
-     * the event listener was not registered for the node indentified by <code>nodeId</code>
-     * an <code>RepositoryException</code> is thrown. The same applies if the
-     * registration timeouted before or an other error occurs.<p/>
-     * Please note, that all eventlistener registrations must be removed upon
-     * {@link javax.jcr.Session#logout()} logout) of the <code>Session</code>.
-     *
-     * @param sessionInfo
-     * @param nodeId
-     * @param listener
-     * @throws javax.jcr.RepositoryException
-     * @see javax.jcr.observation.ObservationManager#removeEventListener(javax.jcr.observation.EventListener)
+     * @param sessionInfo the session info.
+     * @param timeout     a timeout in milliseconds to wait at most for an
+     *                    external event bundle. If <code>timeout</code> is up
+     *                    and no event occurred meanwhile an empty array is
+     *                    returned.
+     * @return an array of <code>EventBundle</code>s representing the external
+     *         events that occurred.
+     * @throws RepositoryException if an error occurs while retrieving the event
+     *                             bundles or the currently set bundle
+     *                             identifier in <code>sessionInfo</code>
+     *                             references an unknown or outdated event
+     *                             bundle.
+     * @throws UnsupportedRepositoryOperationException
+     *                             if this implementation does not support
+     *                             observation.
      */
-    public void removeEventListener(SessionInfo sessionInfo, NodeId nodeId, EventListener listener) throws RepositoryException;
+    public EventBundle[] getEvents(SessionInfo sessionInfo, long timeout)
+            throws RepositoryException, UnsupportedRepositoryOperationException;
 
     //---------------------------------------------------------< Namespaces >---
     /**

Modified: jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java (original)
+++ jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java Fri Oct 27 07:16:13 2006
@@ -17,6 +17,11 @@
 package org.apache.jackrabbit.spi;
 
 /**
+ * TODO: implement as bean and instanticate on client?
+ * TODO: use credentials instead of UserID?
+ * TODO: add name/value parameter facility
+ * TODO: server returns set of name/value pairs which should be set by the client?
+ *
  * <code>SessionInfo</code>...
  */
 public interface SessionInfo {
@@ -30,4 +35,32 @@
     public void addLockToken(String lockToken);
 
     public void removeLockToken(String lockToken);
+
+    /**
+     * Returns the identifier of the last {@link EventBundle} delivered using
+     * this <code>SessionInfo</code>. When a <code>SessionInfo</code> is
+     * initially aquired the returned event identifier is set to the last
+     * <code>EventBundle</code> created by the SPI implementation previously to
+     * the call to {@link RepositoryService#obtain(javax.jcr.Credentials, String)
+     * RepositoryService.obtain()}. If there was no previous event <code>null</code>
+     * is returned. Thus a <code>null</code> value will effectively return all
+     * events that occurred since the start of the SPI server.
+     * <p/>
+     * For implementations, that do not support observation this method will
+     * always return <code>null</code>.
+     *
+     * @return the identifier of the last {@link EventBundle} delivered using
+     * this <code>SessionInfo</code>.
+     */
+    public String getLastEventBundleId();
+
+    /**
+     * Sets the identifier of the last {@link EventBundle} delivered using this
+     * <code>SessionInfo</code>. This identifier will be used to retrieve the
+     * subsequent event bundles when calling {@link RepositoryService#getEvents(SessionInfo, long)}.
+     *
+     * @param eventBundleId the identifier of the last {@link EventBundle}
+     *                      delivered using this <code>SessionInfo</code>.
+     */
+    public void setLastEventBundleId(String eventBundleId);
 }

Added: jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/package.html
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/package.html?view=auto&rev=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/package.html (added)
+++ jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/package.html Fri Oct 27 07:16:13 2006
@@ -0,0 +1,63 @@
+<!--
+   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.
+-->
+<body>
+Defines the interfaces of the JCR SPI (Service Provider Interface).
+<p/>
+The SPI cuts the JCR stack into two parts:
+<ul>
+<li>Above the SPI an implementation that whishes to expose the JCR API again
+needs to implement the transient item space, the session local namespace mapping
+and various conversions from the fully qualified values present in the SPI to
+the resolved values in the JCR API.</li>
+<li>An implementation of the SPI interfaces has to deal with the persistent
+view of a JCR repository. This includes almost all aspects of the JSR 170
+specification, except the previously stated transient space and the session
+local namespace resolution to prefixes.</li>
+</ul>
+
+<h3>Observation</h3>
+Because one of the goals of this SPI is to make it easier to implement a
+remoting layer using various existing protocols, the observation mechanism has
+been design with this goal in mind. Instead of a listener registration with
+a callback for each event bundle, the SPI uses a polling mechanism with a
+timeout: {@link org.apache.jackrabbit.spi.RepositoryService#getEvents
+RepositoryService.getEvents()}. With every call to this method the repository
+is advised to return the events that occurred since the last call. As a
+reference to the last retrieved {@link org.apache.jackrabbit.spi.EventBundle}
+the {@link org.apache.jackrabbit.spi.SessionInfo} contains a bundle identifier
+which is automatically updated on each call to <code>RepositoryService.getEvents()</code>.
+While this design allows for a polling implementation on top of the SPI it is
+also well suited for a listener based observation implementation on top of the
+SPI. With only little thread synchronization overhead events can be acquired
+using a <code>timeout</code> of {@link java.lang.Long#MAX_VALUE}.
+<p/>
+In addition all methods on the RepositoryService that execute an operation on
+the repository return an array of <code>EventBundle</code>s. This array not
+only contains the bundle that is associated to the previously executed operation
+but also contains all preceeding event bundles not yet delivered since the last
+call to <code>RepositoryService.getEvents()</code> or any other method on the
+<code>RepositoryService</code> which returns <code>EventBundle</code>s. This
+design ensures a consistent sequence of event bundles delivered to the SPI
+client.
+<p/>
+If an SPI implementation does not support observation, the method
+<code>RepositoryService.getEvents()</code> will always throw an
+{@link javax.jcr.UnsupportedRepositoryOperationException} and all methods
+on <code>RepositoryService</code> which return an array of
+<code>EventBundle</code>s will only return one event bundle which is associated
+with the local operation that has just been executed.
+</body>
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/spi/src/main/java/org/apache/jackrabbit/spi/package.html
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventBundleImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventBundleImpl.java?view=auto&rev=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventBundleImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventBundleImpl.java Fri Oct 27 07:16:13 2006
@@ -0,0 +1,89 @@
+/*
+ * 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.spi2dav;
+
+import org.apache.jackrabbit.spi.EventBundle;
+import org.apache.jackrabbit.spi.EventIterator;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.observation.ObservationConstants;
+import org.w3c.dom.Element;
+
+/**
+ * <code>EventBundleImpl</code> implements a bundle of events. The individual
+ * events can be retrieved when calling {@link #getEvents()}.
+ */
+class EventBundleImpl implements EventBundle, ObservationConstants {
+
+    static final EventBundle EMPTY = new EventBundleImpl(null, null, null) {
+        public EventIterator getEvents() {
+            return IteratorHelper.EMPTY;
+        }
+    };
+
+    private final Element eventBundleElement;
+
+    private final URIResolver uriResolver;
+
+    private final SessionInfo sessionInfo;
+
+    private final boolean isLocal;
+
+    /**
+     * Creates a new event bundle.
+     *
+     * @param eventBundleElement
+     * @param uriResolver
+     * @param sessionInfo
+     */
+    EventBundleImpl(Element eventBundleElement,
+                    URIResolver uriResolver,
+                    SessionInfo sessionInfo) {
+        this.eventBundleElement = eventBundleElement;
+        this.uriResolver = uriResolver;
+        this.sessionInfo = sessionInfo;
+        String value = null;
+        if (eventBundleElement != null) {
+            value = DomUtil.getAttribute(eventBundleElement,
+                        XML_EVENT_IS_LOCAL, NAMESPACE);
+        }
+        this.isLocal = (value != null) ? Boolean.valueOf(value).booleanValue() : false;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public EventIterator getEvents() {
+        return new EventIteratorImpl(eventBundleElement, uriResolver, sessionInfo);
+    }
+
+    /**
+     * @inheritDoc
+     * <p/>
+     * TODO implement
+     */
+    public String getBundleId() {
+        return null;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public boolean isLocal() {
+        return isLocal;
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventBundleImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventIteratorImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventIteratorImpl.java?view=diff&rev=468388&r1=468387&r2=468388
==============================================================================
--- jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventIteratorImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventIteratorImpl.java Fri Oct 27 07:16:13 2006
@@ -38,18 +38,16 @@
 
     private final SessionInfo sessionInfo;
     private final URIResolver uriResolver;
-    private ElementIterator bundleIterator;
     private ElementIterator eventElementIterator;
 
     private Event next;
     private long pos;
 
-    public EventIteratorImpl(Element eventDiscoveryElem, URIResolver uriResolver, SessionInfo sessionInfo) {
+    public EventIteratorImpl(Element eventBundleElement, URIResolver uriResolver, SessionInfo sessionInfo) {
 
         this.sessionInfo = sessionInfo;
         this.uriResolver = uriResolver;
-        bundleIterator = DomUtil.getChildren(eventDiscoveryElem, ObservationConstants.XML_EVENTBUNDLE, ObservationConstants.NAMESPACE);;
-        retrieveNextEventIterator();
+        this.eventElementIterator = DomUtil.getChildren(eventBundleElement, ObservationConstants.XML_EVENT, ObservationConstants.NAMESPACE);
         retrieveNextEvent();
     }
 
@@ -103,18 +101,6 @@
                     log.error("Unexpected error while creating event.", e);
                 }
             }
-
-            if (!eventElementIterator.hasNext()) {
-                retrieveNextEventIterator();
-            }
-        }
-    }
-
-    private void retrieveNextEventIterator() {
-        eventElementIterator = null;
-        if (bundleIterator.hasNext()) {
-            Element bundleElem = bundleIterator.nextElement();
-            eventElementIterator =  DomUtil.getChildren(bundleElem, ObservationConstants.XML_EVENT, ObservationConstants.NAMESPACE);
         }
     }
 }