SLING-3747 - Provide a way to signal Jcr Installer to pause and resume scanning

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1617254 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java b/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java
index a7b346b..3077258 100644
--- a/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java
+++ b/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java
@@ -178,6 +178,15 @@
     /** The path for new configurations. */
     private String newConfigPath;
 
+    public static final String PAUSE_SCAN_NODE_PATH = "/system/sling/installer/jcr/pauseInstallation";
+    @Property(value= PAUSE_SCAN_NODE_PATH)
+    private static final String PROP_SCAN_PROP_PATH = "sling.jcrinstall.signal.path";
+
+    /** The path for pauseInstallation property */
+    private String pauseScanNodePath;
+
+    private volatile boolean pauseMessageLogged = false;
+
     private static final boolean DEFAULT_ENABLE_WRITEBACK = true;
     @Property(boolValue=DEFAULT_ENABLE_WRITEBACK)
     private static final String PROP_ENABLE_WRITEBACK = "sling.jcrinstall.enable.writeback";
@@ -360,6 +369,8 @@
             this.newConfigPath = this.folderNameFilter.getRootPaths()[0] + '/' + this.newConfigPath;
         }
 
+        this.pauseScanNodePath = PropertiesUtil.toString(getPropertyValue(PROP_SCAN_PROP_PATH), PAUSE_SCAN_NODE_PATH);
+
         backgroundThread = new StoppableThread();
         backgroundThread.start();
     }
@@ -593,6 +604,22 @@
         try {
             boolean didRefresh = false;
 
+            if (anyWatchFolderNeedsScan()) {
+                session.refresh(false);
+                didRefresh = true;
+                if (scanningIsPaused()) {
+                    if (!pauseMessageLogged) {
+                        //Avoid flooding the logs every 500 msec so log at info level once
+                        logger.info("Detected signal for pausing the JCR Provider i.e. child nodes found under path {}. " +
+                                "JCR Provider scanning would not be performed", pauseScanNodePath);
+                        pauseMessageLogged = true;
+                    }
+                    return;
+                } else if (pauseMessageLogged) {
+                    pauseMessageLogged = false;
+                }
+            }
+
             // Rescan WatchedFolders if needed
             boolean scanWf = false;
             for(WatchedFolder wf : watchedFolders) {
@@ -651,6 +678,32 @@
         counters[RUN_LOOP_COUNTER]++;
     }
 
+    boolean scanningIsPaused() throws RepositoryException {
+        if (session.nodeExists(pauseScanNodePath)) {
+            Node node = session.getNode(pauseScanNodePath);
+            boolean result = node.hasNodes();
+            if (result && logger.isDebugEnabled()) {
+                List<String> nodeNames = new ArrayList<String>();
+                NodeIterator childItr = node.getNodes();
+                while (childItr.hasNext()) {
+                    nodeNames.add(childItr.nextNode().getName());
+                }
+                logger.debug("Found child nodes {} at path {}. Scanning would be paused", nodeNames, pauseScanNodePath);
+            }
+            return result;
+        }
+        return false;
+    }
+
+    private boolean anyWatchFolderNeedsScan() {
+        for (WatchedFolder wf : watchedFolders) {
+            if (wf.needsScan()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     long [] getCounters() {
         return counters;
     }
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
index 0334971..d04e307 100644
--- a/src/main/resources/OSGI-INF/metatype/metatype.properties
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -33,7 +33,7 @@
   
 sling.jcrinstall.folder.name.regexp.name = Installation folders name regexp
 sling.jcrinstall.folder.name.regexp.description = JCRInstall looks in repository folders \
-  having a name that match this regular expression (under the root paths, which are defined \  
+  having a name that match this regular expression (under the root paths, which are defined \
   by the ResourceResolver search path) for resources to install. Folders having names \
   that match this expression, followed by dotted run mode selectors (like "install.author.production") \
   are also included.   
@@ -58,4 +58,9 @@
 handler.schemes.description = For these schemes this installer writes back configurations.
 
 service.ranking.name = Ranking
-service.ranking.description = Ranking of this service.
\ No newline at end of file
+service.ranking.description = Ranking of this service.
+
+sling.jcrinstall.signal.path.name = Signal Node Path
+sling.jcrinstall.signal.path.description = Path of the node in repository whose children would be \
+  watched for determining if the watch folder scanning has to be performed or not. If any child node is found \
+  at this path then scanning would be paused.
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/installer/provider/jcr/impl/ContentHelper.java b/src/test/java/org/apache/sling/installer/provider/jcr/impl/ContentHelper.java
index e10e07c..9bc8764 100644
--- a/src/test/java/org/apache/sling/installer/provider/jcr/impl/ContentHelper.java
+++ b/src/test/java/org/apache/sling/installer/provider/jcr/impl/ContentHelper.java
@@ -112,7 +112,7 @@
         }
     }
 
-    void createFolder(String path) throws Exception {
+    Node createFolder(String path) throws Exception {
         final String [] parts = relPath(path).split("/");
         Node n = session.getRootNode();
         for(String part : parts) {
@@ -123,6 +123,7 @@
             }
         }
         session.save();
+        return n;
     }
 
     void delete(String path) throws RepositoryException {
diff --git a/src/test/java/org/apache/sling/installer/provider/jcr/impl/ScanningLoopTest.java b/src/test/java/org/apache/sling/installer/provider/jcr/impl/ScanningLoopTest.java
index b0076b6..3211ab8 100644
--- a/src/test/java/org/apache/sling/installer/provider/jcr/impl/ScanningLoopTest.java
+++ b/src/test/java/org/apache/sling/installer/provider/jcr/impl/ScanningLoopTest.java
@@ -18,11 +18,12 @@
  */
 package org.apache.sling.installer.provider.jcr.impl;
 
+
+import javax.jcr.Node;
 import javax.jcr.Session;
 
 import org.apache.sling.commons.testing.jcr.EventHelper;
 import org.apache.sling.commons.testing.jcr.RepositoryTestBase;
-import org.apache.sling.installer.provider.jcr.impl.JcrInstaller;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.junit.Test;
 
@@ -56,6 +57,7 @@
         MiscUtil.waitForInstallerThread(installer, TIMEOUT);
         installer = null;
         contentHelper.cleanupContent();
+        contentHelper.deleteQuietly(JcrInstaller.PAUSE_SCAN_NODE_PATH);
         if(session != null) {
             session.logout();
             session = null;
@@ -85,6 +87,38 @@
         assertIdle();
     }
 
+    public void testDefaultScanPauseFalse() throws Exception{
+        assertFalse(installer.scanningIsPaused());
+    }
+
+    public void testPauseScan() throws Exception{
+        assertFalse(installer.scanningIsPaused());
+
+        Node n = contentHelper.createFolder(JcrInstaller.PAUSE_SCAN_NODE_PATH);
+        Node testNode = n.addNode("foo.example.pause");
+        session.save();
+
+        eventHelper.waitForEvents(TIMEOUT);
+
+        assertTrue(installer.scanningIsPaused());
+        final long sf = installer.getCounters()[JcrInstaller.SCAN_FOLDERS_COUNTER];
+        final long uc = installer.getCounters()[JcrInstaller.UPDATE_FOLDERS_LIST_COUNTER];
+
+        Thread.sleep(JcrInstaller.RUN_LOOP_DELAY_MSEC * 2);
+
+        //Counters should not have changed as no scanning being performed
+        assertEquals(sf, installer.getCounters()[JcrInstaller.SCAN_FOLDERS_COUNTER]);
+
+        //Now lets resume again
+        testNode.remove();
+        session.save();
+
+        Thread.sleep(JcrInstaller.RUN_LOOP_DELAY_MSEC * 2);
+
+        //Now counters should have changed
+        assertIdle();
+    }
+
     @Test
     public void testAddBundle() throws Exception {
         contentHelper.createOrUpdateFile(contentHelper.FAKE_RESOURCES[0]);
@@ -137,4 +171,9 @@
 
         assertIdle();
     }
+
+    private static String getParentPath(String absPath){
+        int pos = absPath.lastIndexOf('/');
+        return absPath.substring(0, pos);
+    }
 }
\ No newline at end of file