You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by st...@apache.org on 2016/10/31 14:26:15 UTC

svn commit: r1767287 - in /jackrabbit/oak/trunk/oak-jcr/src: main/java/org/apache/jackrabbit/oak/jcr/observation/ main/java/org/apache/jackrabbit/oak/jcr/observation/filter/ test/java/org/apache/jackrabbit/oak/jcr/observation/

Author: stefanegli
Date: Mon Oct 31 14:26:15 2016
New Revision: 1767287

URL: http://svn.apache.org/viewvc?rev=1767287&view=rev
Log:
OAK-5020 : adding withIncludeAncestorsRemove support to the OakEventFilter: includes NODE_REMOVED events for all parents

Modified:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/OakEventFilterImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/ObservationManagerImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/filter/OakEventFilter.java
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/OakEventFilterImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/OakEventFilterImpl.java?rev=1767287&r1=1767286&r2=1767287&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/OakEventFilterImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/OakEventFilterImpl.java Mon Oct 31 14:26:15 2016
@@ -20,10 +20,22 @@ package org.apache.jackrabbit.oak.jcr.ob
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static javax.jcr.observation.Event.NODE_REMOVED;
+
 import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.api.observation.JackrabbitEventFilter;
+import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.jcr.observation.filter.OakEventFilter;
+import org.apache.jackrabbit.oak.plugins.observation.filter.FilterBuilder;
+import org.apache.jackrabbit.oak.plugins.observation.filter.PermissionProviderFactory;
+import org.apache.jackrabbit.oak.plugins.observation.filter.FilterBuilder.Condition;
 
 /**
  * Implements OakEventFilter which is an extension to the JackrabbitEventFilter
@@ -36,6 +48,9 @@ public class OakEventFilterImpl extends
     /** whether or not applyNodeTypeOnSelf feature is enabled */
     private boolean applyNodeTypeOnSelf;
 
+    /** whether or not includeAncestorsRemove feature is enabled */
+    private boolean includeAncestorRemove;
+
     public OakEventFilterImpl(@Nonnull JackrabbitEventFilter delegate) {
         checkNotNull(delegate);
         this.delegate = delegate;
@@ -161,4 +176,77 @@ public class OakEventFilterImpl extends
         return applyNodeTypeOnSelf;
     }
 
+    @Override
+    public OakEventFilter withIncludeAncestorsRemove() {
+        this.includeAncestorRemove = true;
+        return this;
+    }
+
+    boolean getIncludeAncestorsRemove() {
+        return includeAncestorRemove;
+    }
+
+    private void addAncestorsRemoveCondition(Set<String> parentPaths, String globPath) {
+        if (globPath == null || !globPath.contains("/")) {
+            return;
+        }
+        // from /a/b/c         => add /a and /a/b
+        // from /a/b/**        => add /a
+        // from /a             => add nothing
+        // from /              => add nothing
+        // from /a/b/**/*.html => add /a
+        // from /a/b/*/*.html  => add /a
+
+        Iterator<String> it = PathUtils.elements(globPath).iterator();
+        StringBuffer sb = new StringBuffer();
+        while(it.hasNext()) {
+            String element = it.next();
+            if (element.contains("*")) {
+                if (parentPaths.size() > 0) {
+                    parentPaths.remove(parentPaths.size()-1);
+                }
+                break;
+            } else if (!it.hasNext()) {
+                break;
+            }
+            sb.append("/");
+            sb.append(element);
+            parentPaths.add(sb.toString() + "/*");
+        }
+    }
+
+    public Condition wrapMainCondition(Condition mainCondition, FilterBuilder filterBuilder, PermissionProviderFactory permissionProviderFactory) {
+        if (!includeAncestorRemove || (getEventTypes() & NODE_REMOVED) != NODE_REMOVED) {
+            return mainCondition;
+        }
+        Set<String> parentPaths = new HashSet<String>();
+        addAncestorsRemoveCondition(parentPaths, getAbsPath());
+        if (getAdditionalPaths() != null) {
+            for (String absPath : getAdditionalPaths()) {
+                addAncestorsRemoveCondition(parentPaths, absPath);
+            }
+        }
+//        if (globPaths != null) {
+//            for (String globPath : globPaths) {
+//                addAncestorsRemoveCondition(parentPaths, globPath);
+//            }
+//        }
+        if (parentPaths.size() == 0) {
+            return mainCondition;
+        }
+        List<Condition> ancestorsRemoveConditions = new LinkedList<Condition>();
+        for (String aParentPath : parentPaths) {
+            ancestorsRemoveConditions.add(filterBuilder.path(aParentPath));
+        }
+        return filterBuilder.any(
+                        mainCondition,
+                        filterBuilder.all(
+                                filterBuilder.eventType(NODE_REMOVED),
+                                filterBuilder.any(ancestorsRemoveConditions),
+                                filterBuilder.deleteSubtree(),
+                                filterBuilder.accessControl(permissionProviderFactory)
+                                )
+                        );
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/ObservationManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/ObservationManagerImpl.java?rev=1767287&r1=1767286&r2=1767287&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/ObservationManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/ObservationManagerImpl.java Mon Oct 31 14:26:15 2016
@@ -238,6 +238,13 @@ public class ObservationManagerImpl impl
         List<Condition> includeConditions = newArrayList();
         for (String path : includePaths) {
             includeConditions.add(filterBuilder.path(concat(path, depthPattern)));
+            if (oakEventFilter != null && oakEventFilter.getIncludeAncestorsRemove()) {
+                // with the 'includeAncestorsRemove' extension we need
+                // to register '/' as the base path - done in wrapMainCondition
+                // - in order to catch any node removal. So we have to skip adding 
+                // the subtree here as a result.
+                continue;
+            }
             filterBuilder.addSubTree(path);
         }
 
@@ -250,11 +257,7 @@ public class ObservationManagerImpl impl
             }
         }
 
-        filterBuilder
-            .includeSessionLocal(!noLocal)
-            .includeClusterExternal(!noExternal)
-            .includeClusterLocal(!noInternal)
-            .condition(filterBuilder.all(
+        Condition condition = filterBuilder.all(
                     filterBuilder.all(excludeConditions),
                     filterBuilder.any(includeConditions),
                     filterBuilder.deleteSubtree(),
@@ -262,7 +265,15 @@ public class ObservationManagerImpl impl
                     filterBuilder.eventType(eventTypes),
                     filterBuilder.uuid(Selectors.PARENT, uuids),
                     filterBuilder.nodeType(nodeTypeSelector, validateNodeTypeNames(nodeTypeName)),
-                    filterBuilder.accessControl(permissionProviderFactory)));
+                    filterBuilder.accessControl(permissionProviderFactory));
+        if (oakEventFilter != null) {
+            condition = oakEventFilter.wrapMainCondition(condition, filterBuilder, permissionProviderFactory);
+        }
+        filterBuilder
+            .includeSessionLocal(!noLocal)
+            .includeClusterExternal(!noExternal)
+            .includeClusterLocal(!noInternal)
+            .condition(condition);
 
         // FIXME support multiple path in ListenerTracker
         ListenerTracker tracker = new WarningListenerTracker(

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/filter/OakEventFilter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/filter/OakEventFilter.java?rev=1767287&r1=1767286&r2=1767287&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/filter/OakEventFilter.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/filter/OakEventFilter.java Mon Oct 31 14:26:15 2016
@@ -46,4 +46,24 @@ public abstract class OakEventFilter ext
      */
     public abstract OakEventFilter withApplyNodeTypeOnSelf();
 
+    /**
+     * This causes the registration of !deep NODE_REMOVED registrations
+     * of all parents of the include paths (both normal and glob).
+     * <ul>
+     * <li>include path /a/b/c/d results in additional !deep NODE_REMOVED
+     * filters on /a/b/c, on /a/b and on /a</li>
+     * <li>include path /a/b/** results in additional !deep NODE_REMOVED
+     * filter on /a</li>
+     * </ul>
+     * <p>
+     * Note that unlike 'normal' include and exclude paths, this variant
+     * doesn't apply Oak's NamePathMapper on the ancestors of the
+     * registers paths.
+     * <p>
+     * Also note that this might disable 'observation prefiltering based on paths' 
+     * (OAK-4796) on this listener.
+     * @return this filter with the filter change applied
+     */
+    public abstract OakEventFilter withIncludeAncestorsRemove();
+
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java?rev=1767287&r1=1767286&r2=1767287&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java Mon Oct 31 14:26:15 2016
@@ -1276,6 +1276,75 @@ public class ObservationTest extends Abs
     
     }
 
+    @Test
+    public void includeAncestorsRemove() throws Exception {
+        assumeTrue(observationManager instanceof ObservationManagerImpl);
+
+        Node testNode = getNode(TEST_PATH);
+        testNode.addNode("a").addNode("b").addNode("c").addNode("d").setProperty("e", 42);
+        testNode.getSession().save();
+
+        ObservationManagerImpl oManager = (ObservationManagerImpl) observationManager;
+        ExpectationListener listener = new ExpectationListener();
+        
+        JackrabbitEventFilter filter = new JackrabbitEventFilter();
+        filter.setEventTypes(ALL_EVENTS);
+        filter.setAbsPath(TEST_PATH + "/a/b/c/d");
+        filter.setIsDeep(true);
+        filter = FilterFactory.wrap(filter).withIncludeAncestorsRemove();
+
+        oManager.addEventListener(listener, filter);
+
+        Node d = testNode.getNode("a").getNode("b").getNode("c").getNode("d");
+        Property e = d.getProperty("e");
+        listener.expectRemove(e);
+//        listener.expectRemove(d.getProperty("jcr:primaryType"));
+//        d.remove();
+        listener.expectRemove(d).remove();
+        testNode.getSession().save();
+
+        Thread.sleep(1000);
+        List<Expectation> missing = listener.getMissing(TIME_OUT, TimeUnit.SECONDS);
+        List<Event> unexpected = listener.getUnexpected();
+        assertTrue("Unexpected events: " + unexpected, unexpected.isEmpty());
+        assertTrue("Missing events: " + missing, missing.isEmpty());
+
+        oManager.addEventListener(new EventListener() {
+            
+            @Override
+            public void onEvent(EventIterator events) {
+                while(events.hasNext()) {
+                    System.out.println("GOT: "+events.next());
+                }
+                
+            }
+        }, NODE_REMOVED, TEST_PATH + "/a", false, null, null, false);
+        System.out.println("REGISTERED");
+        
+        testNode = getNode(TEST_PATH);
+        Node b = testNode.getNode("a").getNode("b");
+        listener.expect(b.getPath(), NODE_REMOVED);
+        b.remove();
+        // but not the jcr:primaryType
+        testNode.getSession().save();
+
+        Thread.sleep(1000);
+        missing = listener.getMissing(TIME_OUT, TimeUnit.SECONDS);
+        unexpected = listener.getUnexpected();
+        assertTrue("Missing events: " + missing, missing.isEmpty());
+        assertTrue("Unexpected events: " + unexpected, unexpected.isEmpty());
+    
+        Node a = testNode.getNode("a");
+        listener.expect(a.getPath(), NODE_REMOVED);
+        a.remove();
+        // but not the jcr:primaryType
+        testNode.getSession().save();
+
+        missing = listener.getMissing(TIME_OUT, TimeUnit.SECONDS);
+        unexpected = listener.getUnexpected();
+        assertTrue("Unexpected events: " + unexpected, unexpected.isEmpty());
+        assertTrue("Missing events: " + missing, missing.isEmpty());
+    }
 
 
 }