SLING-4058 - more specific MappingEventsTest added

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1634552 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/MappingEventsTest.java b/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/MappingEventsTest.java
new file mode 100644
index 0000000..2784779
--- /dev/null
+++ b/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/MappingEventsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.launchpad.testservices.serversidetests;
+
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.junit.annotations.SlingAnnotationsTestRunner;
+import org.apache.sling.junit.annotations.TestReference;
+import org.apache.sling.launchpad.testservices.events.EventsCounter;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@RunWith(SlingAnnotationsTestRunner.class)
+public class MappingEventsTest {
+
+    public static final String PROP_REDIRECT_EXTERNAL = "sling:redirect";
+    
+    private static final Logger logger = LoggerFactory.getLogger(MappingEventsTest.class);
+    private static Session session;
+    private Node mapRoot;
+    private static List<String> toDelete = new ArrayList<String>();
+    
+    @TestReference
+    private EventsCounter eventsCounter;
+    
+    @TestReference
+    private SlingRepository repository;
+    
+    private Node maybeCreateNode(Node parent, String name, String type) throws RepositoryException {
+        if(parent.hasNode(name)) {
+            return parent.getNode(name);
+        } else {
+            return parent.addNode(name, type);
+        }
+    }
+
+    @Before
+    public synchronized void setup() throws Exception {
+        // Do the mappings setup only once, and clean it up 
+        // after all tests
+        session = repository.loginAdministrative(null);
+        final Node rootNode = maybeCreateNode(session.getRootNode(), "content", "nt:unstructured");
+        session.save();
+        if(toDelete.isEmpty()) {
+            final Node mapRoot = maybeCreateNode(session.getRootNode(), "etc", "nt:folder");
+            final Node map = maybeCreateNode(mapRoot, "map", "sling:Mapping");
+            final Node http = maybeCreateNode(map, "http", "sling:Mapping");
+            maybeCreateNode(http, "localhost.80", "sling:Mapping");
+            final Node https = maybeCreateNode(map, "https", "sling:Mapping");
+            maybeCreateNode(https, "localhost.443", "sling:Mapping");
+            toDelete.add(map.getPath());
+            toDelete.add(rootNode.getPath());
+        }
+        
+        mapRoot = session.getNode("/etc");
+    }
+    
+    @AfterClass
+    public static void deleteTestNodes() throws Exception {
+        logger.debug("{} test done, deleting test nodes", MappingEventsTest.class.getSimpleName());
+        
+        try {
+            for(String path : toDelete) {
+                if(session.itemExists(path)) {
+                    session.getItem(path).remove();
+                }
+            }
+            toDelete.clear();
+            session.save();
+        } finally {
+            session.logout();
+        }
+    }
+    
+    /** Test SLING-4058 - unexpected timeouts in saveMappings */
+    @Test public void testSaveMappings() throws Exception {
+        final Node base = mapRoot.getNode("map/https/localhost.443");
+        final MappingsFacade f = new MappingsFacade(eventsCounter);
+        try {
+            int count = 20;
+            while(count-- > 0) {
+                base.setProperty(PROP_REDIRECT_EXTERNAL,"http://somehost." + count);
+                final String result = f.saveMappings(session);
+                if(result != null) {
+                    fail(result);
+                }
+            }
+        } finally {
+            base.setProperty(PROP_REDIRECT_EXTERNAL,"");
+            session.save();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/MappingsFacade.java b/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/MappingsFacade.java
new file mode 100644
index 0000000..9ee3f6e
--- /dev/null
+++ b/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/MappingsFacade.java
@@ -0,0 +1,64 @@
+/*
+ * 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.launchpad.testservices.serversidetests;
+
+import javax.jcr.Session;
+
+import org.apache.sling.launchpad.testservices.events.EventsCounter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Facade for saving mappings and waiting for the corresponding events */
+public class MappingsFacade {
+
+    public static final String MAPPING_EVENT_TOPIC = "org/apache/sling/api/resource/ResourceResolverMapping/CHANGED";
+    private static final Logger logger = LoggerFactory.getLogger(MappingsFacade.class);
+    private final EventsCounter eventsCounter;
+    
+    // How long to wait for mapping updates
+    public static final String MAPPING_UPDATE_TIMEOUT_MSEC = "ResourceResolverTest.mapping.update.timeout.msec";
+    private static final long updateTimeout = Long.valueOf(System.getProperty(MAPPING_UPDATE_TIMEOUT_MSEC, "10000"));
+
+    public MappingsFacade(EventsCounter c) {
+        logger.info("updateTimeout = {}, use {} system property to change", updateTimeout, MAPPING_UPDATE_TIMEOUT_MSEC);
+        eventsCounter = c;
+    }
+    
+    /** Save a Session that has mapping changes, and wait for the OSGi event
+     *  that signals that mappings have been updated.
+     *  @return error message, null if ok
+     */
+    public String saveMappings(Session session) throws Exception {
+        final int oldEventsCount = eventsCounter.getEventsCount(MAPPING_EVENT_TOPIC);
+        logger.debug("Saving Session and waiting for event counter {} to change from current value {}", MAPPING_EVENT_TOPIC, oldEventsCount);
+        session.save();
+        final long timeout = System.currentTimeMillis() + updateTimeout;
+        while(System.currentTimeMillis() < timeout) {
+            if(eventsCounter.getEventsCount(MAPPING_EVENT_TOPIC) != oldEventsCount) {
+                // Sleeping here shouldn't be needed but it looks
+                // like mappings are not immediately updated once the event arrives
+                Thread.sleep(updateTimeout / 50);
+                return null;
+            }
+            try {
+                Thread.sleep(10);
+            } catch(InterruptedException ignore) {
+            }
+        }
+        return "Timeout waiting for " + MAPPING_EVENT_TOPIC + " event, after " + updateTimeout + " msec";
+    }
+}
diff --git a/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/ResourceResolverTest.java b/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/ResourceResolverTest.java
index b2d0f11..906573b 100644
--- a/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/ResourceResolverTest.java
+++ b/src/main/java/org/apache/sling/launchpad/testservices/serversidetests/ResourceResolverTest.java
@@ -70,7 +70,8 @@
     private String [] vanity;
     private static List<String> toDelete = new ArrayList<String>();
     private static ResourceResolverFactory cleanupResolverFactory;
-    private static String eventTimeoutTopic;
+    private MappingsFacade mappingsFacade;
+    private static String saveMappingsError;
     
     @TestReference
     private EventsCounter eventsCounter;
@@ -78,44 +79,6 @@
     @TestReference
     private ResourceResolverFactory resourceResolverFactory;  
     
-    // How long to wait for mapping updates
-    public static final String MAPPING_UPDATE_TIMEOUT_MSEC = "ResourceResolverTest.mapping.update.timeout.msec";
-    private static final long updateTimeout = Long.valueOf(System.getProperty(MAPPING_UPDATE_TIMEOUT_MSEC, "10000"));
-
-    public ResourceResolverTest() throws Exception {
-        logger.info("updateTimeout = {}, use {} system property to change", updateTimeout, MAPPING_UPDATE_TIMEOUT_MSEC);
-    }
-    
-    /** Save a Session that has mapping changes, and wait for the OSGi event
-     *  that signals that mappings have been updated.
-     */
-    private void saveMappings(Session session) throws Exception {
-        if(eventTimeoutTopic != null) {
-            // Avoid wasting a lot of time if events are not detected in timely fashion
-            fail("Event timeout (" + eventTimeoutTopic + ") detected in previous tests, failing saveMappings()");
-        }
-        
-        final int oldEventsCount = eventsCounter.getEventsCount(MAPPING_EVENT_TOPIC);
-        logger.debug("Saving Session and waiting for event counter {} to change from current value {}", MAPPING_EVENT_TOPIC, oldEventsCount);
-        session.save();
-        final long timeout = System.currentTimeMillis() + updateTimeout;
-        while(System.currentTimeMillis() < timeout) {
-            if(eventsCounter.getEventsCount(MAPPING_EVENT_TOPIC) != oldEventsCount) {
-                // Sleeping here shouldn't be needed but it looks
-                // like mappings are not immediately updated once the event arrives
-                Thread.sleep(updateTimeout / 50);
-                return;
-            }
-            try {
-                Thread.sleep(10);
-            } catch(InterruptedException ignore) {
-            }
-        }
-        eventTimeoutTopic = MAPPING_EVENT_TOPIC;
-        logger.error("Timeout waiting for event counter {} to change from current value {}", MAPPING_EVENT_TOPIC, oldEventsCount);
-        fail("Timeout waiting for " + MAPPING_EVENT_TOPIC + " event, after " + updateTimeout + " msec");
-    }
-    
     private Node maybeCreateNode(Node parent, String name, String type) throws RepositoryException {
         if(parent.hasNode(name)) {
             return parent.getNode(name);
@@ -123,6 +86,20 @@
             return parent.addNode(name, type);
         }
     }
+    
+    private void saveMappings(Session s) throws Exception {
+        if(mappingsFacade == null) {
+            mappingsFacade = new MappingsFacade(eventsCounter);
+        }
+        if(saveMappingsError != null) {
+            fail(saveMappingsError);
+        } else {
+            saveMappingsError = mappingsFacade.saveMappings(s);
+            if(saveMappingsError != null) {
+                fail(saveMappingsError);
+            }
+        }
+    }
 
     @Before
     public synchronized void setup() throws Exception {
@@ -173,7 +150,7 @@
     @AfterClass
     @BeforeClass
     public static void clearTimeouts() {
-        eventTimeoutTopic = null;
+        saveMappingsError = null;
     }
     
     @AfterClass
@@ -195,7 +172,7 @@
             resolver.close();
         }
     }
-
+    
     @Test public void test_clone_based_on_anonymous() throws Exception {
         final ResourceResolver anon0 = this.resourceResolverFactory.getResourceResolver((Map<String, Object>) null);
         final Session anon0Session = anon0.adaptTo(Session.class);