Index: frontend/engine/src/main/java/org/hippoecm/frontend/JcrObservationManager.java
===================================================================
--- frontend/engine/src/main/java/org/hippoecm/frontend/JcrObservationManager.java	(revision 18892)
+++ frontend/engine/src/main/java/org/hippoecm/frontend/JcrObservationManager.java	(working copy)
@@ -69,6 +69,8 @@
 
     private static JcrObservationManager INSTANCE = new JcrObservationManager();
 
+    private WeakHashMap<Session, Map<String, NodeState>> cache = new WeakHashMap<Session, Map<String, NodeState>>();
+
     private static class ObservationException extends Exception {
         private static final long serialVersionUID = 1L;
 
@@ -77,9 +79,9 @@
         }
     }
 
-    private class NodeCache {
+    private static class NodeState {
 
-        private class NodeEvent implements Event {
+        class NodeEvent implements Event {
 
             int type;
             String name;
@@ -115,19 +117,13 @@
         private Map<String, Value[]> properties;
         private List<String> nodes;
 
-        NodeCache(Node node) throws RepositoryException {
+        NodeState(Node node) throws RepositoryException {
             this.path = node.getPath();
             this.userId = node.getSession().getUserID();
 
             properties = new HashMap<String, Value[]>();
             nodes = new LinkedList<String>();
-        }
-        
-        void init(Node node) throws RepositoryException {
-            process(node, properties, nodes);
-        }
 
-        void process(Node node, Map<String, Value[]> properties, List<String> nodes) throws RepositoryException {
             PropertyIterator propIter = node.getProperties();
             while (propIter.hasNext()) {
                 Property property = propIter.nextProperty();
@@ -155,12 +151,11 @@
             }
         }
 
-        Iterator<Event> update(Node node) throws RepositoryException {
+        Iterator<Event> getEvents(NodeState newCache) throws RepositoryException {
             List<Event> events = new LinkedList<Event>();
 
-            Map<String, Value[]> newProperties = new HashMap<String, Value[]>();
-            List<String> nodes = new LinkedList<String>();
-            process(node, newProperties, nodes);
+            Map<String, Value[]> newProperties = newCache.properties;
+            List<String> nodes = newCache.nodes;
 
             for (Map.Entry<String, Value[]> entry : this.properties.entrySet()) {
                 if (newProperties.containsKey(entry.getKey())) {
@@ -175,7 +170,6 @@
                                 break;
                             }
                         }
-                        
                     }
                 } else {
                     events.add(new NodeEvent(entry.getKey(), Event.PROPERTY_REMOVED));
@@ -197,13 +191,26 @@
                     events.add(new NodeEvent(child, Event.NODE_REMOVED));
                 }
             }
-
-            this.properties = newProperties;
-            this.nodes = nodes;
             return events.iterator();
         }
+
+        Event getChangeEvent() {
+            return new NodeEvent(null, 0);
+        }
+
     }
 
+    private NodeState getNodeState(Session session, String path) {
+        synchronized (cache) {
+            Map<String, NodeState> states = cache.get(session);
+            if (states == null) {
+                states = new HashMap<String, NodeState>();
+                cache.put(session, states);
+            }
+            return states.get(path);
+        }
+    }
+
     private class JcrListener extends WeakReference<EventListener> implements EventListener {
         String path;
         int eventTypes;
@@ -214,7 +221,6 @@
 
         boolean isvirtual;
         List<String> fixed;
-        Map<String, NodeCache> pending;
         List<Event> events;
         Session session;
         FacetSearchObserver fso;
@@ -256,7 +262,6 @@
             this.isvirtual = isVirtual(getRoot());
 
             session = getSession().getJcrSession();
-            pending = new HashMap<String, NodeCache>();
 
             if (session != null) {
                 subscribe();
@@ -266,16 +271,9 @@
 
             fixed = new ArrayList<String>();
             if (!isDeep) {
-                Node root = getRoot();
-                NodeCache cache = new NodeCache(root);
-                cache.init(root);
-                pending.put(absPath, cache);
                 fixed.add(absPath);
             } else if (uuids != null) {
                 for (Node node : getReferencedNodes()) {
-                    NodeCache cache = new NodeCache(node);
-                    cache.init(node);
-                    pending.put(node.getPath(), cache);
                     fixed.add(node.getPath());
                 }
             }
@@ -291,7 +289,6 @@
                 session = null;
             }
             events.clear();
-            pending = null;
         }
 
         void subscribe() throws RepositoryException {
@@ -387,7 +384,7 @@
             return false;
         }
 
-        void processPending(NodeIterator iter, List<Node> nodes) {
+        void processPending(NodeIterator iter, Set<Node> nodes) {
             while (iter.hasNext()) {
                 Node node = iter.nextNode();
                 if (node != null) {
@@ -398,7 +395,7 @@
             }
         }
 
-        void expandNew(final List<Node> nodes) {
+        void expandNew(final Set<Node> nodes) {
             for (Node node : new ArrayList<Node>(nodes)) {
                 if (node.isNew()) {
                     ItemVisitor visitor = new TraversingItemVisitor() {
@@ -449,7 +446,7 @@
             }
         }
 
-        synchronized void process() {
+        synchronized void process(Map<String, NodeState> dirty) {
             // listeners can be invoked after they have been removed
             if (session == null) {
                 log.debug("Listener " + this + " is no longer registerd");
@@ -475,7 +472,17 @@
 
             // process pending changes
             Node root = null;
-            List<Node> nodes = new LinkedList<Node>();
+            Set<Node> nodes = new TreeSet<Node>(new Comparator<Node>() {
+
+                public int compare(Node o1, Node o2) {
+                    try {
+                        return o1.getPath().compareTo(o2.getPath());
+                    } catch (RepositoryException ex) {
+                        return 0;
+                    }
+                }
+
+            });
             if (!isvirtual) {
                 try {
                     root = getRoot();
@@ -548,51 +555,29 @@
                     try {
                         path = node.getPath();
                         paths.add(path);
-                        NodeCache cache;
-                        if (pending.containsKey(path)) {
-                            cache = pending.get(path);
+
+                        NodeState newState;
+                        if (dirty.containsKey(path)) {
+                            newState = dirty.get(path);
                         } else {
-                            if (uuids != null) {
-                                log.debug("New cache entry for " + path + ", listener " + this.path + " uuids: " + uuids);
+                            newState = new NodeState(node);
+                            dirty.put(path, newState);
+                        }
+
+                        NodeState oldState = getNodeState(session, path);
+                        if (oldState != null) {
+                            Iterator<Event> iter = oldState.getEvents(newState);
+                            while (iter.hasNext()) {
+                                events.add(iter.next());
                             }
-                            cache = new NodeCache(node);
-                            pending.put(path, cache);
+                        } else {
+                            events.add(newState.getChangeEvent());
                         }
-                        Iterator<Event> iter = cache.update(node);
-                        while (iter.hasNext()) {
-                            events.add(iter.next());
-                        }
                     } catch (RepositoryException e) {
                         log.warn("Failed to process node", e);
                     }
                 }
 
-                for (String path : new ArrayList<String>(pending.keySet())) {
-                    if (!paths.contains(path)) {
-                        if (!fixed.contains(path)) {
-                            pending.remove(path);
-                        } else {
-                            // update fixed nodes
-                            try {
-                                if (session.itemExists(path)) {
-                                    Node node = (Node) session.getItem(path);
-                                    pending.get(path).init(node);
-                                } else {
-                                    fixed.remove(path);
-                                    pending.remove(path);
-                                }
-                            } catch (PathNotFoundException ex) {
-                                log.warn("Fixed (monitored) node was not found at " + path);
-                                fixed.remove(path);
-                                pending.remove(path);
-                            } catch (RepositoryException ex) {
-                                log.warn("Fixed (monitored) node ("+ path+") error", ex);
-                                fixed.remove(path);
-                                pending.remove(path);
-                            }
-                        }
-                    }
-                }
             }
 
             final Iterator<Event> upstream = events.iterator();
@@ -676,6 +661,33 @@
                 synchronized (listeners) {
                     listeners.put(listener, realListener);
                 }
+
+                // prefetch fixed nodes into cache
+                if (realListener.fixed.size() > 0) {
+                    Session jcrSession = session.getJcrSession();
+                    Map<String, NodeState> states;
+                    synchronized (cache) {
+                        states = cache.get(jcrSession);
+                        if (states == null) {
+                            states = new HashMap<String, NodeState>();
+                            cache.put(jcrSession, states);
+                        }
+                    }
+                    synchronized (states) {
+                        for (String path : realListener.fixed) {
+                            if (!states.containsKey(path)) {
+                                try {
+                                    if (jcrSession.itemExists(path)) {
+                                        NodeState state = new NodeState((Node) jcrSession.getItem(path));
+                                        states.put(path, state);
+                                    }
+                                } catch (RepositoryException ex) {
+                                    log.warn("Failed to initialize node state", ex);
+                                }
+                            }
+                        }
+                    }
+                }
             } catch (ObservationException ex) {
                 log.error(ex.getMessage());
             }
@@ -765,7 +777,8 @@
                         try {
                             session.getRootNode().getNode(path.substring(1)).refresh(true);
                         } catch (PathNotFoundException ex) {
-                            log.error("Could not find path " + path + " for event, discarding event and continue: " + ex.getMessage());
+                            log.error("Could not find path " + path + " for event, discarding event and continue: "
+                                    + ex.getMessage());
                         }
                     }
                 }
@@ -795,6 +808,7 @@
                 }
 
             });
+
             synchronized (listeners) {
                 for (JcrListener listener : listeners.values()) {
                     if (listener.getSession() == session) {
@@ -803,9 +817,39 @@
                 }
             }
 
+            Map<String, NodeState> dirty = new HashMap<String, NodeState>();
             for (JcrListener listener : set) {
-                listener.process();
+                listener.process(dirty);
             }
+
+            Session jcrSession = session.getJcrSession();
+            Map<String, NodeState> states;
+            synchronized (cache) {
+                states = cache.get(jcrSession);
+                if (states == null) {
+                    states = new HashMap<String, NodeState>();
+                    cache.put(jcrSession, states);
+                }
+            }
+            synchronized (states) {
+                // update cache
+                for (Map.Entry<String, NodeState> nodes : dirty.entrySet()) {
+                    states.put(nodes.getKey(), nodes.getValue());
+                }
+
+                // remove stale entries
+                Iterator<Map.Entry<String, NodeState>> cacheIter = states.entrySet().iterator();
+                while (cacheIter.hasNext()) {
+                    Map.Entry<String, NodeState> entry = cacheIter.next();
+                    try {
+                        if (!jcrSession.itemExists(entry.getKey())) {
+                            cacheIter.remove();
+                        }
+                    } catch (RepositoryException ex) {
+                        log.warn("Could not determine whether " + entry.getKey() + " exists", ex);
+                    }
+                }
+            }
         } else {
             log.error("No session found");
         }
