You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2014/03/25 08:06:14 UTC

svn commit: r1581264 - in /sling/trunk/bundles/jcr/resource: ./ src/main/java/org/apache/sling/jcr/resource/ src/main/java/org/apache/sling/jcr/resource/internal/ src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/ src/test/java/org/apache...

Author: cziegeler
Date: Tue Mar 25 07:06:13 2014
New Revision: 1581264

URL: http://svn.apache.org/r1581264
Log:
SLING-3279 : Leverage improved observation support from Oak

Added:
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java   (with props)
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ObservationListenerSupport.java   (with props)
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/package-info.java   (with props)
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java   (with props)
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java   (with props)
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousOakResourceListener.java   (with props)
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/JcrResourceProviderFactory.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
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousJcrResourceListener.java

Modified: sling/trunk/bundles/jcr/resource/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/pom.xml?rev=1581264&r1=1581263&r2=1581264&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/pom.xml (original)
+++ sling/trunk/bundles/jcr/resource/pom.xml Tue Mar 25 07:06:13 2014
@@ -84,17 +84,13 @@
                             org.apache.sling.api.resource;provide:=true,
                             org.apache.sling.commons.osgi;version="$(@)",
                             !org.apache.jackrabbit.api.observation,
+                            org.apache.jackrabbit.oak.plugins.observation;resolution:=optional,
+                            org.apache.jackrabbit.oak.spi.commit;resolution:=optional,
                             *
                         </Import-Package>
                         <DynamicImport-Package>
                             org.apache.jackrabbit.api.observation
                         </DynamicImport-Package>
-                        <Export-Package>
-                            org.apache.sling.jcr.resource;version=2.2
-                        </Export-Package>
-                        <Private-Package>
-                            org.apache.sling.jcr.resource.internal.*
-                        </Private-Package>
 
                         <!-- Include URL support from Jackrabbit -->
                         <Embed-Dependency>
@@ -123,7 +119,7 @@
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-api</artifactId>
-            <version>2.0.0</version>
+            <version>2.7.4</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -142,6 +138,7 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
+            <version>4.3.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
@@ -207,6 +204,12 @@
             <version>1.0.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-core</artifactId>
+            <version>0.19</version>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- For the Console Plugin of the JcrResourceResolverFactoryImpl -->
         <dependency>
@@ -224,7 +227,7 @@
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-jcr-commons</artifactId>
-            <version>2.0.0</version>
+            <version>2.7.4</version>
             <scope>provided</scope>
         </dependency>
 
@@ -247,5 +250,12 @@
             <version>1.4</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-jcr</artifactId>
+            <version>0.19</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 </project>

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=1581264&r1=1581263&r2=1581264&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 Mar 25 07:06:13 2014
@@ -18,10 +18,10 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
-import java.util.Dictionary;
+import java.io.Closeable;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Hashtable;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -29,24 +29,17 @@ import java.util.concurrent.LinkedBlocki
 
 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.jackrabbit.api.observation.JackrabbitEvent;
 import org.apache.sling.api.SlingConstants;
-import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.jcr.api.SlingRepository;
-import org.apache.sling.jcr.resource.JcrResourceConstants;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
 import org.osgi.service.event.EventAdmin;
 import org.osgi.service.event.EventConstants;
-import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.service.event.EventProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,24 +48,11 @@ import org.slf4j.LoggerFactory;
  * events and creates resource events which are sent through the
  * OSGi event admin.
  */
-public class JcrResourceListener implements EventListener {
+public class JcrResourceListener implements EventListener, Closeable {
 
     /** Logger */
     private final Logger logger = LoggerFactory.getLogger(JcrResourceListener.class);
 
-    private final ServiceTracker eventAdminTracker;
-
-    private ServiceReference resourceResolverFactoryReference;
-
-    /** The admin resource resolver. */
-    private ResourceResolver resourceResolver;
-
-    /** The session for observation. */
-    private final Session session;
-
-    /** Everything below this path is observed. */
-    private final String startPath;
-
     /** The repository is mounted under this path. */
     private final String mountPrefix;
 
@@ -85,20 +65,20 @@ public class JcrResourceListener impleme
      * waiting for actual dispatching to the OSGi Event Admin in
      * {@link #processOsgiEventQueue()}
      */
-    private final LinkedBlockingQueue<Dictionary<String, Object>> osgiEventQueue;
+    private final LinkedBlockingQueue<Map<String, Object>> osgiEventQueue;
 
-    private final BundleContext bundleContext;
+    /** Helper object. */
+    final ObservationListenerSupport support;
 
     /**
      * Marker event for {@link #processOsgiEventQueue()} to be signaled to
      * terminate processing Events.
      */
-    private final Dictionary<String, Object> TERMINATE_PROCESSING = new Hashtable<String, Object>(1);
+    private final Map<String, Object> TERMINATE_PROCESSING = new HashMap<String, Object>(1);
 
-    public JcrResourceListener(final String startPath,
+    public JcrResourceListener(
                     final String mountPrefix,
-                    final SlingRepository repository,
-                    final BundleContext bundleContext)
+                    final ObservationListenerSupport support)
     throws RepositoryException {
         boolean foundClass = false;
         try {
@@ -108,61 +88,38 @@ public class JcrResourceListener impleme
             // we ignore this
         }
         this.hasJackrabbitEventClass = foundClass;
-        this.startPath = startPath;
-        this.mountPrefix = mountPrefix;
-        this.bundleContext = bundleContext;
+        this.mountPrefix = (mountPrefix == null || mountPrefix.length() == 0 || mountPrefix.equals("/") ? null : mountPrefix);
 
-        this.eventAdminTracker = new ServiceTracker(bundleContext, EventAdmin.class.getName(), null);
-        this.eventAdminTracker.open();
+        this.support = support;
+        this.support.getSession().getWorkspace().getObservationManager().addEventListener(this,
+                        Event.NODE_ADDED|Event.NODE_REMOVED|Event.PROPERTY_ADDED|Event.PROPERTY_CHANGED|Event.PROPERTY_REMOVED,
+                        "/", true, null, null, false);
 
-        this.session = repository.loginAdministrative(null);
-        try {
-            session.getWorkspace().getObservationManager().addEventListener(this,
-                            Event.NODE_ADDED|Event.NODE_REMOVED|Event.PROPERTY_ADDED|Event.PROPERTY_CHANGED|Event.PROPERTY_REMOVED,
-                            this.startPath, true, null, null, false);
-        } catch (final RepositoryException re) {
-            session.logout();
-            throw re;
-        }
-
-        this.osgiEventQueue = new LinkedBlockingQueue<Dictionary<String,Object>>();
+        this.osgiEventQueue = new LinkedBlockingQueue<Map<String,Object>>();
         final Thread oeqt = new Thread(new Runnable() {
             public void run() {
                 processOsgiEventQueue();
             }
-        }, "Apache Sling JCR Resource Event Queue Processor for path '" + this.startPath + "'");
+        }, "Apache Sling JCR Resource Event Queue Processor");
         oeqt.start();
     }
 
     /**
      * Dispose this listener.
      */
-    public void deactivate() {
+    public void close() throws IOException {
         // unregister from observations
-        if ( this.session != null ) {
-            try {
-                this.session.getWorkspace().getObservationManager().removeEventListener(this);
-            } catch (RepositoryException e) {
-                logger.warn("Unable to remove session listener: " + this, e);
-            }
-            this.session.logout();
-        }
-        if ( this.resourceResolver != null ) {
-            this.resourceResolver.close();
-            this.resourceResolver = null;
-        }
-
-        if ( this.resourceResolverFactoryReference != null ) {
-            this.bundleContext.ungetService(this.resourceResolverFactoryReference);
+        try {
+            this.support.getSession().getWorkspace().getObservationManager().removeEventListener(this);
+        } catch (RepositoryException e) {
+            logger.warn("Unable to remove session listener: " + this, e);
         }
 
         // drop any remaining OSGi Events not processed yet
         this.osgiEventQueue.clear();
         this.osgiEventQueue.offer(TERMINATE_PROCESSING);
 
-        if ( this.eventAdminTracker != null ) {
-            this.eventAdminTracker.close();
-        }
+        this.support.dispose();
     }
 
     /**
@@ -170,13 +127,13 @@ public class JcrResourceListener impleme
      */
     public void onEvent(final EventIterator events) {
         // if the event admin is currently not available, we just skip this
-        final EventAdmin localEA = (EventAdmin) this.eventAdminTracker.getService();
+        final EventAdmin localEA = this.support.getEventAdmin();
         if ( localEA == null ) {
             return;
         }
-        final Map<String, Dictionary<String, Object>> addedEvents = new HashMap<String, Dictionary<String, Object>>();
+        final Map<String, Map<String, Object>> addedEvents = new HashMap<String, Map<String, Object>>();
         final Map<String, ChangedAttributes> changedEvents = new HashMap<String, ChangedAttributes>();
-        final Map<String, Dictionary<String, Object>> removedEvents = new HashMap<String, Dictionary<String, Object>>();
+        final Map<String, Map<String, Object>> removedEvents = new HashMap<String, Map<String, Object>>();
         while ( events.hasNext() ) {
             final Event event = events.nextEvent();
             try {
@@ -208,13 +165,13 @@ public class JcrResourceListener impleme
             }
         }
 
-        for (final Entry<String, Dictionary<String, Object>> e : removedEvents.entrySet()) {
+        for (final Entry<String, Map<String, Object>> e : removedEvents.entrySet()) {
             // Launch an OSGi event
             sendOsgiEvent(e.getKey(), e.getValue(), SlingConstants.TOPIC_RESOURCE_REMOVED,
                 null);
         }
 
-        for (final Entry<String, Dictionary<String, Object>> e : addedEvents.entrySet()) {
+        for (final Entry<String, Map<String, Object>> e : addedEvents.entrySet()) {
             // Launch an OSGi event.
             sendOsgiEvent(e.getKey(), e.getValue(), SlingConstants.TOPIC_RESOURCE_ADDED,
                 changedEvents.remove(e.getKey()));
@@ -229,9 +186,9 @@ public class JcrResourceListener impleme
 
     private static final class ChangedAttributes {
 
-        private final Dictionary<String, Object> properties;
+        private final Map<String, Object> properties;
 
-        public ChangedAttributes(final Dictionary<String, Object> properties) {
+        public ChangedAttributes(final Map<String, Object> properties) {
             this.properties = properties;
         }
 
@@ -270,7 +227,7 @@ public class JcrResourceListener impleme
          *            to.
          * @return The {@code properties} object is returned.
          */
-        public final Dictionary<String, Object> mergeAttributesInto(final Dictionary<String, Object> properties) {
+        public final Map<String, Object> mergeAttributesInto(final Map<String, Object> properties) {
             if ( addedAttributes != null )  {
                 properties.put(SlingConstants.PROPERTY_ADDED_ATTRIBUTES, addedAttributes.toArray(new String[addedAttributes.size()]));
             }
@@ -287,7 +244,7 @@ public class JcrResourceListener impleme
          * @return a {@code Dictionary} with all changes recorded including
          *         original JCR event information.
          */
-        public final Dictionary<String, Object> toEventProperties() {
+        public final Map<String, Object> toEventProperties() {
             return mergeAttributesInto(properties);
         }
     }
@@ -305,8 +262,8 @@ public class JcrResourceListener impleme
     /**
      * Create the base OSGi event properties based on the JCR event object
      */
-    private Dictionary<String, Object> createEventProperties(final Event event) {
-        final Dictionary<String, Object> properties = new Hashtable<String, Object>();
+    private Map<String, Object> createEventProperties(final Event event) {
+        final Map<String, Object> properties = new HashMap<String, Object>();
 
         if (this.isExternal(event)) {
             properties.put("event.application", "unknown");
@@ -328,7 +285,7 @@ public class JcrResourceListener impleme
      * @param topic The topic that should be used for the OSGi event.
      */
     private void sendOsgiEvent(final String path,
-            final Dictionary<String, Object> properties,
+            final Map<String, Object> properties,
             final String topic,
             final ChangedAttributes changedAttributes) {
 
@@ -345,37 +302,13 @@ public class JcrResourceListener impleme
     }
 
     /**
-     * Get a resource resolver.
-     * We don't need any syncing as this is called from the process OSGi thread.
-     */
-    private ResourceResolver getResourceResolver() {
-        if ( this.resourceResolver == null ) {
-            final ServiceReference ref = this.bundleContext.getServiceReference(ResourceResolverFactory.class.getName());
-            if ( ref != null ) {
-                final ResourceResolverFactory factory = (ResourceResolverFactory) this.bundleContext.getService(ref);
-                if ( factory != null ) {
-                    final Map<String, Object> authInfo = new HashMap<String, Object>();
-                    authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, this.session);
-                    try {
-                        this.resourceResolver = factory.getResourceResolver(authInfo);
-                        this.resourceResolverFactoryReference = ref;
-                    } catch (final LoginException le) {
-                        logger.error("Unable to get administrative resource resolver.", le);
-                        this.bundleContext.ungetService(ref);
-                    }
-                }
-            }
-        }
-        return this.resourceResolver;
-    }
-    /**
      * Called by the Runnable.run method of the JCR Event Queue processor to
      * process the {@link #osgiEventQueue} until the
      * {@link #TERMINATE_PROCESSING} event is received.
      */
     void processOsgiEventQueue() {
         while (true) {
-            final Dictionary<String, Object> event;
+            final Map<String, Object> event;
             try {
                 event = this.osgiEventQueue.take();
             } catch (InterruptedException e) {
@@ -388,8 +321,8 @@ public class JcrResourceListener impleme
             }
 
             try {
-                final EventAdmin localEa = (EventAdmin) this.eventAdminTracker.getService();
-                final ResourceResolver resolver = this.getResourceResolver();
+                final EventAdmin localEa = this.support.getEventAdmin();
+                final ResourceResolver resolver = this.support.getResourceResolver();
                 if (localEa != null && resolver != null ) {
                     final String topic = (String) event.remove(EventConstants.EVENT_TOPIC);
                     final String path = (String) event.get(SlingConstants.PROPERTY_PATH);
@@ -439,7 +372,7 @@ public class JcrResourceListener impleme
                     }
 
                     if ( sendEvent ) {
-                        localEa.sendEvent(new org.osgi.service.event.Event(topic, event));
+                        localEa.sendEvent(new org.osgi.service.event.Event(topic, new EventProperties(event)));
                     }
                 }
             } catch (final Exception e) {

Added: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java?rev=1581264&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java (added)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java Tue Mar 25 07:06:13 2014
@@ -0,0 +1,243 @@
+/*
+ * 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 static org.apache.sling.api.SlingConstants.PROPERTY_ADDED_ATTRIBUTES;
+import static org.apache.sling.api.SlingConstants.PROPERTY_CHANGED_ATTRIBUTES;
+import static org.apache.sling.api.SlingConstants.PROPERTY_PATH;
+import static org.apache.sling.api.SlingConstants.PROPERTY_REMOVED_ATTRIBUTES;
+import static org.apache.sling.api.SlingConstants.PROPERTY_RESOURCE_SUPER_TYPE;
+import static org.apache.sling.api.SlingConstants.PROPERTY_RESOURCE_TYPE;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_CHANGED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_REMOVED;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.oak.plugins.observation.NodeObserver;
+import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This {@code OakResourceListener} implementation translates and relays
+ * all events to the OSGi {@code EventAdmin}.
+ */
+public class OakResourceListener extends NodeObserver implements Closeable {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** The repository is mounted under this path. */
+    private final String mountPrefix;
+
+    private final ServiceRegistration serviceRegistration;
+
+    /** Helper object. */
+    final ObservationListenerSupport support;
+
+    public OakResourceListener(
+            final String mountPrefix,
+            final ObservationListenerSupport support,
+            final BundleContext bundleContext,
+            final Executor executor)
+    throws RepositoryException {
+        super("/", "jcr:primaryType", "sling:resourceType", "sling:resourceSuperType");
+        this.support = support;
+        this.mountPrefix = (mountPrefix == null || mountPrefix.length() == 0 || mountPrefix.equals("/") ? null : mountPrefix);
+
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+        props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling JCR Observation Listener for Oak");
+
+        final Observer observer = new BackgroundObserver(this, executor);
+        serviceRegistration = bundleContext.registerService(Observer.class.getName(), observer, props);
+    }
+
+    /**
+     * Dispose this listener.
+     */
+    public void close() throws IOException {
+        serviceRegistration.unregister();
+        this.support.dispose();
+    }
+
+    @Override
+    protected void added(final String path,
+            final Set<String> added,
+            final Set<String> deleted,
+            final Set<String> changed,
+            final Map<String, String> properties,
+            final CommitInfo commitInfo) {
+        final Map<String, Object> changes = toEventProperties(added, deleted, changed);
+        addCommitInfo(changes, commitInfo);
+        sendOsgiEvent(path, TOPIC_RESOURCE_ADDED, changes, properties);
+    }
+
+    @Override
+    protected void deleted(final String path,
+            final Set<String> added,
+            final Set<String> deleted,
+            final Set<String> changed,
+            final Map<String, String> properties,
+            final CommitInfo commitInfo) {
+        final Map<String, Object> changes = toEventProperties(added, deleted, changed);
+        addCommitInfo(changes, commitInfo);
+        sendOsgiEvent(path, TOPIC_RESOURCE_REMOVED, changes, properties);
+    }
+
+    @Override
+    protected void changed(final String path,
+            final Set<String> added,
+            final Set<String> deleted,
+            final Set<String> changed,
+            final Map<String, String> properties,
+            final CommitInfo commitInfo) {
+        final Map<String, Object> changes = toEventProperties(added, deleted, changed);
+        addCommitInfo(changes, commitInfo);
+        sendOsgiEvent(path, TOPIC_RESOURCE_CHANGED, changes, properties);
+    }
+
+    private static void addCommitInfo(final Map<String, Object> changes, final CommitInfo commitInfo) {
+        if ( commitInfo.getUserId() != null ) {
+            changes.put(SlingConstants.PROPERTY_USERID, commitInfo.getUserId());
+        }
+        if (commitInfo == CommitInfo.EMPTY) {
+            changes.put("event.application", "unknown");
+        }
+    }
+
+    private static Map<String, Object> toEventProperties(final Set<String> added, final Set<String> deleted, final Set<String> changed) {
+        final Map<String, Object> properties = new HashMap<String, Object>();
+        if ( added != null && added.size() > 0 ) {
+            properties.put(PROPERTY_ADDED_ATTRIBUTES, added.toArray(new String[added.size()]));
+        }
+        if ( changed != null && changed.size() > 0 ) {
+            properties.put(PROPERTY_CHANGED_ATTRIBUTES, changed.toArray(new String[changed.size()]));
+        }
+        if ( deleted != null && deleted.size() > 0 ) {
+            properties.put(PROPERTY_REMOVED_ATTRIBUTES, deleted.toArray(new String[deleted.size()]));
+        }
+        return properties;
+    }
+
+    private void sendOsgiEvent(final String path,
+            final String topic,
+            final Map<String, Object> changes,
+            final Map<String, String> properties) {
+        // set the path (will be changed for nt:file jcr:content sub resource)
+        if ( this.mountPrefix == null ) {
+            changes.put(PROPERTY_PATH, path);
+        } else {
+            changes.put(PROPERTY_PATH, this.mountPrefix + path);
+        }
+
+        try {
+            final EventAdmin localEa = this.support.getEventAdmin();
+            if (localEa != null ) {
+                boolean sendEvent = true;
+                if (!TOPIC_RESOURCE_REMOVED.equals(topic)) {
+                    String resourceType = properties.get("sling:resourceType");
+                    String resourceSuperType = properties.get("sling:resourceSuperType");
+                    String nodeType = properties.get("jcr:primaryType");
+
+                    // check for nt:file nodes
+                    if (path.endsWith("/jcr:content")) {
+                        final ResourceResolver resolver = this.support.getResourceResolver();
+                        if ( resolver == null ) {
+                            sendEvent = false;
+                        } else {
+                            final Resource rsrc = resolver.getResource((String)changes.get(PROPERTY_PATH));
+                            if ( rsrc == null ) {
+                                sendEvent = false;
+                            } else {
+                                // check if this is a JCR backed resource, otherwise it is not visible!
+                                final Node node = rsrc.adaptTo(Node.class);
+                                if (node != null) {
+                                    try {
+                                        if (node.getParent().isNodeType("nt:file")) {
+                                            final Resource parentResource = rsrc.getParent();
+                                            if (parentResource != null) {
+                                                // update resource type and path to parent node
+                                                resourceType = parentResource.getResourceType();
+                                                resourceSuperType = parentResource.getResourceSuperType();
+                                                changes.put(PROPERTY_PATH, parentResource.getPath());
+                                            }
+                                        }
+                                    } catch (RepositoryException re) {
+                                        // ignore this
+                                    }
+
+                                } else {
+                                    // this is not a jcr backed resource
+                                    sendEvent = false;
+                                }
+
+                            }
+                        }
+                        if ( !sendEvent ) {
+                            // take a quite silent note of not being able to
+                            // resolve the resource
+                            logger.debug(
+                                "processOsgiEventQueue: Resource at {} not found, which is not expected for an added or modified node",
+                                    path);
+                        }
+                    }
+
+                    // update resource type properties
+                    if ( sendEvent ) {
+                        if ( resourceType == null ) {
+                            changes.put(PROPERTY_RESOURCE_TYPE, nodeType.replace(':', '/'));
+                        } else {
+                            changes.put(PROPERTY_RESOURCE_TYPE, resourceType);
+                        }
+                        if ( resourceSuperType != null ) {
+                            changes.put(PROPERTY_RESOURCE_SUPER_TYPE, resourceSuperType);
+                        }
+                    }
+                }
+
+                if ( sendEvent ) {
+                    localEa.sendEvent(new org.osgi.service.event.Event(topic, new EventProperties(changes)));
+                }
+            }
+        } catch (final Exception e) {
+            logger.warn("sendOsgiEvent: Unexpected problem processing event " + topic + " at " + path + " with " + changes, e);
+        }
+    }
+}

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

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

Propchange: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ObservationListenerSupport.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ObservationListenerSupport.java?rev=1581264&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ObservationListenerSupport.java (added)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ObservationListenerSupport.java Tue Mar 25 07:06:13 2014
@@ -0,0 +1,119 @@
+package org.apache.sling.jcr.resource.internal;
+/*
+ * 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.
+ */
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.JcrResourceConstants;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Support object for the different observation implementations
+ */
+public class ObservationListenerSupport  {
+
+    /** Logger */
+    private final Logger logger = LoggerFactory.getLogger(ObservationListenerSupport.class);
+
+    private final ServiceTracker eventAdminTracker;
+
+    private ServiceReference resourceResolverFactoryReference;
+
+    /** The admin resource resolver. */
+    private ResourceResolver resourceResolver;
+
+    private final BundleContext bundleContext;
+
+    private final Session session;
+
+    public ObservationListenerSupport(final BundleContext bundleContext, final SlingRepository repository)
+    throws RepositoryException {
+        this.bundleContext = bundleContext;
+
+        this.eventAdminTracker = new ServiceTracker(bundleContext, EventAdmin.class.getName(), null);
+        this.eventAdminTracker.open();
+
+        this.session = repository.loginAdministrative(null);
+    }
+
+    /**
+     * Dispose this support object.
+     */
+    public void dispose() {
+        if ( this.resourceResolver != null ) {
+            this.resourceResolver.close();
+            this.resourceResolver = null;
+        }
+
+        if ( this.resourceResolverFactoryReference != null ) {
+            this.bundleContext.ungetService(this.resourceResolverFactoryReference);
+            this.resourceResolverFactoryReference = null;
+        }
+        this.eventAdminTracker.close();
+
+        this.session.logout();
+    }
+
+    public Session getSession() {
+        return this.session;
+    }
+
+    public EventAdmin getEventAdmin() {
+        return (EventAdmin) this.eventAdminTracker.getService();
+    }
+
+    /**
+     * Get a resource resolver.
+     * We don't need any syncing as this is called from the process OSGi thread.
+     */
+    public ResourceResolver getResourceResolver() {
+        if ( this.resourceResolver == null ) {
+            final ServiceReference ref = this.bundleContext.getServiceReference(ResourceResolverFactory.class.getName());
+            if ( ref != null ) {
+                final ResourceResolverFactory factory = (ResourceResolverFactory) this.bundleContext.getService(ref);
+                if ( factory != null ) {
+                    final Map<String, Object> authInfo = new HashMap<String, Object>();
+                    authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, this.session);
+                    try {
+                        this.resourceResolver = factory.getResourceResolver(authInfo);
+                        this.resourceResolverFactoryReference = ref;
+                    } catch (final LoginException le) {
+                        logger.error("Unable to get administrative resource resolver.", le);
+                        this.bundleContext.ungetService(ref);
+                    }
+                }
+            }
+        }
+        return this.resourceResolver;
+    }
+}

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

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

Propchange: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ObservationListenerSupport.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java?rev=1581264&r1=1581263&r2=1581264&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java Tue Mar 25 07:06:13 2014
@@ -18,11 +18,15 @@
  */
 package org.apache.sling.jcr.resource.internal.helper.jcr;
 
+import java.io.Closeable;
+import java.io.IOException;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 import javax.jcr.Credentials;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
@@ -48,6 +52,8 @@ import org.apache.sling.commons.osgi.Pro
 import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.resource.JcrResourceConstants;
 import org.apache.sling.jcr.resource.internal.JcrResourceListener;
+import org.apache.sling.jcr.resource.internal.OakResourceListener;
+import org.apache.sling.jcr.resource.internal.ObservationListenerSupport;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -60,20 +66,28 @@ import org.slf4j.LoggerFactory;
  * The <code>JcrResourceProviderFactory</code> creates
  * resource providers based on JCR.
  */
-@Component
+@Component(metatype = true,
+           label = "Apache Sling JCR Resource Provider Factory",
+           description = "This provider adds  JCR resources to the resource tree")
 @Service(value = ResourceProviderFactory.class )
 @Properties({
-    @Property(name=ResourceProviderFactory.PROPERTY_REQUIRED, boolValue=true),
-    @Property(name=ResourceProvider.ROOTS, value="/"),
+    @Property(name=ResourceProviderFactory.PROPERTY_REQUIRED, boolValue=true, propertyPrivate=true),
+    @Property(name=ResourceProvider.ROOTS, value="/", propertyPrivate=true),
     @Property(name = Constants.SERVICE_DESCRIPTION, value = "Apache Sling JCR Resource Provider Factory"),
     @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
-    @Property(name = QueriableResourceProvider.LANGUAGES, value = {Query.XPATH, Query.SQL, Query.JCR_SQL2})
+    @Property(name = QueriableResourceProvider.LANGUAGES, value = {Query.XPATH, Query.SQL, Query.JCR_SQL2}, propertyPrivate=true)
 })
 public class JcrResourceProviderFactory implements ResourceProviderFactory {
 
-    /** default log */
+    /** Logger */
     private final Logger log = LoggerFactory.getLogger(getClass());
 
+    private static final boolean DEFAULT_OPTIMIZE_FOR_OAK = true;
+    @Property(boolValue=DEFAULT_OPTIMIZE_FOR_OAK,
+              label="Optimize For Oak",
+              description="If this switch is enabled, and Oak is used as the repository implementation, some optimized components are used.")
+    private static final String PROPERTY_OPTIMIZE_FOR_OAK = "optimize.oak";
+
     private static final String REPOSITORY_REFERNENCE_NAME = "repository";
 
     /** The dynamic class loader */
@@ -85,8 +99,12 @@ public class JcrResourceProviderFactory 
 
     private SlingRepository repository;
 
-    /** The jcr resource listner. */
-    private JcrResourceListener listener;
+    /** This service is only available on OAK, therefore optional and static) */
+    @Reference(policy=ReferencePolicy.STATIC, cardinality=ReferenceCardinality.OPTIONAL_UNARY)
+    private Executor executor;
+
+    /** The JCR observation listener. */
+    private Closeable listener;
 
     @Activate
     protected void activate(final ComponentContext context) throws RepositoryException {
@@ -101,16 +119,53 @@ public class JcrResourceProviderFactory 
             return;
         }
 
-        final String root = PropertiesUtil.toString(context.getProperties().get(ResourceProvider.ROOTS), "/");
-
         this.repository = repository;
-        this.listener = new JcrResourceListener(root, null, this.repository, context.getBundleContext());
+        // check for Oak
+        final boolean optimizeForOak = PropertiesUtil.toBoolean(context.getProperties().get(PROPERTY_OPTIMIZE_FOR_OAK), DEFAULT_OPTIMIZE_FOR_OAK);
+        boolean isOak = false;
+        if ( optimizeForOak ) {
+            final String repoDesc = this.repository.getDescriptor(Repository.REP_NAME_DESC);
+            if ( repoDesc != null && repoDesc.toLowerCase().contains(" oak") ) {
+                if ( this.executor != null ) {
+                    isOak = true;
+                } else {
+                   log.error("Detected Oak based repository but no executor service available! Unable to use improved JCR Resource listener");
+                }
+            }
+        }
+        final String root = PropertiesUtil.toString(context.getProperties().get(ResourceProvider.ROOTS), "/");
+        final ObservationListenerSupport support = new ObservationListenerSupport(context.getBundleContext(), repository);
+        boolean closeSupport = true;
+        try {
+            if ( isOak ) {
+                try {
+                    this.listener = new OakResourceListener(root, support, context.getBundleContext(), executor);
+                    log.info("Detected Oak based repository. Using improved JCR Resource Listener");
+                } catch ( final RepositoryException re ) {
+                    throw re;
+                } catch ( final Throwable t ) {
+                    log.error("Unable to instantiate improved JCR Resource listener for Oak. Using fallback.", t);
+                }
+            }
+            if ( this.listener == null ) {
+                this.listener = new JcrResourceListener(root, support);
+            }
+            closeSupport = false;
+        } finally {
+            if ( closeSupport ) {
+                support.dispose();
+            }
+        }
     }
 
     @Deactivate
     protected void deactivate() {
         if ( this.listener != null ) {
-            this.listener.deactivate();
+            try {
+                this.listener.close();
+            } catch (final IOException e) {
+                // ignore this as the method above does not throw it
+            }
             this.listener = null;
         }
     }

Added: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/package-info.java?rev=1581264&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/package-info.java (added)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/package-info.java Tue Mar 25 07:06:13 2014
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.2")
+package org.apache.sling.jcr.resource;
+
+import aQute.bnd.annotation.Version;
+

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

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

Propchange: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/package-info.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java?rev=1581264&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java (added)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java Tue Mar 25 07:06:13 2014
@@ -0,0 +1,135 @@
+/*
+ * 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 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.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+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.SlingConstants;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+/**
+ * Base for the two different listener tests.
+ */
+public abstract class AbstractListenerTest {
+
+    private String createdPath = "/test" + System.currentTimeMillis() + "-create";
+
+    private String pathToDelete = "/test" + System.currentTimeMillis() + "-delete";
+
+    private String pathToModify = "/test" + System.currentTimeMillis() + "-modify";
+
+    private final List<Event> events = synchronizedList(new ArrayList<Event>());
+
+    protected void addEvent(final Event e) {
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        for(final String name : e.getPropertyNames()) {
+            props.put(name, e.getProperty(name));
+        }
+        this.events.add(new Event(e.getTopic(), props) {
+
+            @Override
+            public String toString() {
+                return "Event(topic=" + e.getTopic() + ", properties=" + props + ")";
+            }
+        });
+    }
+
+    @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 Event event : events) {
+            if ( event.getTopic().equals(SlingConstants.TOPIC_RESOURCE_ADDED) ) {
+                addPaths.add((String)event.getProperty(SlingConstants.PROPERTY_PATH));
+            } else if ( event.getTopic().equals(SlingConstants.TOPIC_RESOURCE_CHANGED) ) {
+                modifyPaths.add((String)event.getProperty(SlingConstants.PROPERTY_PATH));
+            } else if ( event.getTopic().equals(SlingConstants.TOPIC_RESOURCE_REMOVED) ) {
+                removePaths.add((String)event.getProperty(SlingConstants.PROPERTY_PATH));
+            } else {
+                fail("Unexpected event: " + event);
+            }
+            assertNotNull(event.getProperty(SlingConstants.PROPERTY_USERID));
+        }
+        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));
+    }
+
+    private static void createNode(final Session session, final String path) throws RepositoryException {
+        session.getRootNode().addNode(path.substring(1), "nt:unstructured");
+        session.save();
+    }
+
+    private void generateEvents() throws Exception {
+        final Session session = 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();
+        }
+    }
+
+    public abstract SlingRepository getRepository();
+}

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

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

Propchange: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

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=1581264&r1=1581263&r2=1581264&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 Mar 25 07:06:13 2014
@@ -65,7 +65,7 @@ public class JcrResourceListenerScalabil
         when(bundleContext.getServiceReferences(anyString(), anyString())).thenReturn(serviceRefs);
         when(bundleContext.getService(serviceRef)).thenReturn(eventAdmin);
 
-        jcrResourceListener = new JcrResourceListener("/", "/", repository, bundleContext);
+        jcrResourceListener = new JcrResourceListener("/", new ObservationListenerSupport(bundleContext, repository));
 
         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=1581264&r1=1581263&r2=1581264&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 Mar 25 07:06:13 2014
@@ -20,20 +20,15 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
-import org.apache.sling.api.SlingConstants;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.commons.testing.jcr.EventHelper;
-import org.apache.sling.commons.testing.jcr.RepositoryTestBase;
 import org.apache.sling.commons.testing.jcr.RepositoryUtil;
+import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.resource.internal.helper.jcr.JcrTestNodeResource;
+import org.junit.After;
+import org.junit.Before;
 import org.mockito.Mockito;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.event.Event;
@@ -43,140 +38,62 @@ import org.osgi.util.tracker.ServiceTrac
 /**
  * Test of JcrResourceListener.
  */
-public class JcrResourceListenerTest extends RepositoryTestBase {
-    private String createdPath;
+public class JcrResourceListenerTest extends AbstractListenerTest {
 
-    private String pathToDelete;
+    private SynchronousJcrResourceListener listener;
 
-    private String pathToModify;
+    private Session adminSession;
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
+        if ( adminSession != null ) {
+            adminSession.logout();
+            adminSession = null;
+        }
         RepositoryUtil.stopRepository();
+        if ( listener != null ) {
+            listener.dispose();
+            listener = null;
+        }
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         RepositoryUtil.startRepository();
-        final Session adminSession = RepositoryUtil.getRepository().loginAdministrative(null);
+        this.adminSession = RepositoryUtil.getRepository().loginAdministrative(null);
         RepositoryUtil.registerSlingNodeTypes(adminSession);
-        adminSession.logout();
-    }
-
-    public void testDefaultWorkspace() throws Exception {
-        List<Event> events = generateEvents(null);
-
-        assertTrue("Received: " + events, events.size() >= 3);
-        Event event = events.get(0);
-        assertEquals(SlingConstants.TOPIC_RESOURCE_ADDED, event.getTopic());
-        assertEquals(createdPath, event.getProperty(SlingConstants.PROPERTY_PATH));
-        assertNotNull(event.getProperty(SlingConstants.PROPERTY_USERID));
-
-        event = events.get(1);
-        assertEquals(SlingConstants.TOPIC_RESOURCE_CHANGED, event.getTopic());
-        assertEquals(pathToModify, event.getProperty(SlingConstants.PROPERTY_PATH));
-        assertNotNull(event.getProperty(SlingConstants.PROPERTY_USERID));
-
-        event = events.get(2);
-        assertEquals(SlingConstants.TOPIC_RESOURCE_REMOVED, event.getTopic());
-        assertEquals(pathToDelete, event.getProperty(SlingConstants.PROPERTY_PATH));
-        assertNotNull(event.getProperty(SlingConstants.PROPERTY_USERID));
-
-    }
-
-    private static void createNode(Session session, String path) throws RepositoryException {
-        session.getRootNode().addNode(path.substring(1), "nt:unstructured");
-        session.save();
-    }
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.adaptTo(Mockito.any(Class.class))).thenReturn(this.adminSession);
+        Mockito.when(resolver.getResource(Mockito.anyString())).thenReturn(new JcrTestNodeResource(resolver, this.adminSession.getNode("/"), null));
 
+        final ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getAdministrativeResourceResolver(Mockito.anyMap())).thenReturn(resolver);
 
-    private void addNodeToDelete(Session session) throws RepositoryException {
-        pathToDelete = createTestPath();
-        createNode(session, pathToDelete);
+        final EventAdmin mockEA = new EventAdmin() {
 
-    }
+            public void postEvent(final Event event) {
+                addEvent(event);
+            }
 
-    private void addNodeToModify(Session session) throws RepositoryException {
-        pathToModify = createTestPath();
-        createNode(session, pathToModify);
+            public void sendEvent(final Event event) {
+                addEvent(event);
+            }
+        };
 
-    }
+        final ServiceTracker tracker = mock(ServiceTracker.class);
+        when(tracker.getService()).thenReturn(mockEA);
 
-    int counter = 0;
+        final BundleContext bundleContext = mock(BundleContext.class);
+        when(bundleContext.createFilter(any(String.class))).thenReturn(null);
+        when(bundleContext.getServiceReference(any(String.class))).thenReturn(null);
+        when(bundleContext.getService(null)).thenReturn(mockEA);
 
-    private String createTestPath() {
-        counter++;
-        return "/test" + System.currentTimeMillis() + counter;
+        this.listener = new SynchronousJcrResourceListener(RepositoryUtil.getRepository(),
+                        bundleContext, resolver, tracker);
     }
 
-    private List<Event> generateEvents(String workspaceName) throws Exception {
-        final Session session = getRepository().loginAdministrative(workspaceName);
-
-        try {
-            final List<Event> events = new ArrayList<Event>();
-
-            addNodeToModify(session);
-            addNodeToDelete(session);
-
-            final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
-            Mockito.when(resolver.adaptTo(Mockito.any(Class.class))).thenReturn(session);
-            Mockito.when(resolver.getResource(Mockito.anyString())).thenReturn(new JcrTestNodeResource(resolver, session.getNode("/"), null));
-
-            final ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
-            Mockito.when(factory.getAdministrativeResourceResolver(Mockito.anyMap())).thenReturn(resolver);
-
-            final EventAdmin mockEA = new EventAdmin() {
-
-                public void postEvent(Event event) {
-                    events.add(event);
-                }
-
-                public void sendEvent(Event event) {
-                    events.add(event);
-                }
-            };
-
-            final ServiceTracker tracker = mock(ServiceTracker.class);
-            when(tracker.getService()).thenReturn(mockEA);
-
-            final BundleContext bundleContext = mock(BundleContext.class);
-            when(bundleContext.createFilter(any(String.class))).thenReturn(null);
-            when(bundleContext.getServiceReference(any(String.class))).thenReturn(null);
-            when(bundleContext.getService(null)).thenReturn(mockEA);
-
-            final SynchronousJcrResourceListener listener = new SynchronousJcrResourceListener(factory, getRepository(),
-                            bundleContext, resolver, tracker);
-            Session newSession = null;
-            try {
-                createdPath = createTestPath();
-                createNode(session, createdPath);
-
-                Node modified = session.getNode(pathToModify);
-                modified.setProperty("foo", "bar");
-
-                session.save();
-
-                Node deleted = session.getNode(pathToDelete);
-                deleted.remove();
-                session.save();
-
-                newSession = getRepository().loginAdministrative(workspaceName);
-
-                EventHelper helper = new EventHelper(newSession);
-                helper.waitForEvents(5000);
-                helper.dispose();
-            } finally {
-                listener.dispose();
-                if ( newSession != null ) {
-                    newSession.logout();
-                }
-            }
-            return events;
-        } finally {
-            session.logout();
-        }
-
+    @Override
+    public SlingRepository getRepository() {
+        return RepositoryUtil.getRepository();
     }
 }

Added: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java?rev=1581264&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java (added)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java Tue Mar 25 07:06:13 2014
@@ -0,0 +1,123 @@
+/*
+ * 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 static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerObserver;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyMap;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Dictionary;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStore;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.testing.jcr.RepositoryUtil.RepositoryWrapper;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.internal.helper.jcr.JcrTestNodeResource;
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+/**
+ * Test of OakResourceListener.
+ */
+public class OakResourceListenerTest extends AbstractListenerTest {
+
+    private Session session;
+    private SynchronousOakResourceListener listener;
+    private ExecutorService executor;
+    private Whiteboard whiteboard;
+    private SlingRepository slingRepository;
+
+    @Before
+    public void setUp() throws Exception {
+        executor = Executors.newSingleThreadExecutor();
+        final Oak oak = new Oak(new SegmentNodeStore());
+        this.whiteboard = oak.getWhiteboard();
+        final Repository repository = new Jcr(oak).createRepository();
+        this.slingRepository = new RepositoryWrapper(repository);
+
+        session = this.slingRepository.loginAdministrative(null);
+
+        ResourceResolver resolver = mock(ResourceResolver.class);
+        when(resolver.adaptTo(any(Class.class))).thenReturn(session);
+        when(resolver.getResource(anyString())).thenReturn(new JcrTestNodeResource(resolver, session.getNode("/"), null));
+
+        ResourceResolverFactory factory = mock(ResourceResolverFactory.class);
+        when(factory.getAdministrativeResourceResolver(anyMap())).thenReturn(resolver);
+
+        EventAdmin mockEA = new EventAdmin() {
+            public void postEvent(Event event) {
+                addEvent(event);
+            }
+
+            public void sendEvent(Event event) {
+                addEvent(event);
+            }
+        };
+
+        ServiceTracker tracker = mock(ServiceTracker.class);
+        when(tracker.getService()).thenReturn(mockEA);
+
+        BundleContext bundleContext = mock(BundleContext.class);
+        when(bundleContext.createFilter(any(String.class))).thenReturn(null);
+        when(bundleContext.getServiceReference(any(String.class))).thenReturn(null);
+        when(bundleContext.getService(null)).thenReturn(mockEA);
+        when(bundleContext.registerService(any(String.class), any(Object.class), any(Dictionary.class)))
+                .thenAnswer(new Answer<ServiceRegistration>() {
+                    public ServiceRegistration answer(InvocationOnMock invocation) throws Throwable {
+                        Object[] arguments = invocation.getArguments();
+                        registerObserver(whiteboard, (Observer) arguments[1]);
+                        return mock(ServiceRegistration.class);
+                    }
+                });
+
+        listener = new SynchronousOakResourceListener(
+                this.slingRepository, bundleContext, resolver, tracker, executor);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        listener.dispose();
+        session.logout();
+        executor.shutdown();
+    }
+
+    @Override
+    public SlingRepository getRepository() {
+        return slingRepository;
+    }
+}

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

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

Propchange: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousJcrResourceListener.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousJcrResourceListener.java?rev=1581264&r1=1581263&r2=1581264&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousJcrResourceListener.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousJcrResourceListener.java Tue Mar 25 07:06:13 2014
@@ -16,6 +16,8 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
+import java.io.IOException;
+
 import javax.jcr.RepositoryException;
 
 import junitx.util.PrivateAccessor;
@@ -23,7 +25,6 @@ import junitx.util.PrivateAccessor;
 import org.apache.jackrabbit.core.observation.SynchronousEventListener;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.osgi.framework.BundleContext;
 import org.osgi.util.tracker.ServiceTracker;
@@ -38,18 +39,22 @@ import org.osgi.util.tracker.ServiceTrac
 public class SynchronousJcrResourceListener extends JcrResourceListener implements SynchronousEventListener {
 
     public SynchronousJcrResourceListener(
-            ResourceResolverFactory factory,
-            SlingRepository repo,
-            BundleContext bundleContext,
+            final SlingRepository repo,
+            final BundleContext bundleContext,
             final ResourceResolver resolver,
             final ServiceTracker tracker)
             throws LoginException, RepositoryException, NoSuchFieldException {
-        super("/", null, repo, bundleContext);
-        PrivateAccessor.setField(this, "resourceResolver", resolver);
-        PrivateAccessor.setField(this, "eventAdminTracker", tracker);
+        super("/", new ObservationListenerSupport(bundleContext, repo));
+        PrivateAccessor.setField(this.support, "resourceResolver", resolver);
+        PrivateAccessor.setField(this.support, "eventAdminTracker", tracker);
     }
 
     public void dispose() {
-        this.deactivate();
+        try {
+            this.close();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
     }
 }

Added: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousOakResourceListener.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousOakResourceListener.java?rev=1581264&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousOakResourceListener.java (added)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousOakResourceListener.java Tue Mar 25 07:06:13 2014
@@ -0,0 +1,61 @@
+/*
+ * 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.IOException;
+import java.util.concurrent.Executor;
+
+import javax.jcr.RepositoryException;
+
+import junitx.util.PrivateAccessor;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * This class is used to ensure that events are handled during the test.
+ *
+ * TODO - Ideally, this wouldn't be necessary, but EventHelper doesn't seem
+ * to be working 100% of the time.
+ *
+ */
+public class SynchronousOakResourceListener extends OakResourceListener {
+
+    public SynchronousOakResourceListener(
+            final SlingRepository repo,
+            final BundleContext bundleContext,
+            final ResourceResolver resolver,
+            final ServiceTracker tracker,
+            final Executor executor)
+            throws LoginException, RepositoryException, NoSuchFieldException {
+        super("/", new ObservationListenerSupport(bundleContext, repo), bundleContext, executor);
+        PrivateAccessor.setField(this.support, "resourceResolver", resolver);
+        PrivateAccessor.setField(this.support, "eventAdminTracker", tracker);
+    }
+
+    public void dispose() {
+        try {
+            this.close();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+}

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

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

Propchange: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousOakResourceListener.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain