Author: stefanegli
Date: Fri Nov 11 15:46:33 2016
New Revision: 1769310

URL: http://svn.apache.org/viewvc?rev=1769310&view=rev
Log:
OAK-5096 : when switching from AND to OR one must assure that the aggregated 
subpaths are also added to the include paths - both to the prefilter and the 
filter conditions - earlier in AND mode this was assured via the 
NodeTypeAggregationFilter but now this is no longer used

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/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=1769310&r1=1769309&r2=1769310&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
 Fri Nov 11 15:46:33 2016
@@ -239,10 +239,12 @@ public class OakEventFilterImpl extends
 
     private FilterBuilder builder;
 
-    private Condition all;
-
     private EventAggregator aggregator;
 
+    private boolean withNodeTypeAggregate;
+
+    private Set<String> relativeGlobPaths;
+
     public OakEventFilterImpl(@Nonnull JackrabbitEventFilter delegate) {
         checkNotNull(delegate);
         this.delegate = delegate;
@@ -475,24 +477,46 @@ public class OakEventFilterImpl extends
         return builder;
     }
 
-    public OakEventFilterImpl and(Condition... condition) {
-        checkNotNull(condition);
-        if (all == null) {
-            all = builder().all(condition);
-        } else {
-            all = builder().all(all, builder.all(condition));
-        }
-        return this;
-    }
-
     public OakEventFilterImpl aggregator(EventAggregator aggregator) {
         checkNotNull(aggregator);
         this.aggregator = aggregator;
         return this;
     }
 
-    public Condition getAnds() {
-        return all;
+    public Condition getAdditionalIncludeConditions(Set<String> includePaths) {
+        if (!withNodeTypeAggregate) {
+            return null;
+        }
+        List<Condition> additionalIncludeConditions = new 
LinkedList<Condition>();
+        // for nodeTypeAggregation in OR mode we must append
+        // the relativeGlobPaths to all includePaths
+        for (String includePath : includePaths) {
+            if (includePath.equals("**") || includePath.endsWith("/*") || 
includePath.endsWith("/**")) {
+                // this will include anything, so nothing to append in this 
case
+            } else {
+                // otherwise append all the relativeGlobPaths, except ""
+                for (String relativeGlobPath : relativeGlobPaths) {
+                    if (relativeGlobPath.equals("")) {
+                        // this corresponds to 'SELF' which is already 
included, so skip
+                        continue;
+                    } else {
+                        String additionalGlobPath;
+                        if (includePath.endsWith("/")) {
+                            additionalGlobPath = includePath + 
relativeGlobPath;
+                        } else {
+                            additionalGlobPath = includePath + "/" + 
relativeGlobPath;
+                        }
+                        
additionalIncludeConditions.add(builder().path(additionalGlobPath));
+                        
additionalIncludeConditions.add(builder().path(additionalGlobPath + "/*"));
+                    }
+                }
+            }
+        }
+        if (additionalIncludeConditions.size() == 0) {
+            return null;
+        } else {
+            return builder().any(additionalIncludeConditions);
+        }
     }
 
     public EventAggregator getAggregator() {
@@ -501,6 +525,12 @@ public class OakEventFilterImpl extends
 
     @Override
     public OakEventFilter withNodeTypeAggregate(String[] nodeTypes, String[] 
relativeGlobPaths) {
+        this.withNodeTypeAggregate = true;
+        if (this.relativeGlobPaths == null) {
+            this.relativeGlobPaths = new HashSet<String>();
+        }
+        this.relativeGlobPaths.addAll(Arrays.asList(relativeGlobPaths));
+        
         final Pattern[] relativePathPatterns = new 
Pattern[relativeGlobPaths.length];
         for (int i = 0; i < relativePathPatterns.length; i++) {
             relativePathPatterns[i] = 
Pattern.compile(GlobbingPathHelper.globPathAsRegex(relativeGlobPaths[i]));
@@ -527,6 +557,25 @@ public class OakEventFilterImpl extends
         if (includeAncestorRemove) {
             includePaths.clear();
             includePaths.add("/");
+        } else if (withNodeTypeAggregate) {
+            // ensure that, for prefixing, all includePaths allow additional
+            // subpaths for the aggregation - this can be simplified
+            // to just allow anything (**) below there, as this is just
+            // about prefiltering, not actual (precise) filtering.
+            // so the goal is just to ensure nothing is erroneously excluded
+            // so more including is fine.
+            Set<String> originalPaths = new HashSet<String>(includePaths);
+            for (String includePath : originalPaths) {
+                if (includePath.equals("**") || includePath.endsWith("/*") || 
includePath.endsWith("/**")) {
+                    // skip, this is fine
+                } else if (includePath.endsWith("/")) {
+                    includePaths.remove(includePath);
+                    includePaths.add(includePath + "**");
+                } else {
+                    includePaths.remove(includePath);
+                    includePaths.add(includePath + "/**");
+                }
+            }
         }
     }
 

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=1769310&r1=1769309&r2=1769310&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
 Fri Nov 11 15:46:33 2016
@@ -285,9 +285,9 @@ public class ObservationManagerImpl impl
         Selector nodeTypeSelector = Selectors.PARENT;
         boolean deleteSubtree = true;
         if (oakEventFilter != null) {
-            Condition ands = oakEventFilter.getAnds();
-            if (ands != null) {
-                excludeConditions.add(ands);
+            Condition additionalIncludes = 
oakEventFilter.getAdditionalIncludeConditions(includePaths);
+            if (additionalIncludes != null) {
+                includeConditions.add(additionalIncludes);
             }
             filterBuilder.aggregator(oakEventFilter.getAggregator());
             if (oakEventFilter.getApplyNodeTypeOnSelf()) {

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=1769310&r1=1769309&r2=1769310&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
 Fri Nov 11 15:46:33 2016
@@ -38,6 +38,7 @@ import static org.junit.Assert.assertFal
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assume.assumeTrue;
 
 import java.util.Arrays;
@@ -52,20 +53,30 @@ import java.util.concurrent.ScheduledExe
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import javax.jcr.AccessDeniedException;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemExistsException;
 import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.Property;
 import javax.jcr.PropertyType;
+import javax.jcr.ReferentialIntegrityException;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeTypeManager;
 import javax.jcr.nodetype.NodeTypeTemplate;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.EventListener;
 import javax.jcr.observation.ObservationManager;
+import javax.jcr.version.VersionException;
 
 import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.ForwardingListenableFuture;
@@ -1756,7 +1767,7 @@ public class ObservationTest extends Abs
     }
 
     /**
-     * OAK -5096 : new test case for OR mode
+     * OAK-5096 : new test case for OR mode
      */
     @Test
     public void testAggregate5() throws Exception {
@@ -1793,6 +1804,108 @@ public class ObservationTest extends Abs
         assertTrue("Unexpected events: " + unexpected, unexpected.isEmpty());
         assertTrue("Missing events: " + missing, missing.isEmpty());
     }
+
+    @Test
+    public void testFileWithGlobs() throws Exception {
+        doTestFileWithGlobs("/parent/bar/zet.jsp", "/parent/bar/zet.jsp");
+        doTestFileWithGlobs("/parent/bar/*.jsp", "/parent/bar");
+        doTestFileWithGlobs("/parent/*/zet.jsp", "/parent");
+        doTestFileWithGlobs("/parent/*/*.jsp", "/parent");
+        doTestFileWithGlobs("/parent/**/zet.jsp", "/parent");
+        doTestFileWithGlobs("/parent/**/*.jsp", "/parent");
+        doTestFileWithGlobs("/*/bar/*.jsp", "");
+        doTestFileWithGlobs("/**/bar/*.jsp", "");
+        doTestFileWithGlobs("/**/*.jsp", "");
+        doTestFileWithGlobs("**/*.jsp", "");
+    }
+
+    private void doTestFileWithGlobs(String globPath, String... 
expectedSubTrees)
+            throws RepositoryException, ItemExistsException, 
PathNotFoundException, NoSuchNodeTypeException,
+            LockException, VersionException, ConstraintViolationException, 
AccessDeniedException,
+            ReferentialIntegrityException, InvalidItemStateException, 
InterruptedException, ExecutionException {
+        assumeTrue(observationManager instanceof ObservationManagerImpl);
+        ObservationManagerImpl oManager = (ObservationManagerImpl) 
observationManager;
+        ExpectationListener listener = new ExpectationListener();
+        JackrabbitEventFilter filter = new JackrabbitEventFilter();
+        filter.setEventTypes(ALL_EVENTS);
+        filter = FilterFactory.wrap(filter)
+                .withIncludeGlobPaths(globPath);
+        oManager.addEventListener(listener, filter);
+        ChangeProcessor cp = oManager.getChangeProcessor(listener);
+        assertNotNull(cp);
+        FilterProvider filterProvider = cp.getFilterProvider();
+        assertNotNull(filterProvider);
+        assertArrayEquals(expectedSubTrees, 
Iterables.toArray(filterProvider.getSubTrees(), String.class));
+        
+        Node parent = getAdminSession().getRootNode().addNode("parent", 
"nt:unstructured");
+        Node bar = parent.addNode("bar", "nt:unstructured");
+        Node zetDotJsp = bar.addNode("zet.jsp", "nt:unstructured");
+        listener.expect(zetDotJsp.getPath() + "/jcr:primaryType", 
PROPERTY_ADDED);
+        
+        parent.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());
+        
+        Session session = getAdminSession();
+        session.getRootNode().getNode("parent").remove();
+        session.save();
+        oManager.removeEventListener(listener);
+    }
+
+    // OAK-5096 : a specific **/*.jsp test case
+    @Test
+    public void testAggregate6() throws Exception {
+        assumeTrue(observationManager instanceof ObservationManagerImpl);
+        ObservationManagerImpl oManager = (ObservationManagerImpl) 
observationManager;
+        ExpectationListener listener = new ExpectationListener();
+        JackrabbitEventFilter filter = new JackrabbitEventFilter();
+        filter.setEventTypes(ALL_EVENTS);
+        filter = FilterFactory.wrap(filter)
+                .withNodeTypeAggregate(new String[] { "oak:Unstructured" }, 
new String[] { "", "jcr:content" } )
+                .withIncludeGlobPaths("**/*.jsp");
+        oManager.addEventListener(listener, filter);
+        ChangeProcessor cp = oManager.getChangeProcessor(listener);
+        assertNotNull(cp);
+        FilterProvider filterProvider = cp.getFilterProvider();
+        assertNotNull(filterProvider);
+        
+        Node parent = getAdminSession().getRootNode().addNode("parent", 
"nt:unstructured");
+        Node bar = parent.addNode("bar", "nt:unstructured");
+        Node zetDotJsp = bar.addNode("zet.jsp", "nt:unstructured");
+        listener.expect(zetDotJsp.getPath() + "/jcr:primaryType", 
PROPERTY_ADDED);
+        Node c = bar.addNode("c", "nt:unstructured");
+        Node fooDotJsp = c.addNode("foo.jsp", "oak:Unstructured");
+        listener.expect(fooDotJsp.getPath() + "/jcr:primaryType", 
PROPERTY_ADDED);
+        Node jcrContent = fooDotJsp.addNode("jcr:content", "nt:unstructured");
+        jcrContent.setProperty("jcr:data", "foo");
+        listener.expectAdd(jcrContent);
+        listener.expect(jcrContent.getPath() + "/jcr:data", 
"/parent/bar/c/foo.jsp", PROPERTY_ADDED);
+        
+        parent.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());
+        
+        // OAK-5096 : this is what OAK-5096 is all about: when you change
+        // the property jcr:content/jcr:data it should be reported with 
+        // identifier of the aggregate - even though it is not in the original 
glob path
+        jcrContent.setProperty("jcr:data", "bar");
+        listener.expect(jcrContent.getPath() + "/jcr:data", 
"/parent/bar/c/foo.jsp", PROPERTY_CHANGED);
+        parent.getSession().save();
+
+        Thread.sleep(1000);
+        missing = listener.getMissing(TIME_OUT, TimeUnit.SECONDS);
+        unexpected = listener.getUnexpected();
+        assertTrue("Unexpected events: " + unexpected, unexpected.isEmpty());
+        assertTrue("Missing events: " + missing, missing.isEmpty());
+    }
 
     private void assertMatches(Iterable<String> actuals, String... expected) {
         assertEquals(newHashSet(expected), newHashSet(actuals));


Reply via email to