You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2012/02/03 00:13:22 UTC

svn commit: r1239916 - in /sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal: JcrResourceResolver.java helper/MapEntries.java

Author: fmeschbe
Date: Thu Feb  2 23:13:21 2012
New Revision: 1239916

URL: http://svn.apache.org/viewvc?rev=1239916&view=rev
Log:
SLING-2321 Enhance event handler filter to also capture /etc/map updates
SLING-2398 Refactor asynchronous map initialization with a single thread

Modified:
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java?rev=1239916&r1=1239915&r2=1239916&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java Thu Feb  2 23:13:21 2012
@@ -98,6 +98,8 @@ public class JcrResourceResolver
 
     public static final String PROP_REDIRECT_EXTERNAL_STATUS = "sling:status";
 
+    public static final String PROP_REDIRECT_EXTERNAL_REDIRECT_STATUS = "sling:status";
+
     // The suffix of a resource being a content node of some parent
     // such as nt:file. The slash is included to prevent false
     // positives for the String.endsWith check for names like

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java?rev=1239916&r1=1239915&r2=1239916&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/MapEntries.java Thu Feb  2 23:13:21 2012
@@ -34,6 +34,9 @@ import java.util.Map.Entry;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.servlet.http.HttpServletResponse;
 
@@ -69,7 +72,7 @@ public class MapEntries implements Event
 
     private JcrResourceResolverFactoryImpl factory;
 
-    private ResourceResolver resolver;
+    private volatile ResourceResolver resolver;
 
     private final String mapRoot;
 
@@ -79,11 +82,13 @@ public class MapEntries implements Event
 
     private Collection<String> vanityTargets;
 
-    private boolean initializing = false;
+    private ServiceRegistration registration;
 
-    private final ServiceRegistration registration;
+    private ServiceTracker eventAdminTracker;
 
-    private final ServiceTracker eventAdminTracker;
+    private final Semaphore initTrigger = new Semaphore(0);
+
+    private final ReentrantLock initializing = new ReentrantLock();
 
     private MapEntries() {
         this.factory = null;
@@ -106,16 +111,25 @@ public class MapEntries implements Event
         this.mapRoot = factory.getMapRoot();
         this.eventAdminTracker = eventAdminTracker;
 
-        init();
+        this.resolveMaps = Collections.<MapEntry> emptyList();
+        this.mapMaps = Collections.<MapEntry> emptyList();
+        this.vanityTargets = Collections.<String> emptySet();
+
+        doInit();
 
         // build a filter which matches if any of the nodeProps (JCR
         // properties modified) is listed in any of the eventProps (event
         // properties listing modified JCR properties)
         // this allows to only get events interesting for updating the
         // internal structure
-        final String[] nodeProps = { "sling:vanityPath", "sling:vanityOrder", "sling:redirect" };
-        final String[] eventProps = { "resourceAddedAttributes", "resourceChangedAttributes",
-            "resourceRemovedAttributes" };
+        final String[] nodeProps = {
+            "sling:vanityPath", "sling:vanityOrder", JcrResourceResolver.PROP_REDIRECT_EXTERNAL_REDIRECT_STATUS,
+            JcrResourceResolver.PROP_REDIRECT_EXTERNAL, JcrResourceResolver.PROP_REDIRECT_INTERNAL,
+            JcrResourceResolver.PROP_REDIRECT_EXTERNAL_STATUS
+        };
+        final String[] eventProps = {
+            "resourceAddedAttributes", "resourceChangedAttributes", "resourceRemovedAttributes"
+        };
         StringBuilder filter = new StringBuilder();
         filter.append("(|");
         for (String eventProp : eventProps) {
@@ -134,20 +148,56 @@ public class MapEntries implements Event
         props.put(Constants.SERVICE_DESCRIPTION, "Map Entries Observation");
         props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
         this.registration = bundleContext.registerService(EventHandler.class.getName(), this, props);
-    }
 
-    private void init() {
-        synchronized (this) {
-            // no initialization if the session has already been reset
-            if (resolver == null) {
-                return;
+        Thread updateThread = new Thread(new Runnable() {
+            public void run() {
+                MapEntries.this.init();
             }
+        }, "MapEntries Update");
+        updateThread.start();
+    }
+
+    /**
+     * Signals the init method that a the doInit method should be
+     * called.
+     */
+    private void triggerInit() {
+        // only release if there is not one in the queue already
+        if (initTrigger.availablePermits() < 1) {
+            initTrigger.release();
+        }
+    }
 
-            // set the flag
-            initializing = true;
+    /**
+     * Runs as the method of the update thread. Waits for the triggerInit
+     * method to trigger a call to doInit. Terminates when the resolver
+     * has been null-ed after having been triggered.
+     */
+    void init() {
+        while (MapEntries.this.resolver != null) {
+            try {
+                MapEntries.this.initTrigger.acquire();
+                MapEntries.this.doInit();
+            } catch (InterruptedException ie) {
+                // just continue acquisition
+            }
         }
 
+    }
+
+    /**
+     * Actual initializer. Guards itself agains concurrent use by
+     * using a ReentrantLock. Does nothing if the resource resolver
+     * has already been null-ed.
+     */
+    private void doInit() {
+
+        this.initializing.lock();
         try {
+            final ResourceResolver resolver = this.resolver;
+            if (resolver == null) {
+                return;
+            }
 
             List<MapEntry> newResolveMaps = new ArrayList<MapEntry>();
             SortedMap<String, MapEntry> newMapMaps = new TreeMap<String, MapEntry>();
@@ -171,45 +221,72 @@ public class MapEntries implements Event
 
             sendChangeEvent();
 
+        } catch (Exception e) {
+
+            log.warn("doInit: Unexpected problem during initialization", e);
+
         } finally {
 
-            // reset the flag and notify listeners
-            synchronized (this) {
-                initializing = false;
-                notifyAll();
-            }
+            this.initializing.unlock();
+
         }
     }
 
+    /**
+     * Cleans up this class.
+     */
     public void dispose() {
-        final ResourceResolver oldResolver;
+        if ( this.registration != null ) {
+            this.registration.unregister();
+            this.registration = null;
+        }
+
+        /*
+         * Cooperation with doInit: The same lock as used by doInit
+         * is acquired thus preventing doInit from running and waiting
+         * for a concurrent doInit to terminate.
+         * Once the lock has been acquired, the resource resolver is
+         * null-ed (thus causing the init to terminate when triggered
+         * the right after and prevent the doInit method from doing any
+         * thing).
+         */
 
         // wait at most 10 seconds for a notifcation during initialization
-        synchronized (this) {
-            if (initializing) {
-                try {
-                    wait(10L * 1000L);
-                } catch (InterruptedException ie) {
-                    // ignore
-                }
+        boolean initLocked;
+        try {
+            initLocked = this.initializing.tryLock(10, TimeUnit.SECONDS);
+        } catch (InterruptedException ie) {
+            initLocked = false;
+        }
+
+        try {
+            if (!initLocked) {
+                log.warn("dispose: Could not acquire initialization lock within 10 seconds; ongoing intialization may fail");
             }
 
             // immediately set the resolver field to null to indicate
             // that we have been disposed (this also signals to the
             // event handler to stop working
-            oldResolver = resolver;
-            resolver = null;
-        }
-        if ( this.registration != null ) {
-            this.registration.unregister();
-        }
+            final ResourceResolver oldResolver = this.resolver;
+            this.resolver = null;
+
+            // trigger initialization to terminate init thread
+            triggerInit();
 
-        if (oldResolver != null) {
-            oldResolver.close();
+            if (oldResolver != null) {
+                oldResolver.close();
+            } else {
+                log.warn("dispose: ResourceResolver has already been cleared before; duplicate call to dispose ?");
+            }
+        } finally {
+            if (initLocked) {
+                this.initializing.unlock();
+            }
         }
 
         // clear the rest of the fields
-        factory = null;
+        this.factory = null;
+        this.eventAdminTracker = null;
     }
 
     public List<MapEntry> getResolveMaps() {
@@ -237,22 +314,21 @@ public class MapEntries implements Event
             final Object p = event.getProperty(SlingConstants.PROPERTY_PATH);
             if (p instanceof String) {
                 final String path = (String) p;
-                for (String target : this.vanityTargets) {
-                    if (target.startsWith(path)) {
-                        doInit = true;
-                        break;
+                doInit = path.startsWith(this.mapRoot);
+                if (!doInit) {
+                    for (String target : this.vanityTargets) {
+                        if (target.startsWith(path)) {
+                            doInit = true;
+                            break;
+                        }
                     }
                 }
             }
         }
 
+        // trigger an update
         if (doInit) {
-            final Thread t = new Thread() {
-                public void run() {
-                    init();
-                }
-            };
-            t.start();
+            triggerInit();
         }
     }
 
@@ -263,10 +339,12 @@ public class MapEntries implements Event
      */
     private void sendChangeEvent() {
         final EventAdmin ea = (EventAdmin) this.eventAdminTracker.getService();
-        if ( ea != null ) {
-            // we hard code the topic here and don't use SlingConstants.TOPIC_RESOURCE_RESOLVER_MAPPING_CHANGED
+        if (ea != null) {
+            // we hard code the topic here and don't use
+            // SlingConstants.TOPIC_RESOURCE_RESOLVER_MAPPING_CHANGED
             // to avoid requiring the latest API version for this bundle to work
-            final Event event = new Event("org/apache/sling/api/resource/ResourceResolverMapping/CHANGED", (Dictionary<?,?>)null);
+            final Event event = new Event("org/apache/sling/api/resource/ResourceResolverMapping/CHANGED",
+                (Dictionary<?, ?>) null);
             ea.postEvent(event);
         }
     }
@@ -362,7 +440,7 @@ public class MapEntries implements Event
                     // whether the target is attained by a 302/FOUND or by an
                     // internal redirect is defined by the sling:redirect property
                     int status = row.get("sling:redirect", false)
-                            ? row.get("sling:redirectStatus", HttpServletResponse.SC_FOUND)
+                            ? row.get(JcrResourceResolver.PROP_REDIRECT_EXTERNAL_REDIRECT_STATUS, HttpServletResponse.SC_FOUND)
                             : -1;
 
                     // 1. entry with exact match