Author: cziegeler
Date: Tue Oct 18 12:23:59 2016
New Revision: 1765425

URL: http://svn.apache.org/viewvc?rev=1765425&view=rev
Log:
SLING-6056 : achieve 1:1 mapping between observation and resource change 
listener

Added:
    
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrListenerBaseConfig.java
   (with props)
Removed:
    
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java
Modified:
    sling/trunk/bundles/jcr/resource/pom.xml
    
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java
    
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
    
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerScalabilityTest.java
    
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java

Modified: sling/trunk/bundles/jcr/resource/pom.xml
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/pom.xml?rev=1765425&r1=1765424&r2=1765425&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/pom.xml (original)
+++ sling/trunk/bundles/jcr/resource/pom.xml Tue Oct 18 12:23:59 2016
@@ -81,7 +81,6 @@
                     <instructions>
                         <Import-Package>
                             
org.apache.sling.scripting.api.*;resolution:=optional,
-                            
org.apache.jackrabbit.api.observation;resolution:=optional,
                             *
                         </Import-Package>
 

Added: 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrListenerBaseConfig.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrListenerBaseConfig.java?rev=1765425&view=auto
==============================================================================
--- 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrListenerBaseConfig.java
 (added)
+++ 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrListenerBaseConfig.java
 Tue Oct 18 12:23:59 2016
@@ -0,0 +1,157 @@
+/*
+ * 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.sling.jcr.resource.internal;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.ObservationManager;
+
+import org.apache.jackrabbit.api.observation.JackrabbitEventFilter;
+import org.apache.jackrabbit.api.observation.JackrabbitObservationManager;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.path.Path;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.internal.helper.jcr.PathMapper;
+import org.apache.sling.spi.resource.provider.ObservationReporter;
+import org.apache.sling.spi.resource.provider.ObserverConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the base configuration for a JCR listener, shared
+ * by all registered {@link JcrResourceListener}s.
+ */
+public class JcrListenerBaseConfig implements Closeable {
+
+    private final Logger logger = 
LoggerFactory.getLogger(JcrResourceListener.class);
+
+    private final Session session;
+
+    private final PathMapper pathMapper;
+
+    private final ObservationReporter reporter;
+
+    @SuppressWarnings("deprecation")
+    public JcrListenerBaseConfig(
+                    final ObservationReporter reporter,
+                    final PathMapper pathMapper,
+                    final SlingRepository repository)
+    throws RepositoryException {
+        this.pathMapper = pathMapper;
+        this.reporter = reporter;
+        this.session = 
repository.loginAdministrative(repository.getDefaultWorkspace());
+    }
+
+    /**
+     * Dispose this config
+     * Close session.
+     */
+    @Override
+    public void close() throws IOException {
+        this.session.logout();
+    }
+
+    public void register(final JcrResourceListener listener, final 
ObserverConfiguration config)
+    throws RepositoryException {
+        final ObservationManager mgr = 
this.session.getWorkspace().getObservationManager();
+        if ( mgr instanceof JackrabbitObservationManager ) {
+            final JackrabbitEventFilter filter = new JackrabbitEventFilter();
+
+            // paths
+            final Set<String> paths = config.getPaths().toStringSet();
+            final String[] pathArray = new String[paths.size()];
+            int i=0;
+            // remove global prefix
+            for(final String p : paths) {
+                pathArray[i] = (p.startsWith(Path.GLOB_PREFIX) ? 
p.substring(Path.GLOB_PREFIX.length()) : p);
+                i++;
+            }
+            filter.setAdditionalPaths(pathArray);
+            filter.setIsDeep(true);
+
+            // exclude paths
+            final Set<String> excludePaths = 
config.getExcludedPaths().toStringSet();
+            if ( !excludePaths.isEmpty() ) {
+                filter.setExcludedPaths(excludePaths.toArray(new 
String[excludePaths.size()]));
+            }
+
+            // external
+            filter.setNoExternal(config.includeExternal());
+
+            // types
+            filter.setEventTypes(this.getTypes(config));
+
+            ((JackrabbitObservationManager)mgr).addEventListener(listener, 
filter);
+        } else {
+            throw new RepositoryException("Observation manager is not a 
JackrabbitObservationManager");
+        }
+
+    }
+
+    private int getTypes(final ObserverConfiguration c) {
+        int result = 0;
+        for (ChangeType t : c.getChangeTypes()) {
+            switch (t) {
+            case ADDED:
+                result = result | Event.NODE_ADDED;
+                break;
+            case REMOVED:
+                result = result | Event.NODE_REMOVED;
+                break;
+            case CHANGED:
+                result = result | Event.PROPERTY_ADDED;
+                result = result | Event.PROPERTY_CHANGED;
+                result = result | Event.PROPERTY_REMOVED;
+                break;
+            default:
+                break;
+            }
+        }
+        return result;
+    }
+
+    public void unregister(final JcrResourceListener listener) {
+        try {
+            
this.session.getWorkspace().getObservationManager().removeEventListener(listener);
+        } catch (RepositoryException e) {
+            logger.warn("Unable to remove session listener: " + this, e);
+        }
+    }
+
+    public Logger getLogger() {
+        return this.logger;
+    }
+
+    public ObservationReporter getReporter() {
+        return this.reporter;
+    }
+
+    public PathMapper getPathMapper() {
+        return this.pathMapper;
+    }
+
+    public Session getSession() {
+        return this.session;
+    }
+}

Propchange: 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrListenerBaseConfig.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrListenerBaseConfig.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Modified: 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java?rev=1765425&r1=1765424&r2=1765425&view=diff
==============================================================================
--- 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java
 (original)
+++ 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java
 Tue Oct 18 12:23:59 2016
@@ -28,97 +28,40 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
-import javax.jcr.Session;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.EventListener;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.observation.JackrabbitEvent;
 import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
-import org.apache.sling.api.resource.path.Path;
-import org.apache.sling.api.resource.path.PathSet;
-import org.apache.sling.jcr.api.SlingRepository;
-import org.apache.sling.jcr.resource.internal.helper.jcr.PathMapper;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
 import org.apache.sling.spi.resource.provider.ObserverConfiguration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * The <code>JcrResourceListener</code> listens for JCR observation
- * events and creates resource events which are sent through the
- * OSGi event admin.
+ * events and creates resource change events which are sent through
+ * the {@link ObservationReporter}.
  */
 public class JcrResourceListener implements EventListener, Closeable {
 
-    /** Logger */
-    private final Logger logger = 
LoggerFactory.getLogger(JcrResourceListener.class);
+    private final ObserverConfiguration config;
 
-    /** The repository is mounted under this path. */
-   private final String mountPrefix;
+    private final JcrListenerBaseConfig baseConfig;
 
-    /** Is the Jackrabbit event class available? */
-    private final boolean hasJackrabbitEventClass;
-
-    private final Session session;
-
-    private final PathMapper pathMapper;
-
-    private final boolean includeExternal;
-
-    private final PathSet excludedPaths;
-
-    private final ObservationReporter reporter;
-
-    @SuppressWarnings("deprecation")
-    public JcrResourceListener(
-                    final ObservationReporter reporter,
-                    final List<ObserverConfiguration> configs,
-                    final PathSet excludedPaths,
-                    final String mountPrefix,
-                    final PathMapper pathMapper,
-                    final SlingRepository repository)
+    public JcrResourceListener(final JcrListenerBaseConfig listenerConfig,
+                    final ObserverConfiguration config)
     throws RepositoryException {
-        this.includeExternal = isIncludeExternal(configs);
-        this.excludedPaths = excludedPaths;
-        this.pathMapper = pathMapper;
-        this.mountPrefix = mountPrefix;
-        this.reporter = reporter;
-        boolean foundClass = false;
-        try {
-            
this.getClass().getClassLoader().loadClass(JackrabbitEvent.class.getName());
-            foundClass = true;
-        } catch (final Throwable t) {
-            // we ignore this
-        }
-        this.hasJackrabbitEventClass = foundClass;
-        this.session = 
repository.loginAdministrative(repository.getDefaultWorkspace());
-        // TODO - as paths can have glob patterns, we need to update the 
algorithm
-        // final String absPath = getAbsPath(pathMapper, configs, 
excludedPaths);
-        final String absPath = "/";
-        final int types = getTypes(configs);
-        
this.session.getWorkspace().getObservationManager().addEventListener(this, 
types, absPath, true, null, null, false);
-    }
-
-    private boolean isIncludeExternal(final List<ObserverConfiguration> 
configs) {
-        for (ObserverConfiguration c : configs) {
-            if (c.includeExternal()) {
-                return true;
-            }
-        }
-        return false;
+        this.baseConfig = listenerConfig;
+        this.config = config;
+        this.baseConfig.register(this, config);
     }
 
     /**
@@ -127,12 +70,7 @@ public class JcrResourceListener impleme
     @Override
     public void close() throws IOException {
         // unregister from observations
-        try {
-            
this.session.getWorkspace().getObservationManager().removeEventListener(this);
-        } catch (RepositoryException e) {
-            logger.warn("Unable to remove session listener: " + this, e);
-        }
-        this.session.logout();
+        this.baseConfig.unregister(this);
     }
 
     /**
@@ -147,9 +85,6 @@ public class JcrResourceListener impleme
         AtomicBoolean refreshedSession = new AtomicBoolean(false);
         while ( events.hasNext() ) {
             final Event event = events.nextEvent();
-            if (isExternal(event) && !includeExternal) {
-                continue;
-            }
 
             try {
                 final String eventPath = event.getPath();
@@ -162,27 +97,24 @@ public class JcrResourceListener impleme
                     final String rsrcPath = 
stripNtFilePath(eventPath.substring(0, lastSlash), refreshedSession);
                     if ( !addedEvents.containsKey(rsrcPath)
                       && !removedEvents.containsKey(rsrcPath)
-                      && !changedEvents.containsKey(rsrcPath)
-                      && this.excludedPaths.matches(rsrcPath) == null) {
+                      && !changedEvents.containsKey(rsrcPath) ) {
 
                         changedEvents.put(rsrcPath, 
createResourceChange(event, rsrcPath, ChangeType.CHANGED));
                     }
                 } else {
-                    if ( this.excludedPaths.matches(eventPath) == null ) {
-                        if ( type == NODE_ADDED ) {
-                            // add is stronger than update
-                            changedEvents.remove(eventPath);
-                            addedEvents.put(eventPath, 
createResourceChange(event, eventPath, ChangeType.ADDED));
-                        } else if ( type == NODE_REMOVED) {
-                            // remove is stronger than add and change
-                            addedEvents.remove(eventPath);
-                            changedEvents.remove(eventPath);
-                            removedEvents.put(eventPath, 
createResourceChange(event, eventPath, ChangeType.REMOVED));
-                        }
+                    if ( type == NODE_ADDED ) {
+                        // add is stronger than update
+                        changedEvents.remove(eventPath);
+                        addedEvents.put(eventPath, createResourceChange(event, 
eventPath, ChangeType.ADDED));
+                    } else if ( type == NODE_REMOVED) {
+                        // remove is stronger than add and change
+                        addedEvents.remove(eventPath);
+                        changedEvents.remove(eventPath);
+                        removedEvents.put(eventPath, 
createResourceChange(event, eventPath, ChangeType.REMOVED));
                     }
                 }
             } catch (final RepositoryException e) {
-                logger.error("Error during modification: {}", e);
+                this.baseConfig.getLogger().error("Error during modification: 
{}", e);
             }
         }
 
@@ -190,15 +122,14 @@ public class JcrResourceListener impleme
         changes.addAll(addedEvents.values());
         changes.addAll(removedEvents.values());
         changes.addAll(changedEvents.values());
-        this.reporter.reportChanges(changes, false);
+        this.baseConfig.getReporter().reportChanges(this.config, changes, 
false);
 
     }
 
     private ResourceChange createResourceChange(final Event event,
             final String path,
             final ChangeType changeType) {
-        final String pathWithPrefix = addMountPrefix(mountPrefix, path);
-        final String fullPath = 
pathMapper.mapJCRPathToResourcePath(pathWithPrefix);
+        final String fullPath = 
this.baseConfig.getPathMapper().mapJCRPathToResourcePath(path);
         final boolean isExternal = this.isExternal(event);
         final String userId;
         if (!isExternal) {
@@ -210,94 +141,13 @@ public class JcrResourceListener impleme
     }
 
     private boolean isExternal(final Event event) {
-        if ( this.hasJackrabbitEventClass && event instanceof JackrabbitEvent) 
{
+        if ( event instanceof JackrabbitEvent) {
             final JackrabbitEvent jEvent = (JackrabbitEvent)event;
             return jEvent.isExternal();
         }
         return false;
     }
 
-    static String getAbsPath(PathMapper pathMapper,
-            final List<ObserverConfiguration> configs,
-            final PathSet excludedPaths) {
-        final Set<String> paths = new HashSet<String>();
-        for (final ObserverConfiguration c : configs) {
-            final Set<String> includePaths = new HashSet<String>();
-            final Set<String> excludePaths = new HashSet<String>();
-            for (Path p : c.getExcludedPaths()) {
-                
excludePaths.add(pathMapper.mapResourcePathToJCRPath(p.getPath()));
-            }
-            for (Path p : c.getPaths()) {
-                
includePaths.add(pathMapper.mapResourcePathToJCRPath(p.getPath()));
-            }
-            includePaths.removeAll(excludePaths);
-            paths.addAll(includePaths);
-        }
-        for (Path p : excludedPaths) {
-            paths.remove(pathMapper.mapResourcePathToJCRPath(p.getPath()));
-        }
-        return getLongestCommonPrefix(paths);
-    }
-
-    private static String getLongestCommonPrefix(Set<String> paths) {
-        String prefix = null;
-        Iterator<String> it = paths.iterator();
-        if (it.hasNext()) {
-            prefix = it.next();
-        }
-        while (it.hasNext()) {
-            prefix = getCommonPrefix(prefix, it.next());
-        }
-        return StringUtils.defaultIfEmpty(prefix, "/");
-    }
-
-    private static String getCommonPrefix(String s1, String s2) {
-        int length = Math.min(s1.length(), s2.length());
-        StringBuilder prefix = new StringBuilder(length);
-        for (int i = 0; i < length; i++) {
-            if (s1.charAt(i) == s2.charAt(i)) {
-                prefix.append(s1.charAt(i));
-            } else {
-                break;
-            }
-        }
-        return prefix.toString();
-    }
-
-    private int getTypes(final List<ObserverConfiguration> configs) {
-        int result = 0;
-        for (ObserverConfiguration c : configs) {
-            for (ChangeType t : c.getChangeTypes()) {
-                switch (t) {
-                case ADDED:
-                    result = result | Event.NODE_ADDED;
-                    break;
-                case REMOVED:
-                    result = result | Event.NODE_REMOVED;
-                    break;
-                case CHANGED:
-                    result = result | Event.PROPERTY_ADDED;
-                    result = result | Event.PROPERTY_CHANGED;
-                    result = result | Event.PROPERTY_REMOVED;
-                    break;
-                default:
-                    break;
-                }
-            }
-        }
-        return result;
-    }
-
-    static String addMountPrefix(final String mountPrefix, final String path) {
-        final String result;
-        if (mountPrefix == null || mountPrefix.isEmpty() || 
"/".equals(mountPrefix)) {
-            result = path;
-        } else {
-            result = new StringBuilder(mountPrefix).append(path).toString();
-        }
-        return result;
-    }
-
     private static final String JCR_CONTENT_POSTFIX = "/" + 
JcrConstants.JCR_CONTENT;
 
     private String stripNtFilePath(final String path, final AtomicBoolean 
refreshedSession) {
@@ -306,9 +156,9 @@ public class JcrResourceListener impleme
         }
         try {
             if ( refreshedSession.compareAndSet(false, true) ) {
-                session.refresh(false);
+                baseConfig.getSession().refresh(false);
             }
-            final Node node = session.getNode(path);
+            final Node node = baseConfig.getSession().getNode(path);
             final Node parent = node.getParent();
             if (parent.isNodeType(JcrConstants.NT_FILE)) {
                 return parent.getPath();
@@ -320,4 +170,8 @@ public class JcrResourceListener impleme
         }
     }
 
+    @Override
+    public String toString() {
+        return "JcrResourceListener [" + config + "]";
+    }
 }

Modified: 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java?rev=1765425&r1=1765424&r2=1765425&view=diff
==============================================================================
--- 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
 (original)
+++ 
sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
 Tue Oct 18 12:23:59 2016
@@ -22,9 +22,9 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.security.Principal;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -57,10 +57,10 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
-import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.base.LoginAdminWhitelist;
 import org.apache.sling.jcr.resource.api.JcrResourceConstants;
+import org.apache.sling.jcr.resource.internal.JcrListenerBaseConfig;
 import org.apache.sling.jcr.resource.internal.JcrModifiableValueMap;
 import org.apache.sling.jcr.resource.internal.JcrResourceListener;
 import org.apache.sling.jcr.resource.internal.NodeUtil;
@@ -116,12 +116,13 @@ public class JcrResourceProvider extends
     @Reference(policy=ReferencePolicy.STATIC, 
cardinality=ReferenceCardinality.OPTIONAL_UNARY)
     private Executor executor;
 
-    /** The JCR observation listener. */
-    private volatile Closeable listener;
+    /** The JCR listener base configuration. */
+    private volatile JcrListenerBaseConfig listenerConfig;
 
-    private volatile SlingRepository repository;
+    /** The JCR observation listeners. */
+    private volatile Map<ObserverConfiguration, Closeable> listeners = new 
HashMap<>();
 
-    private volatile String root;
+    private volatile SlingRepository repository;
 
     private volatile JcrProviderStateFactory stateFactory;
 
@@ -140,7 +141,6 @@ public class JcrResourceProvider extends
         }
 
         this.repository = repository;
-        this.root = 
PropertiesUtil.toString(context.getProperties().get(ResourceProvider.PROPERTY_ROOT),
 "/");
 
         this.stateFactory = new JcrProviderStateFactory(repositoryReference, 
repository,
                 classLoaderManagerReference, pathMapper, loginAdminWhitelist);
@@ -162,20 +162,19 @@ public class JcrResourceProvider extends
     @Override
     public void start(final ProviderContext ctx) {
         super.start(ctx);
-        registerListener(ctx);
+        this.registerListeners();
     }
 
     @Override
     public void stop() {
-        unregisterListener();
+        this.unregisterListeners();
         super.stop();
     }
 
     @Override
-    public void update(long changeSet) {
+    public void update(final long changeSet) {
         super.update(changeSet);
-        unregisterListener();
-        registerListener(getProviderContext());
+        this.updateListeners();
     }
 
     @SuppressWarnings("unused")
@@ -192,30 +191,59 @@ public class JcrResourceProvider extends
         }
     }
 
-    private void registerListener(final ProviderContext ctx) {
+    /**
+     * Register all observation listeners.
+     */
+    private void registerListeners() {
         if ( this.repository != null ) {
-            final List<ObserverConfiguration> configs = 
ctx.getObservationReporter().getObserverConfigurations();
-            if ( !configs.isEmpty() ) {
-                try {
-                    this.listener = new 
JcrResourceListener(ctx.getObservationReporter(),
-                            configs,
-                            ctx.getExcludedPaths(),
-                            root, pathMapper, repository);
-                } catch (RepositoryException e) {
-                    throw new SlingException("Can't create the listener", e);
+            try {
+                this.listenerConfig = new 
JcrListenerBaseConfig(this.getProviderContext().getObservationReporter(),
+                    this.pathMapper,
+                    this.repository);
+                for(final ObserverConfiguration config : 
this.getProviderContext().getObservationReporter().getObserverConfigurations()) 
{
+                    final Closeable listener = new 
JcrResourceListener(this.listenerConfig,
+                            config);
+                    this.listeners.put(config, listener);
                 }
+            } catch (final RepositoryException e) {
+                throw new SlingException("Can't create the JCR event 
listener.", e);
             }
         }
     }
 
-    private void unregisterListener() {
-        if ( this.listener != null ) {
+    /**
+     * Unregister all observation listeners.
+     */
+    private void unregisterListeners() {
+        for(final Closeable c : this.listeners.values()) {
             try {
-                this.listener.close();
+                c.close();
             } catch (final IOException e) {
                 // ignore this as the method above does not throw it
             }
-            this.listener = null;
+        }
+        this.listeners.clear();
+        if ( this.listenerConfig != null ) {
+            try {
+                this.listenerConfig.close();
+            } catch (final IOException e) {
+                // ignore this as the method above does not throw it
+            }
+            this.listenerConfig = null;
+        }
+    }
+
+    /**
+     * Update observation listeners.
+     */
+    private void updateListeners() {
+        if ( this.listenerConfig == null ) {
+            this.unregisterListeners();
+            this.registerListeners();
+        } else {
+            // TODO this can be optimized
+            this.unregisterListeners();
+            this.registerListeners();
         }
     }
 

Modified: 
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerScalabilityTest.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerScalabilityTest.java?rev=1765425&r1=1765424&r2=1765425&view=diff
==============================================================================
--- 
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerScalabilityTest.java
 (original)
+++ 
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerScalabilityTest.java
 Tue Oct 18 12:23:59 2016
@@ -51,7 +51,10 @@ import org.osgi.framework.InvalidSyntaxE
  */
 public class JcrResourceListenerScalabilityTest {
 
+    private JcrListenerBaseConfig config;
+
     private JcrResourceListener jcrResourceListener;
+
     private EventIterator events;
 
     @SuppressWarnings("deprecation")
@@ -69,10 +72,10 @@ public class JcrResourceListenerScalabil
         when(repository.loginAdministrative(null)).thenReturn(session);
 
         final ProviderContext ctx = new SimpleProviderContext();
-        jcrResourceListener = new 
JcrResourceListener(ctx.getObservationReporter(),
-                ctx.getObservationReporter().getObserverConfigurations(),
-                ctx.getExcludedPaths(),
-                "/", new PathMapperImpl(), RepositoryUtil.getRepository());
+        this.config = new JcrListenerBaseConfig(ctx.getObservationReporter(),
+                new PathMapperImpl(),
+                RepositoryUtil.getRepository());
+        jcrResourceListener = new JcrResourceListener(this.config, 
ctx.getObservationReporter().getObserverConfigurations().get(0));
 
         Event event = mock(MockEvent.class);
         events = mock(EventIterator.class);

Modified: 
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java?rev=1765425&r1=1765424&r2=1765425&view=diff
==============================================================================
--- 
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java
 (original)
+++ 
sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java
 Tue Oct 18 12:23:59 2016
@@ -16,23 +16,65 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
+import static java.util.Collections.synchronizedList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.api.resource.path.PathSet;
 import org.apache.sling.commons.testing.jcr.RepositoryUtil;
-import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.spi.resource.provider.ObservationReporter;
+import org.apache.sling.spi.resource.provider.ObserverConfiguration;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Test;
 
 /**
  * Test of JcrResourceListener.
  */
-public class JcrResourceListenerTest extends AbstractListenerTest {
+public class JcrResourceListenerTest {
+
+    private JcrListenerBaseConfig config;
 
     private JcrResourceListener listener;
 
     private Session adminSession;
 
+    private String createdPath = "/test" + System.currentTimeMillis() + 
"-create";
+
+    private String pathToDelete = "/test" + System.currentTimeMillis() + 
"-delete";
+
+    private String pathToModify = "/test" + System.currentTimeMillis() + 
"-modify";
+
+    private final List<ResourceChange> events = synchronizedList(new 
ArrayList<ResourceChange>());
+
+    @SuppressWarnings("deprecation")
+    @Before
+    public void setUp() throws Exception {
+        RepositoryUtil.startRepository();
+        this.adminSession = 
RepositoryUtil.getRepository().loginAdministrative(null);
+        RepositoryUtil.registerSlingNodeTypes(adminSession);
+        this.config = new JcrListenerBaseConfig(getObservationReporter(),
+                new PathMapperImpl(),
+                RepositoryUtil.getRepository());
+        this.listener = new JcrResourceListener(this.config,
+                getObservationReporter().getObserverConfigurations().get(0));
+    }
+
     @After
     public void tearDown() throws Exception {
         if ( adminSession != null ) {
@@ -44,23 +86,235 @@ public class JcrResourceListenerTest ext
             listener.close();
             listener = null;
         }
+        if ( config != null ) {
+            config.close();
+            config = null;
+        }
     }
 
-    @SuppressWarnings("deprecation")
-    @Before
-    public void setUp() throws Exception {
-        RepositoryUtil.startRepository();
-        this.adminSession = 
RepositoryUtil.getRepository().loginAdministrative(null);
-        RepositoryUtil.registerSlingNodeTypes(adminSession);
-        this.listener = new JcrResourceListener(
-                JcrResourceListenerTest.this.getObservationReporter(),
-                
JcrResourceListenerTest.this.getObservationReporter().getObserverConfigurations(),
-                PathSet.fromPaths(),
-                "/", new PathMapperImpl(), RepositoryUtil.getRepository());
+    @Test public void testSimpleOperations() throws Exception {
+        generateEvents();
+
+        assertEquals("Received: " + events, 5, events.size());
+        final Set<String> addPaths = new HashSet<String>();
+        final Set<String> modifyPaths = new HashSet<String>();
+        final Set<String> removePaths = new HashSet<String>();
+
+        for (final ResourceChange event : events) {
+            if (event.getType() == ChangeType.ADDED) {
+                addPaths.add(event.getPath());
+            } else if (event.getType() == ChangeType.CHANGED) {
+                modifyPaths.add(event.getPath());
+            } else if (event.getType() == ChangeType.REMOVED) {
+                removePaths.add(event.getPath());
+            } else {
+                fail("Unexpected event: " + event);
+            }
+            assertNotNull(event.getUserId());
+        }
+        assertEquals(3, addPaths.size());
+        assertTrue("Added set should contain " + createdPath, 
addPaths.contains(createdPath));
+        assertTrue("Added set should contain " + pathToDelete, 
addPaths.contains(pathToDelete));
+        assertTrue("Added set should contain " + pathToModify, 
addPaths.contains(pathToModify));
+
+        assertEquals(1, modifyPaths.size());
+        assertTrue("Modified set should contain " + pathToModify, 
modifyPaths.contains(pathToModify));
+
+        assertEquals(1, removePaths.size());
+        assertTrue("Removed set should contain " + pathToDelete, 
removePaths.contains(pathToDelete));
+    }
+
+    @Test
+    public void testMultiplePaths() throws Exception {
+        ObserverConfiguration observerConfig = new ObserverConfiguration() {
+
+            @Override
+            public boolean includeExternal() {
+                return true;
+            }
+
+            @Override
+            public PathSet getPaths() {
+                return PathSet.fromStrings("/libs", "/apps");
+            }
+
+            @Override
+            public PathSet getExcludedPaths() {
+                return PathSet.fromPaths();
+            }
+
+            @Override
+            public Set<ChangeType> getChangeTypes() {
+                return EnumSet.allOf(ChangeType.class);
+            }
+
+            @Override
+            public boolean matches(String path) {
+                return this.getPaths().matches(path) != null;
+            }
+
+            @Override
+            public Set<String> getPropertyNamesHint() {
+                return null;
+            }
+        };
+        this.config.unregister(this.listener);
+        this.listener = null;
+        final Session session = this.config.getSession();
+        if ( !session.nodeExists("/libs") ) {
+            createNode(session, "/libs");
+        }
+        if ( !session.nodeExists("/apps") ) {
+            createNode(session, "/apps");
+        }
+        session.getNode("/libs").addNode("foo" + System.currentTimeMillis());
+        session.getNode("/apps").addNode("foo" + System.currentTimeMillis());
+
+        session.save();
+
+        Thread.sleep(200);
+
+        this.events.clear();
+
+        try ( final JcrResourceListener l = new 
JcrResourceListener(this.config, observerConfig)) {
+            final String rootName = "test_" + System.currentTimeMillis();
+            for ( final String path : new String[] {"/libs", "/", "/apps", 
"/content"}) {
+                final Node parent;
+                if ( !session.nodeExists(path) ) {
+                    parent = createNode(session, path);
+                } else {
+                    parent = session.getNode(path);
+                }
+                final Node node = parent.addNode(rootName, "nt:unstructured");
+                session.save();
+
+                node.setProperty("foo", "bar");
+                session.save();
+
+                node.remove();
+                session.save();
+            }
+            assertEquals("Received: " + events, 6, events.size());
+            final Set<String> addPaths = new HashSet<String>();
+            final Set<String> modifyPaths = new HashSet<String>();
+            final Set<String> removePaths = new HashSet<String>();
+
+            for (final ResourceChange event : events) {
+                if (event.getType() == ChangeType.ADDED) {
+                    addPaths.add(event.getPath());
+                } else if (event.getType() == ChangeType.CHANGED) {
+                    modifyPaths.add(event.getPath());
+                } else if (event.getType() == ChangeType.REMOVED) {
+                    removePaths.add(event.getPath());
+                } else {
+                    fail("Unexpected event: " + event);
+                }
+                assertNotNull(event.getUserId());
+            }
+            assertEquals("Received: " + addPaths, 2, addPaths.size());
+            assertTrue("Added set should contain /libs/" + rootName, 
addPaths.contains("/libs/" + rootName));
+            assertTrue("Added set should contain /apps/" + rootName, 
addPaths.contains("/apps/" + rootName));
+
+            assertEquals("Received: " + modifyPaths, 2, modifyPaths.size());
+            assertTrue("Modified set should contain /libs/" + rootName, 
modifyPaths.contains("/libs/" + rootName));
+            assertTrue("Modified set should contain /apps/" + rootName, 
modifyPaths.contains("/apps/" + rootName));
+
+            assertEquals("Received: " + removePaths, 2, removePaths.size());
+            assertTrue("Removed set should contain /libs/" + rootName, 
removePaths.contains("/libs/" + rootName));
+            assertTrue("Removed set should contain /apps/" + rootName, 
removePaths.contains("/apps/" + rootName));
+        }
     }
 
-    @Override
-    public SlingRepository getRepository() {
-        return RepositoryUtil.getRepository();
+    private static Node createNode(final Session session, final String path) 
throws RepositoryException {
+        final Node n = session.getRootNode().addNode(path.substring(1), 
"nt:unstructured");
+        session.save();
+        return n;
     }
+
+    private void generateEvents() throws Exception {
+        @SuppressWarnings("deprecation")
+        final Session session = 
RepositoryUtil.getRepository().loginAdministrative(null);
+
+        try {
+            // create the nodes
+            createNode(session, createdPath);
+            createNode(session, pathToModify);
+            createNode(session, pathToDelete);
+
+            Thread.sleep(1000);
+
+            // modify
+            final Node modified = session.getNode(pathToModify);
+            modified.setProperty("foo", "bar");
+
+            session.save();
+
+            // delete
+            final Node deleted = session.getNode(pathToDelete);
+            deleted.remove();
+            session.save();
+
+            Thread.sleep(3500);
+
+        } finally {
+            session.logout();
+        }
+    }
+
+    protected ObservationReporter getObservationReporter() {
+        return new SimpleObservationReporter();
+    }
+
+    private class SimpleObservationReporter implements ObservationReporter {
+
+        @Override
+        public void reportChanges(Iterable<ResourceChange> changes, boolean 
distribute) {
+            for (ResourceChange c : changes) {
+                events.add(c);
+            }
+        }
+
+        @Override
+        public List<ObserverConfiguration> getObserverConfigurations() {
+            ObserverConfiguration config = new ObserverConfiguration() {
+
+                @Override
+                public boolean includeExternal() {
+                    return true;
+                }
+
+                @Override
+                public PathSet getPaths() {
+                    return PathSet.fromStrings("/");
+                }
+
+                @Override
+                public PathSet getExcludedPaths() {
+                    return PathSet.fromPaths();
+                }
+
+                @Override
+                public Set<ChangeType> getChangeTypes() {
+                    return EnumSet.allOf(ChangeType.class);
+                }
+
+                @Override
+                public boolean matches(String path) {
+                    return true;
+                }
+
+                @Override
+                public Set<String> getPropertyNamesHint() {
+                    return new HashSet<String>();
+                }
+            };
+            return Collections.singletonList(config);
+        }
+
+        @Override
+        public void reportChanges(ObserverConfiguration config, 
Iterable<ResourceChange> changes, boolean distribute) {
+            this.reportChanges(changes, distribute);
+        }
+    }
+
 }



Reply via email to