SLING-5756 - Async appender is not started upon reattach in case of log config change

stop and start appender in detach and attach calls respectively. Copied RetryLoop from Sling Testing Tools

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1764709 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/commons/log/logback/internal/AppenderTracker.java b/src/main/java/org/apache/sling/commons/log/logback/internal/AppenderTracker.java
index cda97ce..b45f457 100644
--- a/src/main/java/org/apache/sling/commons/log/logback/internal/AppenderTracker.java
+++ b/src/main/java/org/apache/sling/commons/log/logback/internal/AppenderTracker.java
@@ -62,7 +62,6 @@
     public Object addingService(final ServiceReference reference) {
         final Appender<ILoggingEvent> appender = (Appender<ILoggingEvent>) super.addingService(reference);
         appender.setContext(loggerContext);
-        appender.start();
 
         final AppenderInfo ai = new AppenderInfo(reference, appender);
         appenders.put(reference, ai);
@@ -84,11 +83,6 @@
     public void removedService(final ServiceReference reference, final Object service) {
         final AppenderInfo ai = appenders.remove(reference);
         this.detachAppender(ai);
-        if ( ai != null ) {
-            ai.appender.stop();
-            // context can't be unset
-        }
-
         super.removedService(reference, service);
     }
 
@@ -103,22 +97,27 @@
 
                 logger.detachAppender(ai.appender);
             }
+            ai.appender.stop();
         }
     }
 
     private void attachAppender(final AppenderInfo ai) {
         if (ai != null) {
+
+            ai.appender.start();
+
             for (final String name : ai.getLoggers()) {
                 final Logger logger = loggerContext.getLogger(name);
-
-                logger.addAppender(ai.appender);
+                if (!logger.isAttached(ai.appender)) {
+                    logger.addAppender(ai.appender);
+                }
             }
         }
     }
 
     @Override
     public void onResetStart(final LoggerContext context) {
-        attachAppenders();
+
     }
 
     @Override
diff --git a/src/test/java/org/apache/sling/commons/log/logback/integration/ITAppenderServices.java b/src/test/java/org/apache/sling/commons/log/logback/integration/ITAppenderServices.java
index 2b4f278..7762664 100644
--- a/src/test/java/org/apache/sling/commons/log/logback/integration/ITAppenderServices.java
+++ b/src/test/java/org/apache/sling/commons/log/logback/integration/ITAppenderServices.java
@@ -42,8 +42,11 @@
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.sling.commons.log.logback.integration.ITConfigFragments.RESET_EVENT_TOPIC;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -62,6 +65,9 @@
 
     private ServiceRegistration sr;
 
+    @Inject
+    private EventAdmin eventAdmin;
+
     static {
         // uncomment to enable debugging of this test class
         // paxRunnerVmOption = DEBUG_VM_OPTION;
@@ -70,7 +76,10 @@
 
     @Override
     protected Option addExtraOptions() {
-        return composite(configAdmin(), mavenBundle("commons-io", "commons-io").versionAsInProject());
+        return composite(
+                configAdmin(), mavenBundle("commons-io", "commons-io").versionAsInProject(),
+                mavenBundle("org.apache.felix", "org.apache.felix.eventadmin").versionAsInProject()
+        );
     }
 
     @Test
@@ -168,6 +177,32 @@
         assertEquals(2, ta.events.size());
     }
 
+    @Test
+    public void appenderRestartPostReset() throws Exception{
+        final TestAppender ta = registerAppender("ROOT");
+        delay();
+
+        assertTrue(ta.isStarted());
+        final int stopCount = ta.stopCount;
+        final int startCount = ta.startCount;
+
+        eventAdmin.sendEvent(new Event(RESET_EVENT_TOPIC, (Dictionary)null));
+
+        new RetryLoop(new RetryLoop.Condition() {
+            @Override
+            public String getDescription() {
+                return "Stopcount not increased";
+            }
+
+            @Override
+            public boolean isTrue() throws Exception {
+                return ta.stopCount > stopCount && ta.startCount > startCount;
+            }
+        }, 10, 100);
+
+        assertTrue(ta.isStarted());
+    }
+
     @After
     public void unregisterAppender(){
         sr.unregister();
@@ -187,6 +222,8 @@
     private static class TestAppender extends AppenderBase<ILoggingEvent> {
         final List<ILoggingEvent> events = new ArrayList<ILoggingEvent>();
         final List<String> msgs = new ArrayList<String>();
+        int stopCount;
+        int startCount;
 
         @Override
         protected void append(ILoggingEvent eventObject) {
@@ -203,6 +240,18 @@
             events.clear();
             msgs.clear();
         }
+
+        @Override
+        public void stop() {
+            super.stop();
+            stopCount++;
+        }
+
+        @Override
+        public void start() {
+            super.start();
+            startCount++;
+        }
     }
 
 }
diff --git a/src/test/java/org/apache/sling/commons/log/logback/integration/ITConfigFragments.java b/src/test/java/org/apache/sling/commons/log/logback/integration/ITConfigFragments.java
index e600d3e..58ba484 100644
--- a/src/test/java/org/apache/sling/commons/log/logback/integration/ITConfigFragments.java
+++ b/src/test/java/org/apache/sling/commons/log/logback/integration/ITConfigFragments.java
@@ -45,7 +45,7 @@
 @RunWith(PaxExam.class)
 @ExamReactorStrategy(PerClass.class)
 public class ITConfigFragments extends LogTestBase {
-    private static final String RESET_EVENT_TOPIC = "org/apache/sling/commons/log/RESET";
+    static final String RESET_EVENT_TOPIC = "org/apache/sling/commons/log/RESET";
 
     @Inject
     private BundleContext bundleContext;
diff --git a/src/test/java/org/apache/sling/commons/log/logback/integration/RetryLoop.java b/src/test/java/org/apache/sling/commons/log/logback/integration/RetryLoop.java
new file mode 100644
index 0000000..de5105c
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/log/logback/integration/RetryLoop.java
@@ -0,0 +1,82 @@
+/*
+ * 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.commons.log.logback.integration;
+
+import static org.junit.Assert.fail;
+
+/** Convenience class for retrying tests
+ *  until timeout or success.
+ */
+public class RetryLoop {
+    
+    private final long timeout;
+    
+    /** Interface for conditions to check, isTrue will be called
+     *  repeatedly until success or timeout */
+    public interface Condition {
+        /** Used in failure messages to describe what was expected */
+        String getDescription();
+        
+        /** If true we stop retrying. The RetryLoop retries on AssertionError, 
+         *  so if tests fail in this method they are not reported as 
+         *  failures but retried.
+         */
+        boolean isTrue() throws Exception;
+    }
+    
+    /** Retry Condition c until it returns true or timeout. See {@link Condition}
+     *  for isTrue semantics.
+     */
+    public RetryLoop(Condition c, int timeoutSeconds, int intervalBetweenTriesMsec) {
+        timeout = System.currentTimeMillis() + timeoutSeconds * 1000L;
+        while(System.currentTimeMillis() < timeout) {
+            try {
+                if(c.isTrue()) {
+                    return;
+                }
+            } catch(AssertionError ae) {
+                // Retry JUnit tests failing in the condition as well
+                reportException(ae);
+            } catch(Exception e) {
+                reportException(e);
+            }
+            
+            try {
+                Thread.sleep(intervalBetweenTriesMsec);
+            } catch(InterruptedException ignore) {
+            }
+        }
+    
+        onTimeout();
+        fail("RetryLoop failed, condition is false after " + timeoutSeconds + " seconds: " 
+                + c.getDescription());
+    }
+
+    /** Can be overridden to report Exceptions that happen in the retry loop */
+    protected void reportException(Throwable t) {
+    }
+    
+    /** Called if the loop times out without success, just before failing */
+    protected void onTimeout() {
+    }
+    
+    protected long getRemainingTimeSeconds() {
+        return Math.max(0L, (timeout - System.currentTimeMillis()) / 1000L);
+    }
+}
\ No newline at end of file