You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by to...@apache.org on 2016/01/28 14:41:33 UTC

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

Author: tomekr
Date: Thu Jan 28 13:41:32 2016
New Revision: 1727345

URL: http://svn.apache.org/viewvc?rev=1727345&view=rev
Log:
SLING-5163 Rewrite the JCR Resource to post events using the new observation API

Added:
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceChange.java
Removed:
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ObservationListenerSupport.java
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousJcrResourceListener.java
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/SynchronousOakResourceListener.java
Modified:
    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/OakResourceListener.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/AbstractListenerTest.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/OakResourceListenerTest.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/OsgiObservationBridge.java
    sling/trunk/launchpad/builder/src/main/provisioning/sling.txt

Added: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceChange.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceChange.java?rev=1727345&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceChange.java (added)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceChange.java Thu Jan 28 13:41:32 2016
@@ -0,0 +1,149 @@
+/*
+ * 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.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.sling.api.resource.observation.ResourceChange;
+
+public class JcrResourceChange extends ResourceChange {
+
+    private final String userId;
+
+    private JcrResourceChange(Builder builder) {
+        super(builder.changeType, builder.path, builder.isExternal, builder.addedAttributeNames, builder.changedAttributeNames, builder.removedAttributeNames);
+        this.userId = builder.userId;
+    }
+
+    @Override
+    public String getUserId() {
+        return userId;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder();
+        b.append("ResourceChange[type=").append(this.getType()).append(", path=").append(this.getPath());
+        if (getAddedPropertyNames() != null && !getAddedPropertyNames().isEmpty()) {
+            b.append(", added=").append(getAddedPropertyNames());
+        }
+        if (getChangedPropertyNames() != null && !getChangedPropertyNames().isEmpty()) {
+            b.append(", changed=").append(getChangedPropertyNames());
+        }
+        if (getRemovedPropertyNames() != null && !getRemovedPropertyNames().isEmpty()) {
+            b.append(", removed=").append(getRemovedPropertyNames());
+        }
+        b.append("]");
+        return b.toString();
+    }
+
+    public static class Builder {
+
+        private String path;
+
+        private ChangeType changeType;
+
+        private boolean isExternal;
+
+        private String userId;
+
+        private Set<String> changedAttributeNames;
+
+        private Set<String> addedAttributeNames;
+
+        private Set<String> removedAttributeNames;
+
+        public String getPath() {
+            return path;
+        }
+
+        public void setPath(String path) {
+            this.path = path;
+        }
+
+        public ChangeType getChangeType() {
+            return changeType;
+        }
+
+        public void setChangeType(ChangeType changeType) {
+            this.changeType = changeType;
+        }
+
+        public boolean isExternal() {
+            return isExternal;
+        }
+
+        public void setExternal(boolean isExternal) {
+            this.isExternal = isExternal;
+        }
+
+        public String getUserId() {
+            return userId;
+        }
+
+        public void setUserId(String userId) {
+            this.userId = userId;
+        }
+
+        public Set<String> getChangedAttributeNames() {
+            return changedAttributeNames;
+        }
+
+        public void addChangedAttributeName(String propName) {
+            if (changedAttributeNames == null) {
+                changedAttributeNames = new LinkedHashSet<String>();
+            }
+            if (!changedAttributeNames.contains(propName)) {
+                changedAttributeNames.add(propName);
+            }
+        }
+
+        public Set<String> getAddedAttributeNames() {
+            return addedAttributeNames;
+        }
+
+        public void addAddedAttributeName(String propName) {
+            if (addedAttributeNames == null) {
+                addedAttributeNames = new LinkedHashSet<String>();
+            }
+            if (!addedAttributeNames.contains(propName)) {
+                addedAttributeNames.add(propName);
+            }
+        }
+
+        public Set<String> getRemovedAttributeNames() {
+            return removedAttributeNames;
+        }
+
+        public void addRemovedAttributeName(String propName) {
+            if (removedAttributeNames == null) {
+                removedAttributeNames = new LinkedHashSet<String>();
+            }
+            if (!removedAttributeNames.contains(propName)) {
+                removedAttributeNames.add(propName);
+            }
+        }
+
+        public ResourceChange build() {
+            return new JcrResourceChange(this);
+        }
+    }
+
+}
\ No newline at end of file

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=1727345&r1=1727344&r2=1727345&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 Thu Jan 28 13:41:32 2016
@@ -18,29 +18,42 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
+import static javax.jcr.observation.Event.NODE_ADDED;
+import static javax.jcr.observation.Event.NODE_REMOVED;
+import static javax.jcr.observation.Event.PROPERTY_ADDED;
+import static javax.jcr.observation.Event.PROPERTY_CHANGED;
+import static javax.jcr.observation.Event.PROPERTY_REMOVED;
+
 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.Map.Entry;
 import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
 
 import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
 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.SlingConstants;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.util.Path;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.internal.JcrResourceChange.Builder;
 import org.apache.sling.jcr.resource.internal.helper.jcr.PathMapper;
-import org.osgi.service.event.EventAdmin;
-import org.osgi.service.event.EventConstants;
-import org.osgi.service.event.EventProperties;
+import org.apache.sling.spi.resource.provider.ObserverConfiguration;
+import org.apache.sling.spi.resource.provider.ProviderContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,36 +68,30 @@ public class JcrResourceListener impleme
     private final Logger logger = LoggerFactory.getLogger(JcrResourceListener.class);
 
     /** The repository is mounted under this path. */
-    private final String mountPrefix;
+   private final String mountPrefix;
 
     /** Is the Jackrabbit event class available? */
     private final boolean hasJackrabbitEventClass;
 
-    /**
-     * A queue of OSGi Events created by
-     * {@link #sendOsgiEvent(String, Event, String, EventAdmin, ChangedAttributes)}
-     * waiting for actual dispatching to the OSGi Event Admin in
-     * {@link #processOsgiEventQueue()}
-     */
-    private final LinkedBlockingQueue<Map<String, Object>> osgiEventQueue;
-
-    /** Helper object. */
-    final ObservationListenerSupport support;
+    private final Session session;
 
     private final PathMapper pathMapper;
 
-    /**
-     * Marker event for {@link #processOsgiEventQueue()} to be signaled to
-     * terminate processing Events.
-     */
-    private final Map<String, Object> TERMINATE_PROCESSING = new HashMap<String, Object>(1);
+    private final ProviderContext ctx;
 
+    private final boolean includeExternal;
+
+    @SuppressWarnings("deprecation")
     public JcrResourceListener(
+                    final ProviderContext ctx,
                     final String mountPrefix,
-                    final ObservationListenerSupport support,
-                    final PathMapper pathMapper)
+                    final PathMapper pathMapper,
+                    final SlingRepository repository)
     throws RepositoryException {
+        this.includeExternal = isIncludeExternal(ctx);
         this.pathMapper = pathMapper;
+        this.mountPrefix = mountPrefix;
+        this.ctx = ctx;
         boolean foundClass = false;
         try {
             this.getClass().getClassLoader().loadClass(JackrabbitEvent.class.getName());
@@ -93,21 +100,19 @@ public class JcrResourceListener impleme
             // we ignore this
         }
         this.hasJackrabbitEventClass = foundClass;
-        this.mountPrefix = (mountPrefix == null || mountPrefix.length() == 0 || mountPrefix.equals("/") ? null : mountPrefix);
+        this.session = repository.loginAdministrative(repository.getDefaultWorkspace());
+        final String absPath = getAbsPath(pathMapper, ctx);
+        final int types = getTypes(ctx);
+        this.session.getWorkspace().getObservationManager().addEventListener(this, types, absPath, true, null, null, false);
+    }
 
-        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.osgiEventQueue = new LinkedBlockingQueue<Map<String,Object>>();
-        final Thread oeqt = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                processOsgiEventQueue();
+    private boolean isIncludeExternal(ProviderContext ctx) {
+        for (ObserverConfiguration c : ctx.getObservationReporter().getObserverConfigurations()) {
+            if (c.includeExternal()) {
+                return true;
             }
-        }, "Apache Sling JCR Resource Event Queue Processor");
-        oeqt.start();
+        }
+        return false;
     }
 
     /**
@@ -117,16 +122,11 @@ public class JcrResourceListener impleme
     public void close() throws IOException {
         // unregister from observations
         try {
-            this.support.getSession().getWorkspace().getObservationManager().removeEventListener(this);
+            this.session.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);
-
-        this.support.dispose();
+        this.session.logout();
     }
 
     /**
@@ -134,273 +134,224 @@ public class JcrResourceListener impleme
      */
     @Override
     public void onEvent(final EventIterator events) {
-        // if the event admin is currently not available, we just skip this
-        final EventAdmin localEA = this.support.getEventAdmin();
-        if ( localEA == null ) {
-            return;
-        }
-        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, Map<String, Object>> removedEvents = new HashMap<String, Map<String, Object>>();
+        final Map<String, Builder> addedEvents = new HashMap<String, Builder>();
+        final Map<String, Builder> changedEvents = new HashMap<String, Builder>();
+        final Map<String, Builder> removedEvents = new HashMap<String, Builder>();
+
         while ( events.hasNext() ) {
             final Event event = events.nextEvent();
+            if (isExternal(event) && !includeExternal) {
+                continue;
+            }
+
             try {
-                final String eventPath;
-                if ( this.mountPrefix != null ) {
-                    eventPath = this.mountPrefix + event.getPath();
-                } else {
-                    eventPath = event.getPath();
-                }
-                if ( event.getType() == Event.PROPERTY_ADDED
-                     || event.getType() == Event.PROPERTY_REMOVED
-                     || event.getType() == Event.PROPERTY_CHANGED ) {
+                final String eventPath = event.getPath();
+                final int type = event.getType();
+                if ( type == PROPERTY_ADDED
+                     || type == PROPERTY_REMOVED
+                     || type == PROPERTY_CHANGED ) {
                     final int lastSlash = eventPath.lastIndexOf('/');
                     final String nodePath = eventPath.substring(0, lastSlash);
                     final String propName = eventPath.substring(lastSlash + 1);
-                    this.updateChangedEvent(changedEvents, nodePath, event, propName);
-
-                } else if ( event.getType() == Event.NODE_ADDED ) {
-                    addedEvents.put(eventPath, createEventProperties(event));
-
-                } else if ( event.getType() == Event.NODE_REMOVED) {
+                    Builder builder = changedEvents.get(nodePath);
+                    if (builder == null) {
+                        changedEvents.put(nodePath, builder = createResourceChange(event, nodePath, ChangeType.CHANGED));
+                    }
+                    this.updateResourceChanged(builder, event.getType(), propName);
+                } else if ( type == NODE_ADDED ) {
+                    addedEvents.put(eventPath, createResourceChange(event, ChangeType.ADDED));
+                } else if ( type == NODE_REMOVED) {
                     // remove is the strongest operation, therefore remove all removed
                     // paths from added
                     addedEvents.remove(eventPath);
-                    removedEvents.put(eventPath, createEventProperties(event));
+                    removedEvents.put(eventPath, createResourceChange(event, ChangeType.REMOVED));
                 }
             } catch (final RepositoryException e) {
-                logger.error("Error during modification: {}", e.getMessage());
+                logger.error("Error during modification: {}", e);
             }
         }
 
-        for (final Entry<String, Map<String, Object>> e : removedEvents.entrySet()) {
-            // Launch an OSGi event
-            sendOsgiEvent(e.getKey(), e.getValue(), SlingConstants.TOPIC_RESOURCE_REMOVED,
-                null);
+        final List<ResourceChange> changes = new ArrayList<ResourceChange>();
+        for (Entry<String, Builder> e : addedEvents.entrySet()) {
+            String path = e.getKey();
+            if (changedEvents.containsKey(path)) {
+                Builder builder = changedEvents.remove(path);
+                builder.setChangeType(ChangeType.ADDED);
+                changes.add(builder.build());
+            } else {
+                changes.add(e.getValue().build());
+            }
         }
+        buildResourceChanges(changes, removedEvents);
+        buildResourceChanges(changes, changedEvents);
+        filterChanges(changes);
+        ctx.getObservationReporter().reportChanges(changes, false);
+    }
 
-        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()));
+    private void buildResourceChanges(List<ResourceChange> result, Map<String, Builder> builders) {
+        for (Entry<String, Builder> e : builders.entrySet()) {
+            result.add(e.getValue().build());
         }
+    }
 
-        // Send the changed events.
-        for (final Entry<String, ChangedAttributes> e : changedEvents.entrySet()) {
-            // Launch an OSGi event.
-            sendOsgiEvent(e.getKey(), e.getValue().toEventProperties(), SlingConstants.TOPIC_RESOURCE_CHANGED, null);
+    private void updateResourceChanged(Builder builder, int eventType, final String propName) {
+        switch (eventType) {
+        case Event.PROPERTY_ADDED:
+            builder.addAddedAttributeName(propName);
+            break;
+        case Event.PROPERTY_CHANGED:
+            builder.addChangedAttributeName(propName);
+            break;
+        case Event.PROPERTY_REMOVED:
+            builder.addRemovedAttributeName(propName);
+            break;
         }
     }
 
-    private static final class ChangedAttributes {
-
-        private final Map<String, Object> properties;
-
-        public ChangedAttributes(final Map<String, Object> properties) {
-            this.properties = properties;
+    private Builder createResourceChange(final Event event, final String path, final ChangeType changeType) throws RepositoryException {
+        Builder builder = new Builder();
+        String strippedPath;
+        if (event.getType() == Event.NODE_REMOVED) {
+            strippedPath = path;
+        } else {
+            strippedPath = stripNtFilePath(path, session);
         }
+        String pathWithPrefix = addMountPrefix(mountPrefix, strippedPath);
+        builder.setPath(pathMapper.mapJCRPathToResourcePath(pathWithPrefix));
+        builder.setChangeType(changeType);
+        boolean isExternal = this.isExternal(event);
+        builder.setExternal(isExternal);
+        if (!isExternal) {
+            final String userID = event.getUserID();
+            if (userID != null) {
+                builder.setUserId(userID);
+            }
+        }
+        return builder;
+    }
 
-        public Set<String> addedAttributes, changedAttributes, removedAttributes;
+    private Builder createResourceChange(final Event event, final ChangeType changeType) throws RepositoryException {
+        return createResourceChange(event, event.getPath(), changeType);
+    }
 
-        public void addEvent(final Event event, final String propName) {
-            if ( event.getType() == Event.PROPERTY_ADDED ) {
-                if ( removedAttributes != null ) {
-                    removedAttributes.remove(propName);
-                }
-                if ( addedAttributes == null ) {
-                    addedAttributes = new HashSet<String>();
-                }
-                addedAttributes.add(propName);
-            } else if ( event.getType() == Event.PROPERTY_REMOVED ) {
-                if ( addedAttributes != null ) {
-                    addedAttributes.remove(propName);
-                }
-                if ( removedAttributes == null ) {
-                    removedAttributes = new HashSet<String>();
-                }
-                removedAttributes.add(propName);
-            } else if ( event.getType() == Event.PROPERTY_CHANGED ) {
-                if ( changedAttributes == null ) {
-                    changedAttributes = new HashSet<String>();
-                }
-                changedAttributes.add(propName);
-            }
+    private boolean isExternal(final Event event) {
+        if ( this.hasJackrabbitEventClass && event instanceof JackrabbitEvent) {
+            final JackrabbitEvent jEvent = (JackrabbitEvent)event;
+            return jEvent.isExternal();
         }
+        return false;
+    }
 
-        /**
-         * Merges lists of added, changed, and removed properties to the given
-         * non-{@code null} {@code properties} and returns that object.
-         *
-         * @param properties The {@code Dictionary} to add the attribute lists
-         *            to.
-         * @return The {@code properties} object is returned.
-         */
-        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()]));
-            }
-            if ( changedAttributes != null )  {
-                properties.put(SlingConstants.PROPERTY_CHANGED_ATTRIBUTES, changedAttributes.toArray(new String[changedAttributes.size()]));
-            }
-            if ( removedAttributes != null )  {
-                properties.put(SlingConstants.PROPERTY_REMOVED_ATTRIBUTES, removedAttributes.toArray(new String[removedAttributes.size()]));
+    static String getAbsPath(PathMapper pathMapper, ProviderContext ctx) {
+        final Set<String> paths = new HashSet<String>();
+        for (ObserverConfiguration c : ctx.getObservationReporter().getObserverConfigurations()) {
+            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()));
             }
-            return properties;
+            includePaths.removeAll(excludePaths);
+            paths.addAll(includePaths);
         }
-
-        /**
-         * @return a {@code Dictionary} with all changes recorded including
-         *         original JCR event information.
-         */
-        public final Map<String, Object> toEventProperties() {
-            return mergeAttributesInto(properties);
+        for (Path p : ctx.getExcludedPaths()) {
+            paths.remove(pathMapper.mapResourcePathToJCRPath(p.getPath()));
         }
+        return getLongestCommonPrefix(paths);
     }
 
-    private void updateChangedEvent(final Map<String, ChangedAttributes> changedEvents, final String path,
-            final Event event, final String propName) {
-        ChangedAttributes storedEvent = changedEvents.get(path);
-        if ( storedEvent == null ) {
-            storedEvent = new ChangedAttributes(createEventProperties(event));
-            changedEvents.put(path, storedEvent);
+    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());
         }
-        storedEvent.addEvent(event, propName);
+        return StringUtils.defaultIfEmpty(prefix, "/");
     }
 
-    /**
-     * Create the base OSGi event properties based on the JCR event 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");
-        } else {
-            final String userID = event.getUserID();
-            if (userID != null) {
-                properties.put(SlingConstants.PROPERTY_USERID, userID);
+    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 properties;
+        return prefix.toString();
     }
 
-    /**
-     * Send an OSGi event based on a JCR Observation Event.
-     *
-     * @param path The path too the node where the event occurred.
-     * @param properties The base properties for this event.
-     * @param topic The topic that should be used for the OSGi event.
-     */
-    private void sendOsgiEvent(final String path,
-            final Map<String, Object> properties,
-            final String topic,
-            final ChangedAttributes changedAttributes) {
-
-        final String resourcePath = pathMapper.mapJCRPathToResourcePath(path);
-        if ( resourcePath != null && !this.support.isExcluded(resourcePath)) {
-            if (changedAttributes != null) {
-                changedAttributes.mergeAttributesInto(properties);
-            }
-
-            // set the path (might have been changed for nt:file content)
-            properties.put(SlingConstants.PROPERTY_PATH, resourcePath);
-            properties.put(EventConstants.EVENT_TOPIC, topic);
+    private int getTypes(ProviderContext ctx) {
+        int result = 0;
+        for (ObserverConfiguration c : ctx.getObservationReporter().getObserverConfigurations()) {
+            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;
+    }
 
-            // enqueue event for dispatching
-            this.osgiEventQueue.offer(properties);
+    static String addMountPrefix(final String mountPrefix, final String path) {
+        final String result;
+        if (mountPrefix == null || mountPrefix.isEmpty() || "/".equals(mountPrefix)) {
+            result = path;
         } else {
-            logger.error("Dropping observation event for {}", path);
+            result = new StringBuilder(mountPrefix).append(path).toString();
         }
+        return result;
     }
 
-    /**
-     * 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 Map<String, Object> event;
-            try {
-                event = this.osgiEventQueue.take();
-            } catch (InterruptedException e) {
-                // interrupted waiting for the event; keep on waiting
-                continue;
-            }
-
-            if (event == null || event == TERMINATE_PROCESSING) {
-                break;
+    private void filterChanges(List<ResourceChange> changes) {
+        Iterator<ResourceChange> it = changes.iterator();
+        while (it.hasNext()) {
+            String path = it.next().getPath();
+            if (ctx.getExcludedPaths().matches(path) != null) {
+                it.remove();
             }
+        }
+    }
 
+    static String stripNtFilePath(String path, Session session) {
+        if (!path.endsWith("/" + JcrConstants.JCR_CONTENT)) {
+            return path;
+        }
+        try {
+            Node node;
             try {
-                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);
-                    Resource resource = resolver.getResource(path);
-                    boolean sendEvent = true;
-                    if (!SlingConstants.TOPIC_RESOURCE_REMOVED.equals(topic)) {
-                        if (resource != null) {
-                            // check if this is a JCR backed resource, otherwise it is not visible!
-                            final Node node = resource.adaptTo(Node.class);
-                            if (node != null) {
-                                // check for nt:file nodes
-                                if (path.endsWith("/jcr:content")) {
-                                    try {
-                                        if (node.getParent().isNodeType("nt:file")) {
-                                            final Resource parentResource = resource.getParent();
-                                            if (parentResource != null) {
-                                                resource = parentResource;
-                                                event.put(SlingConstants.PROPERTY_PATH, resource.getPath());
-                                            }
-                                        }
-                                    } catch (final RepositoryException re) {
-                                        // ignore this
-                                    }
-                                }
-
-                                final String resourceType = resource.getResourceType();
-                                if (resourceType != null) {
-                                    event.put(SlingConstants.PROPERTY_RESOURCE_TYPE, resource.getResourceType());
-                                }
-                                final String resourceSuperType = resource.getResourceSuperType();
-                                if (resourceSuperType != null) {
-                                    event.put(SlingConstants.PROPERTY_RESOURCE_SUPER_TYPE, resource.getResourceSuperType());
-                                }
-                            } else {
-                                // this is not a jcr backed resource
-                                sendEvent = false;
-                            }
-
-                        } else {
-                            // 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);
-                            sendEvent = false;
-                        }
-                    }
-
-                    if ( sendEvent ) {
-                        localEa.sendEvent(new org.osgi.service.event.Event(topic, new EventProperties(event)));
-                    }
-                }
-            } catch (final Exception e) {
-                logger.warn("processOsgiEventQueue: Unexpected problem processing event " + event, e);
+                node = session.getNode(path);
+            } catch(PathNotFoundException e) {
+                session.refresh(false);
+                node = session.getNode(path);
+            }
+            Node parent = node.getParent();
+            if (parent.isNodeType(JcrConstants.NT_FILE)) {
+                return parent.getPath();
+            } else {
+                return path;
             }
+        } catch (RepositoryException e) {
+            return path;
         }
-
-        this.osgiEventQueue.clear();
     }
 
-    private boolean isExternal(final Event event) {
-        if ( this.hasJackrabbitEventClass && event instanceof JackrabbitEvent) {
-            final JackrabbitEvent jEvent = (JackrabbitEvent)event;
-            return jEvent.isExternal();
-        }
-        return false;
-    }
 }

Modified: 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=1727345&r1=1727344&r2=1727345&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/OakResourceListener.java Thu Jan 28 13:41:32 2016
@@ -18,48 +18,38 @@
  */
 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.Arrays;
 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 javax.jcr.Session;
 
 import org.apache.jackrabbit.oak.plugins.observation.NodeObserver;
 import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver;
 import org.apache.jackrabbit.oak.spi.commit.BackgroundObserverMBean;
 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.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.internal.JcrResourceChange.Builder;
 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.ProviderContext;
 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}.
+ * all events to an {@link ObservationReporter}
  */
 public class OakResourceListener extends NodeObserver implements Closeable {
 
@@ -72,23 +62,27 @@ public class OakResourceListener extends
 
     private final ServiceRegistration mbeanRegistration;
 
-    /** Helper object. */
-    final ObservationListenerSupport support;
-
     private final PathMapper pathMapper;
 
+    private final ProviderContext ctx;
+
+    private final Session session;
+
+    @SuppressWarnings("deprecation")
     public OakResourceListener(
             final String mountPrefix,
-            final ObservationListenerSupport support,
+            final ProviderContext ctx,
             final BundleContext bundleContext,
             final Executor executor,
             final PathMapper pathMapper,
-            final int  observationQueueLength)
+            final int observationQueueLength,
+            final SlingRepository repository)
     throws RepositoryException {
-        super("/", "jcr:primaryType", "sling:resourceType", "sling:resourceSuperType");
-        this.support = support;
+        super(JcrResourceListener.getAbsPath(pathMapper, ctx), "jcr:primaryType", "sling:resourceType", "sling:resourceSuperType");
+        this.ctx = ctx;
         this.pathMapper = pathMapper;
-        this.mountPrefix = (mountPrefix == null || mountPrefix.length() == 0 || mountPrefix.equals("/") ? null : mountPrefix);
+        this.mountPrefix = mountPrefix;
+        this.session = repository.loginAdministrative(repository.getDefaultWorkspace());
 
         final Hashtable<String, Object> props = new Hashtable<String, Object>();
         props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
@@ -120,7 +114,7 @@ public class OakResourceListener extends
     public void close() throws IOException {
         mbeanRegistration.unregister();
         serviceRegistration.unregister();
-        this.support.dispose();
+        session.logout();
     }
 
     @Override
@@ -130,12 +124,15 @@ public class OakResourceListener extends
             final Set<String> changed,
             final Map<String, String> properties,
             final CommitInfo commitInfo) {
-        final Map<String, Object> changes = toEventProperties(added, deleted, changed);
-        addCommitInfo(changes, commitInfo);
+        final Builder builder = toEventProperties(JcrResourceListener.stripNtFilePath(path, session), added, deleted, changed, commitInfo);
+        if (ctx.getExcludedPaths().matches(builder.getPath()) != null) {
+            return;
+        }
+        builder.setChangeType(ChangeType.ADDED);
         if ( logger.isDebugEnabled() ) {
             logger.debug("added(path={}, added={}, deleted={}, changed={})", new Object[] {path, added, deleted, changed});
         }
-        sendOsgiEvent(path, TOPIC_RESOURCE_ADDED, changes, properties);
+        ctx.getObservationReporter().reportChanges(Arrays.asList(builder.build()), false);
     }
 
     @Override
@@ -145,12 +142,15 @@ public class OakResourceListener extends
             final Set<String> changed,
             final Map<String, String> properties,
             final CommitInfo commitInfo) {
-        final Map<String, Object> changes = toEventProperties(added, deleted, changed);
-        addCommitInfo(changes, commitInfo);
+        final Builder builder = toEventProperties(path, added, deleted, changed, commitInfo);
+        if (ctx.getExcludedPaths().matches(builder.getPath()) != null) {
+            return;
+        }
+        builder.setChangeType(ChangeType.REMOVED);
         if ( logger.isDebugEnabled() ) {
             logger.debug("deleted(path={}, added={}, deleted={}, changed={})", new Object[] {path, added, deleted, changed});
         }
-        sendOsgiEvent(path, TOPIC_RESOURCE_REMOVED, changes, properties);
+        ctx.getObservationReporter().reportChanges(Arrays.asList(builder.build()), false);
     }
 
     @Override
@@ -160,133 +160,38 @@ public class OakResourceListener extends
             final Set<String> changed,
             final Map<String, String> properties,
             final CommitInfo commitInfo) {
-        final Map<String, Object> changes = toEventProperties(added, deleted, changed);
-        addCommitInfo(changes, commitInfo);
+        final Builder builder = toEventProperties(JcrResourceListener.stripNtFilePath(path, session), added, deleted, changed, commitInfo);
+        if (ctx.getExcludedPaths().matches(builder.getPath()) != null) {
+            return;
+        }
+        builder.setChangeType(ChangeType.CHANGED);
         if ( logger.isDebugEnabled() ) {
             logger.debug("changed(path={}, added={}, deleted={}, changed={})", new Object[] {path, added, deleted, changed});
         }
-        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");
-        }
+        ctx.getObservationReporter().reportChanges(Arrays.asList(builder.build()), false);
     }
 
-    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>();
+    private Builder toEventProperties(final String path, final Set<String> added, final Set<String> deleted, final Set<String> changed, final CommitInfo commitInfo) {
+        Builder builder = new Builder();
+        String pathWithPrefix = JcrResourceListener.addMountPrefix(mountPrefix, path);
+        builder.setPath(pathMapper.mapJCRPathToResourcePath(pathWithPrefix));
         if ( added != null && added.size() > 0 ) {
-            properties.put(PROPERTY_ADDED_ATTRIBUTES, added.toArray(new String[added.size()]));
+            for (String propName : added) {
+                builder.addAddedAttributeName(propName);
+            }
         }
         if ( changed != null && changed.size() > 0 ) {
-            properties.put(PROPERTY_CHANGED_ATTRIBUTES, changed.toArray(new String[changed.size()]));
+            for (String propName : changed) {
+                builder.addChangedAttributeName(propName);
+            }
         }
         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)
-        final String changePath;
-        if ( this.mountPrefix == null ) {
-            changePath = path;
-        } else {
-            changePath = this.mountPrefix + path;
-        }
-        changes.put(PROPERTY_PATH, changePath);
-
-        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;
-                            logger.debug("resource resolver is null");
-                        } else {
-                            final Resource rsrc = resolver.getResource(changePath);
-                            if ( rsrc == null ) {
-                                resolver.refresh();
-                                sendEvent = false;
-                                logger.debug("not able to get resource for changes path {}", changePath);
-                            } 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
-                                        logger.error(re.getMessage(), re);
-                                    }
-
-                                } else {
-                                    // this is not a jcr backed resource
-                                    sendEvent = false;
-                                    logger.debug("not able to adapt resource {} to node", changePath);
-                                }
-
-                            }
-                        }
-                        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",
-                                        changePath);
-                        }
-                    }
-
-                    // update resource type properties
-                    if ( sendEvent ) {
-                        if ( resourceType == null ) {
-                            changes.put(PROPERTY_RESOURCE_TYPE, nodeType);
-                        } else {
-                            changes.put(PROPERTY_RESOURCE_TYPE, resourceType);
-                        }
-                        if ( resourceSuperType != null ) {
-                            changes.put(PROPERTY_RESOURCE_SUPER_TYPE, resourceSuperType);
-                        }
-                    }
-                }
-
-                if ( sendEvent ) {
-                    final String resourcePath = pathMapper.mapJCRPathToResourcePath(changes.get(SlingConstants.PROPERTY_PATH).toString());
-                    if ( resourcePath != null && !this.support.isExcluded(resourcePath)) {
-                        changes.put(SlingConstants.PROPERTY_PATH, resourcePath);
-
-                        localEa.sendEvent(new org.osgi.service.event.Event(topic, new EventProperties(changes)));
-                    } else {
-                        logger.debug("Dropping observation event for {}", changes.get(SlingConstants.PROPERTY_PATH));
-                    }
-                }
+            for (String propName : deleted) {
+                builder.addRemovedAttributeName(propName);
             }
-        } catch (final Exception e) {
-            logger.warn("sendOsgiEvent: Unexpected problem processing event " + topic + " at " + path + " with " + changes, e);
         }
+        builder.setUserId(commitInfo.getUserId());
+        builder.setExternal(false);
+        return builder;
     }
 }

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=1727345&r1=1727344&r2=1727345&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 Thu Jan 28 13:41:32 2016
@@ -64,7 +64,6 @@ import org.apache.sling.jcr.resource.int
 import org.apache.sling.jcr.resource.internal.JcrResourceListener;
 import org.apache.sling.jcr.resource.internal.NodeUtil;
 import org.apache.sling.jcr.resource.internal.OakResourceListener;
-import org.apache.sling.jcr.resource.internal.ObservationListenerSupport;
 import org.apache.sling.spi.resource.provider.QueryLanguageProvider;
 import org.apache.sling.spi.resource.provider.ProviderContext;
 import org.apache.sling.spi.resource.provider.ResolverContext;
@@ -169,22 +168,27 @@ public class JcrResourceProvider extends
 
     @Deactivate
     protected void deactivate() {
-        unregisterLegacyListener();
     }
 
-
     @Override
     public void start(final ProviderContext ctx) {
         super.start(ctx);
-        registerLegacyListener();
+        registerListener(ctx);
     }
 
     @Override
     public void stop() {
-        unregisterLegacyListener();
+        unregisterListener();
         super.stop();
     }
 
+    @Override
+    public void update(long changeSet) {
+        super.update(changeSet);
+        unregisterListener();
+        registerListener(getProviderContext());
+    }
+
     @SuppressWarnings("unused")
     private void bindRepository(final ServiceReference ref) {
         this.repositoryReference = ref;
@@ -199,7 +203,7 @@ public class JcrResourceProvider extends
         }
     }
 
-    private void registerLegacyListener() {
+    private void registerListener(ProviderContext ctx) {
         // check for Oak
         boolean isOak = false;
         if ( optimizeForOak ) {
@@ -212,13 +216,10 @@ public class JcrResourceProvider extends
                 }
             }
         }
-        ObservationListenerSupport support = null;
-        boolean closeSupport = true;
         try {
-            support = new ObservationListenerSupport(bundleCtx, repository, this.getProviderContext().getExcludedPaths());
             if (isOak) {
                 try {
-                    this.listener = new OakResourceListener(root, support, bundleCtx, executor, pathMapper, observationQueueLength);
+                    this.listener = new OakResourceListener(root, ctx, bundleCtx, executor, pathMapper, observationQueueLength, repository);
                     log.info("Detected Oak based repository. Using improved JCR Resource Listener with observation queue length {}", observationQueueLength);
                 } catch ( final RepositoryException re ) {
                     throw new SlingException("Can't create the OakResourceListener", re);
@@ -227,19 +228,14 @@ public class JcrResourceProvider extends
                 }
             }
             if (this.listener == null) {
-                this.listener = new JcrResourceListener(root, support, pathMapper);
+                this.listener = new JcrResourceListener(ctx, root, pathMapper, repository);
             }
-            closeSupport = false;
         } catch (RepositoryException e) {
             throw new SlingException("Can't create the listener", e);
-        } finally {
-            if ( closeSupport && support != null ) {
-                support.dispose();
-            }
         }
     }
 
-    private void unregisterLegacyListener() {
+    private void unregisterListener() {
         if ( this.listener != null ) {
             try {
                 this.listener.close();

Modified: 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=1727345&r1=1727344&r2=1727345&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/AbstractListenerTest.java Thu Jan 28 13:41:32 2016
@@ -23,9 +23,9 @@ import static org.junit.Assert.assertTru
 import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
-import java.util.Dictionary;
+import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashSet;
-import java.util.Hashtable;
 import java.util.List;
 import java.util.Set;
 
@@ -33,10 +33,13 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
-import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.util.PathSet;
 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.Test;
-import org.osgi.service.event.Event;
 
 /**
  * Base for the two different listener tests.
@@ -49,21 +52,7 @@ public abstract class AbstractListenerTe
 
     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 + ")";
-            }
-        });
-    }
+    private final List<ResourceChange> events = synchronizedList(new ArrayList<ResourceChange>());
 
     @Test public void testSimpleOperations() throws Exception {
         generateEvents();
@@ -73,17 +62,18 @@ public abstract class AbstractListenerTe
         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));
+        for (final ResourceChange event : events) {
+            if (event.getType() == ChangeType.ADDED) {
+                addPaths.add(event.getPath());
+            } else if (event.getType() == ChangeType.CHANGED) {
+                modifyPaths.add(event.getPath());
+                assertEquals(Collections.singleton("foo"), event.getAddedPropertyNames());
+            } else if (event.getType() == ChangeType.REMOVED) {
+                removePaths.add(event.getPath());
             } else {
                 fail("Unexpected event: " + event);
             }
-            assertNotNull(event.getProperty(SlingConstants.PROPERTY_USERID));
+            assertNotNull(event.getUserId());
         }
         assertEquals(3, addPaths.size());
         assertTrue("Added set should contain " + createdPath, addPaths.contains(createdPath));
@@ -103,6 +93,7 @@ public abstract class AbstractListenerTe
     }
 
     private void generateEvents() throws Exception {
+        @SuppressWarnings("deprecation")
         final Session session = getRepository().loginAdministrative(null);
 
         try {
@@ -132,4 +123,51 @@ public abstract class AbstractListenerTe
     }
 
     public abstract SlingRepository getRepository();
+
+    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;
+                }
+            };
+            return Collections.singletonList(config);
+        }
+    }
+
 }

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=1727345&r1=1727344&r2=1727345&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 Thu Jan 28 13:41:32 2016
@@ -16,10 +16,14 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Workspace;
@@ -27,14 +31,18 @@ import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.ObservationManager;
 
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.util.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.apache.sling.spi.resource.provider.ProviderContext;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
-import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.event.EventAdmin;
 
 /**
  * This test case asserts that JcrResourceListener scales to an
@@ -45,6 +53,7 @@ public class JcrResourceListenerScalabil
     private JcrResourceListener jcrResourceListener;
     private EventIterator events;
 
+    @SuppressWarnings("deprecation")
     @Before
     public void setUp() throws RepositoryException, InvalidSyntaxException {
         ObservationManager observationManager = mock(ObservationManager.class);
@@ -58,14 +67,7 @@ public class JcrResourceListenerScalabil
         SlingRepository repository = mock(SlingRepository.class);
         when(repository.loginAdministrative(null)).thenReturn(session);
 
-        EventAdmin eventAdmin = mock(EventAdmin.class);
-        ServiceReference serviceRef = mock(ServiceReference.class);
-        ServiceReference[] serviceRefs = new ServiceReference[]{serviceRef};
-        BundleContext bundleContext = mock(BundleContext.class);
-        when(bundleContext.getServiceReferences(anyString(), anyString())).thenReturn(serviceRefs);
-        when(bundleContext.getService(serviceRef)).thenReturn(eventAdmin);
-
-        jcrResourceListener = new JcrResourceListener("/", new ObservationListenerSupport(bundleContext, repository, null), new PathMapperImpl());
+        jcrResourceListener = new JcrResourceListener(new SimpleProviderContext(), "/", new PathMapperImpl(), RepositoryUtil.getRepository());
 
         Event event = mock(MockEvent.class);
         events = mock(EventIterator.class);
@@ -89,4 +91,53 @@ public class JcrResourceListenerScalabil
             return "path-" + count++;
         }
     }
+
+    private static class SimpleProviderContext implements ProviderContext {
+        @Override
+        public ObservationReporter getObservationReporter() {
+            return new ObservationReporter() {
+
+                @Override
+                public void reportChanges(Iterable<ResourceChange> changes, boolean distribute) {
+                }
+
+                @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;
+                        }
+                    };
+                    return Collections.singletonList(config);
+                }
+            };
+        }
+
+        @Override
+        public PathSet getExcludedPaths() {
+            return PathSet.fromPaths();
+        }
+    }
 }

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=1727345&r1=1727344&r2=1727345&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 Thu Jan 28 13:41:32 2016
@@ -16,31 +16,22 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 import javax.jcr.Session;
 
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.util.PathSet;
 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.apache.sling.spi.resource.provider.ObservationReporter;
+import org.apache.sling.spi.resource.provider.ProviderContext;
 import org.junit.After;
 import org.junit.Before;
-import org.mockito.Mockito;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.osgi.util.tracker.ServiceTracker;
 
 /**
  * Test of JcrResourceListener.
  */
 public class JcrResourceListenerTest extends AbstractListenerTest {
 
-    private SynchronousJcrResourceListener listener;
+    private JcrResourceListener listener;
 
     private Session adminSession;
 
@@ -52,44 +43,28 @@ public class JcrResourceListenerTest ext
         }
         RepositoryUtil.stopRepository();
         if ( listener != null ) {
-            listener.dispose();
+            listener.close();
             listener = null;
         }
     }
 
+    @SuppressWarnings("deprecation")
     @Before
     public void setUp() throws Exception {
         RepositoryUtil.startRepository();
         this.adminSession = RepositoryUtil.getRepository().loginAdministrative(null);
         RepositoryUtil.registerSlingNodeTypes(adminSession);
-        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);
-
-        final EventAdmin mockEA = new EventAdmin() {
-
-            public void postEvent(final Event event) {
-                addEvent(event);
+        this.listener = new JcrResourceListener(new ProviderContext() {
+            @Override
+            public ObservationReporter getObservationReporter() {
+                return JcrResourceListenerTest.this.getObservationReporter();
             }
 
-            public void sendEvent(final Event event) {
-                addEvent(event);
+            @Override
+            public PathSet getExcludedPaths() {
+                return PathSet.fromPaths();
             }
-        };
-
-        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);
-
-        this.listener = new SynchronousJcrResourceListener(RepositoryUtil.getRepository(),
-                        bundleContext, resolver, tracker);
+        }, "/", new PathMapperImpl(), RepositoryUtil.getRepository());
     }
 
     @Override

Modified: 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=1727345&r1=1727344&r2=1727345&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/OakResourceListenerTest.java Thu Jan 28 13:41:32 2016
@@ -17,11 +17,7 @@
 package org.apache.sling.jcr.resource.internal;
 
 import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerObserver;
-import static org.junit.Assert.assertNotNull;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyMap;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -30,37 +26,30 @@ import java.util.concurrent.ExecutorServ
 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.BackgroundObserverMBean;
 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.api.resource.util.PathSet;
 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.apache.sling.spi.resource.provider.ObservationReporter;
+import org.apache.sling.spi.resource.provider.ProviderContext;
 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 OakResourceListener listener;
     private ExecutorService executor;
     private Whiteboard whiteboard;
     private SlingRepository slingRepository;
@@ -73,59 +62,33 @@ public class OakResourceListenerTest ext
         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(eq(Observer.class.getName()), 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);
-                    }
-                });
-
-        when(bundleContext.registerService(eq(BackgroundObserverMBean.class.getName()), any(Object.class), any
-                (Dictionary.class)))
+        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();
-                        assertNotNull(((Dictionary)arguments[2]).get("jmx.objectname"));
+                        if (arguments[1] instanceof Observer) {
+                            registerObserver(whiteboard, (Observer) arguments[1]);
+                        }
                         return mock(ServiceRegistration.class);
                     }
                 });
+        listener = new OakResourceListener("/", new ProviderContext() {
+            @Override
+            public ObservationReporter getObservationReporter() {
+                return OakResourceListenerTest.this.getObservationReporter();
+            }
 
-        listener = new SynchronousOakResourceListener(
-                this.slingRepository, bundleContext, resolver, tracker, executor);
+            @Override
+            public PathSet getExcludedPaths() {
+                return PathSet.fromPaths();
+            }
+        }, bundleContext, executor, new PathMapperImpl(), 1000, slingRepository); 
     }
 
     @After
     public void tearDown() throws Exception {
-        listener.dispose();
-        session.logout();
+        listener.close();
         executor.shutdown();
     }
 

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/OsgiObservationBridge.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/OsgiObservationBridge.java?rev=1727345&r1=1727344&r2=1727345&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/OsgiObservationBridge.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/OsgiObservationBridge.java Thu Jan 28 13:41:32 2016
@@ -46,10 +46,11 @@ import org.osgi.service.event.EventAdmin
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-@Component(policy = ConfigurationPolicy.REQUIRE)
+@Component(policy = ConfigurationPolicy.REQUIRE, metatype = true,
+        label="Apache Sling OSGi Observation Bridge", description="Legacy bridge which converts resource change events to OSGi events")
 @Service(ResourceChangeListener.class)
 @Properties({ @Property(name = ResourceChangeListener.CHANGES, value = { "ADDED", "CHANGED", "REMOVED" }),
-        @Property(name = ResourceChangeListener.PATHS, value = ".") })
+        @Property(name = ResourceChangeListener.PATHS, value = "/") })
 public class OsgiObservationBridge implements ResourceChangeListener, ExternalResourceChangeListener {
 
     private static final Logger logger = LoggerFactory.getLogger(OsgiObservationBridge.class);

Modified: sling/trunk/launchpad/builder/src/main/provisioning/sling.txt
URL: http://svn.apache.org/viewvc/sling/trunk/launchpad/builder/src/main/provisioning/sling.txt?rev=1727345&r1=1727344&r2=1727345&view=diff
==============================================================================
--- sling/trunk/launchpad/builder/src/main/provisioning/sling.txt (original)
+++ sling/trunk/launchpad/builder/src/main/provisioning/sling.txt Thu Jan 28 13:41:32 2016
@@ -168,3 +168,5 @@
   org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet
       alias="/server"
 
+  org.apache.sling.resourceresolver.impl.observation.OsgiObservationBridge
+      enabled=B"true"