You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:27:12 UTC

[sling-org-apache-sling-discovery-commons] 05/07: SLING-5621 : adding InitDelayingTopologyEventListener that came up as part of SLING-5560 to discovery.commons

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.12
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 202f5b97b1de750419a3dc5b41b70fa0cc181d51
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Mar 21 14:16:26 2016 +0000

    SLING-5621 : adding InitDelayingTopologyEventListener that came up as part of SLING-5560 to discovery.commons
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1735979 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/InitDelayingTopologyEventListener.java | 244 +++++++++++
 .../sling/discovery/commons/package-info.java      |   2 +-
 .../TestInitDelayingTopologyEventListener.java     | 459 +++++++++++++++++++++
 3 files changed, 704 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/InitDelayingTopologyEventListener.java b/src/main/java/org/apache/sling/discovery/commons/InitDelayingTopologyEventListener.java
new file mode 100644
index 0000000..4014f80
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/InitDelayingTopologyEventListener.java
@@ -0,0 +1,244 @@
+/*
+ * 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.discovery.commons;
+
+import java.sql.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This listener facade applies a 'startup delay' to a topology event handler,
+ * that is, it allows to specify a startup time during which any topology events
+ * will be queued and processing only starts after this time.
+ * <p>
+ * What happens aFter the startup time depends on what was received during the delay:
+ * <ul>
+ *  <li>if no events were received then this is a no-op</li>
+ *  <li>if the last event received was a CHANGING then this facade
+ *      waits until it receives the next non-CHANGING event (which should
+ *      be the very next), to then simulate an INIT event
+ *      (as the discovery API says the first event received is an INIT event).
+ *      </li>
+ *  <li>if the last event received was not a CHANGING event
+ *      (ie it was an INIT, CHANGED or PROPERTIES), then 
+ *      as soon as the startup time passes this facade will simulate
+ *      an INIT event
+ *      (again, as the discovery API says the first event received is an INIT event)
+ *      </li>
+ * </ul>
+ * Note that users of this facade must call dispose to avoid any async calls
+ * to the delegate after startup, in case they themselves are deactivated!
+ * @since 1.1.0
+ */
+public class InitDelayingTopologyEventListener implements TopologyEventListener {
+
+    /** the logger used by this listener - can be set in the constructor optionally to fit better into user log structure */
+    private final Logger logger;
+
+    /** the delegate which does the actual event processing after delaying **/
+    private final TopologyEventListener delegate;
+
+    /** the sync object used to guard asynchronous discovery events and scheduler callback **/
+    private final Object syncObj = new Object();
+
+    /** the guarded flag indicating whether this listener is active or disposed - used between scheduler callback and dispose **/
+    private final AtomicBoolean active = new AtomicBoolean(false);
+
+    /** flag indicating whether we're still delaying or not **/
+    private boolean delaying = true;
+    
+    /** the last pending delayed event - we only have to keep the last event to support all different cases **/
+    private TopologyEvent pendingDelayedEvent = null;
+
+    /** 
+     * Creates a new init-delaying listener with the given delay, delegate and scheduler.
+     * <p>
+     * For properly disposing the caller should use the dispose method!
+     * @see #dispose()
+     */
+    public InitDelayingTopologyEventListener(final long startupDelay, final TopologyEventListener delegate, final Scheduler scheduler) {
+        this(startupDelay, delegate, scheduler, null);
+    }
+
+    /**
+     * Creates a new init-delaying listener with the given delay, delegate, scheduler and optinoal logger.
+     * <p>
+     * For properly disposing the caller should use the dispose method!
+     * @see #dispose()
+     */
+    public InitDelayingTopologyEventListener(final long startupDelay, final TopologyEventListener delegate, 
+            final Scheduler scheduler, final Logger loggerOrNull) {
+        if ( scheduler == null ) {
+            throw new IllegalArgumentException("scheduler must not be null");
+        }
+        if ( delegate == null ) {
+            throw new IllegalArgumentException("delegate must not be null");
+        }
+        if ( startupDelay <= 0) {
+            throw new IllegalArgumentException("startupDelay must be greater than 0, not " + startupDelay);
+        }
+        this.delegate = delegate;
+        if ( loggerOrNull != null ) {
+            this.logger = loggerOrNull;
+        } else {
+            this.logger = LoggerFactory.getLogger(this.getClass());
+        }
+        final Runnable r = new Runnable() {
+            
+            @Override
+            public void run() {
+                if (InitDelayingTopologyEventListener.this.active.get()) {
+                    // only call afterStartupDelay if we're active
+                    // (to avoid this call if disposed in the meantime)
+                    
+                    // and since after disposing this listener is no longer
+                    // used - ie it is a throw-away - you can create
+                    // such a listener on each activate and dispose it on
+                    // deactivate and you'll be fine.
+
+                    // in any case - time for calling afterStartupDelay here:
+                    afterStartupDelay();
+                }
+            }
+        };
+        
+        // mark this listener as active
+        this.active.set(true);
+
+        // schedule me if you can
+        if ( !scheduler.schedule(r, scheduler.AT(new Date(System.currentTimeMillis() + startupDelay * 1000))) ) {
+            // if for whatever reason scheduling doesn't work, let's run now
+            logger.warn("activate: could not schedule startupDelay handler with scheduler ({}) - "
+                    + "thus starting ({}) immediately", scheduler, delegate);
+            r.run();
+        }
+        // SLING-5560 : at this point either r is invoked immediately or scheduled after the delay
+    }
+    
+    @Override
+    public void handleTopologyEvent(TopologyEvent event) {
+        synchronized ( syncObj ) {
+            if ( this.delaying ) {
+                // when we're delaying, keep hold of the last event
+                // as action afterStartupDelay depends on this last
+                // event
+                this.logger.debug("handleTopologyEvent: delaying processing of received topology event (startup delay active) {}", event);
+                this.pendingDelayedEvent = event;
+                
+                // and we're delaying - so stop processing now and return
+                return;
+            } else if ( this.pendingDelayedEvent != null ) {
+                // this means that while we're no longer delaying we still
+                // have a pending delayed event to process.
+                // more concretely, it means that this event is a CHANGING
+                // event (as the others are treated via an artificial INIT event
+                // in afterStartupDelay).
+                // which means that we must now convert the new event into an INIT
+                // to ensure our code gets an INIT first thing
+                
+                // paranoia check:
+                if ( event.getType() == Type.TOPOLOGY_CHANGING ) {
+                    // this should never happen - but if it does, rinse and repeat
+                    this.pendingDelayedEvent = event;
+                    this.logger.info("handleTopologyEvent: ignoring received topology event of type CHANGING {}", event);
+                    return;
+                } else {
+                    // otherwise we now **convert** the event to an init event
+                    this.pendingDelayedEvent = null;
+                    this.logger.debug("handleTopologyEvent: first stable topology event received after startup delaying. "
+                            + "Simulating an INIT event with this new view: {}", event);
+                    event = new TopologyEvent(Type.TOPOLOGY_INIT, null, event.getNewView());
+                }
+            } else {
+                // otherwise we're no longer delaying nor do we have a pending-backlog.
+                // we can just pass the event through
+                this.logger.debug("handleTopologyEvent: received topology event {}", event);
+            }
+        }
+        // no delaying applicable - call delegate
+        this.delegate.handleTopologyEvent(event);
+    }
+    
+    /**
+     * Marks this listener as no longer active - ensures that it doesn't call the delegate
+     * via any potentially pending scheduler callback.
+     * <p>
+     * Note that after dispose you can *still* call handleTopologyEvent and the events
+     * are passed to the delegate - but those are expected to be 'late' events and not 
+     * really part of the normal game. Hence, the caller must also ensure that the 
+     * handleTopologyEvent method isn't called anymore (which typically is automatically
+     * guaranteed since the caller is typically an osgi service that gets unregistered anyway)
+     */
+    public void dispose() {
+        this.active.set(false);
+    }
+
+    /**
+     * Called via the scheduler callback when the startup delay has passed.
+     * <p>
+     * Hence only called once!
+     */
+    private void afterStartupDelay() {
+        synchronized ( this.syncObj ) {
+            // stop any future delaying
+            this.delaying = false;
+            
+            if ( this.pendingDelayedEvent == null ) {
+                // if no event received while we delayed,
+                // then we don't have to do anything later
+                this.logger.debug("afterStartupDelay: startup delay passed without any events delayed. "
+                        + "So, ready for first upcoming INIT event");
+            } else if ( this.pendingDelayedEvent.getType() == Type.TOPOLOGY_CHANGING ) {
+                // if the last delayed event was CHANGING
+                // then we must convert the next upcoming CHANGED, PROPERTIES
+                // into an INIT
+                
+                // and the way this is done in this class is by leving this
+                // event sit in this.pendingDelayedEvent, for grabs in handleTopologyEvent later
+                this.logger.debug("afterStartupDelay: startup delay passed, pending delayed event was CHANGING. "
+                        + "Waiting for next stable topology event");
+            } else {
+                // otherwise the last delayed event was either an INIT, CHANGED or PROPERTIES
+                // - but in any case we definitely never 
+                // processed any INIT - and our code expects an INIT
+                // as the first event ever..
+                
+                // so we now convert the event into an INIT
+                final TopologyEvent artificialInitEvent = 
+                        new TopologyEvent(Type.TOPOLOGY_INIT, null, this.pendingDelayedEvent.getNewView());
+                
+                this.logger.debug("afterStartupDelay: startup delay passed, last pending delayed event was stable ({}). "
+                        + "Simulating an INIT event with that view: {}", this.pendingDelayedEvent, artificialInitEvent); 
+                this.pendingDelayedEvent = null;
+                
+                // call the delegate.
+                // we must do this call in the synchronized block
+                // to ensure any concurrent new event waits properly
+                // before the INIT is done
+                delegate.handleTopologyEvent(artificialInitEvent);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/package-info.java b/src/main/java/org/apache/sling/discovery/commons/package-info.java
index 8ed120a..427fb1a 100644
--- a/src/main/java/org/apache/sling/discovery/commons/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/package-info.java
@@ -22,7 +22,7 @@
  *
  * @version 1.0.0
  */
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.discovery.commons;
 
 import aQute.bnd.annotation.Version;
diff --git a/src/test/java/org/apache/sling/discovery/commons/TestInitDelayingTopologyEventListener.java b/src/test/java/org/apache/sling/discovery/commons/TestInitDelayingTopologyEventListener.java
new file mode 100644
index 0000000..8f224da
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/TestInitDelayingTopologyEventListener.java
@@ -0,0 +1,459 @@
+/*
+ * 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.discovery.commons;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.apache.sling.commons.scheduler.ScheduleOptions;
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.TopologyView;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestInitDelayingTopologyEventListener {
+    
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+    
+    class TestListener implements TopologyEventListener {
+
+        private List<TopologyEvent> events = new LinkedList<TopologyEvent>();
+        
+        @Override
+        public void handleTopologyEvent(TopologyEvent event) {
+            synchronized(events) {
+                events.add(event);
+                events.notifyAll();
+            }
+        }
+        
+        public List<TopologyEvent> getEvents() {
+            synchronized(events) {
+                return events;
+            }
+        }
+
+        public void waitForEventCnt(int cnt, long timeout) throws InterruptedException {
+            final long start = System.currentTimeMillis();
+            synchronized(events) {
+                while (events.size() != cnt) {
+                    final long now = System.currentTimeMillis();
+                    final long remaining = (start + timeout) - now;
+                    if (remaining > 0) {
+                        events.wait(remaining);
+                    } else {
+                        fail("did not receive " + cnt + " events within " + timeout + " ms, "
+                                + "but " + events.size());
+                    }
+                }
+            }
+        }
+
+        public void assureEventCnt(int cnt, int timeout) throws InterruptedException {
+            final long start = System.currentTimeMillis();
+            synchronized(events) {
+                while (events.size() == cnt) {
+                    final long now = System.currentTimeMillis();
+                    final long remaining = (start + timeout) - now;
+                    if (remaining > 0) {
+                        events.wait(remaining);
+                    } else {
+                        // success
+                        return;
+                    }
+                }
+                fail("did not receive " + cnt + " events within " + timeout + " ms, "
+                        + "but " + events.size());
+            }
+        }
+    }
+
+    private Scheduler createScheduler() {
+        return new Scheduler() {
+
+            @Override
+            public boolean unschedule(String jobName) {
+                // TODO Auto-generated method stub
+                return false;
+            }
+
+            @Override
+            public boolean schedule(final Object job, ScheduleOptions options) {
+                if ( job instanceof Runnable ) {
+                    final Timer t = new Timer();
+                    t.schedule(new TimerTask() {
+
+                        @Override
+                        public void run() {
+                            ((Runnable)job).run();
+                        }
+                    }, 300);
+                    return true;
+                }
+                return false;
+            }
+
+            @Override
+            public void removeJob(String name) throws NoSuchElementException {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            public boolean fireJobAt(String name, Object job, Map<String, Serializable> config, Date date, int times,
+                    long period) {
+                // TODO Auto-generated method stub
+                return false;
+            }
+
+            @Override
+            public void fireJobAt(String name, Object job, Map<String, Serializable> config, Date date) throws Exception {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            public boolean fireJob(Object job, Map<String, Serializable> config, int times, long period) {
+                // TODO Auto-generated method stub
+                return false;
+            }
+
+            @Override
+            public void fireJob(Object job, Map<String, Serializable> config) throws Exception {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            public void addPeriodicJob(String name, Object job, Map<String, Serializable> config, long period,
+                    boolean canRunConcurrently, boolean startImmediate) throws Exception {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            public void addPeriodicJob(String name, Object job, Map<String, Serializable> config, long period,
+                    boolean canRunConcurrently) throws Exception {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            public void addJob(String name, Object job, Map<String, Serializable> config, String schedulingExpression,
+                    boolean canRunConcurrently) throws Exception {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            public ScheduleOptions NOW(int times, long period) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public ScheduleOptions NOW() {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public ScheduleOptions EXPR(String expression) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public ScheduleOptions AT(Date date, int times, long period) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public ScheduleOptions AT(Date date) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+        };
+    }
+
+    @Test
+    public void testConstructor() throws Exception {
+        final TopologyEventListener delegate = new TopologyEventListener() {
+            
+            @Override
+            public void handleTopologyEvent(TopologyEvent event) {
+                // nothing here atm
+            }
+        };
+        final Scheduler scheduler = createScheduler();
+        try{
+            new InitDelayingTopologyEventListener(-1, delegate, scheduler);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(0, delegate, scheduler);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(1, null, scheduler);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(1, delegate, null);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(-1, delegate, scheduler, null);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(0, delegate, scheduler, null);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(1, null, scheduler, null);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(1, delegate, null, null);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(-1, delegate, scheduler, logger);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(0, delegate, scheduler, logger);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(1, null, scheduler, logger);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+        try{
+            new InitDelayingTopologyEventListener(1, delegate, null, logger);
+            fail("should complain");
+        } catch(IllegalArgumentException re) {
+            // ok
+        }
+    }
+    
+    private TopologyView createView(boolean current) {
+        final TopologyView view = Mockito.mock(TopologyView.class);
+        Mockito.when(view.isCurrent()).thenReturn(current);
+        final InstanceDescription local = Mockito.mock(InstanceDescription.class);
+        Mockito.when(local.isLeader()).thenReturn(true);
+        Mockito.when(local.isLocal()).thenReturn(true);
+        Mockito.when(local.getSlingId()).thenReturn("id");
+
+        Mockito.when(view.getLocalInstance()).thenReturn(local);
+        final ClusterView localView = Mockito.mock(ClusterView.class);
+        Mockito.when(localView.getId()).thenReturn("1");
+        Mockito.when(localView.getInstances()).thenReturn(Collections.singletonList(local));
+        Mockito.when(view.getClusterViews()).thenReturn(Collections.singleton(localView));
+        Mockito.when(local.getClusterView()).thenReturn(localView);
+
+        return view;
+    }
+
+    private TopologyEvent createEvent(Type type) {
+        TopologyView oldView = createView(false);
+        TopologyView newView = createView(true);
+        switch(type) {
+            case TOPOLOGY_CHANGING : {
+                return new TopologyEvent(type, oldView, null);
+            }
+            case PROPERTIES_CHANGED :
+            case TOPOLOGY_CHANGED : {
+                return new TopologyEvent(type, oldView, newView);
+            }
+            case TOPOLOGY_INIT : {
+                return new TopologyEvent(type, null, newView);
+            }
+            default : {
+                throw new IllegalArgumentException("unknown type: " + type);
+            }
+        }
+    }
+    
+    @Test
+    public void testDisposing() throws Exception {
+        final TestListener delegate = new TestListener();
+        final Scheduler scheduler = createScheduler();
+        InitDelayingTopologyEventListener listener = new InitDelayingTopologyEventListener(1, delegate, scheduler, logger);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_INIT));
+        delegate.waitForEventCnt(1, 5000);
+        delegate.assureEventCnt(1, 500); // test framework testing :)
+
+        listener = new InitDelayingTopologyEventListener(1, delegate, scheduler, logger);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_INIT));
+        listener.dispose();
+        delegate.assureEventCnt(1, 1000);
+        delegate.assureEventCnt(1, 500);
+    }
+    
+    @Test
+    public void testNoEvents() throws Exception {
+        final TestListener delegate = new TestListener();
+        final Scheduler scheduler = createScheduler();
+        InitDelayingTopologyEventListener listener = new InitDelayingTopologyEventListener(1, delegate, scheduler, logger);
+        // no events:
+        delegate.assureEventCnt(0, 1500);
+        
+        // then the first init is passed through
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_INIT));
+        delegate.waitForEventCnt(1, 5000);
+        assertEquals(delegate.getEvents().get(0).getType(), Type.TOPOLOGY_INIT);
+
+        doTestAdditionalEventsAfterInit(delegate, listener);
+    }
+
+    private void doTestAdditionalEventsAfterInit(final TestListener delegate, InitDelayingTopologyEventListener listener)
+            throws InterruptedException {
+        // 2nd one too
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        delegate.waitForEventCnt(2, 5000);
+        assertEquals(delegate.getEvents().get(1).getType(), Type.TOPOLOGY_CHANGING);
+
+        // 3rd one too
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        delegate.waitForEventCnt(3, 5000);
+        assertEquals(delegate.getEvents().get(2).getType(), Type.TOPOLOGY_CHANGED);
+
+        // 4th one too
+        listener.handleTopologyEvent(createEvent(Type.PROPERTIES_CHANGED));
+        delegate.waitForEventCnt(4, 5000);
+        assertEquals(delegate.getEvents().get(3).getType(), Type.PROPERTIES_CHANGED);
+
+        // 5th one too
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        delegate.waitForEventCnt(5, 5000);
+        assertEquals(delegate.getEvents().get(4).getType(), Type.TOPOLOGY_CHANGING);
+
+        // 6th one too
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        delegate.waitForEventCnt(6, 5000);
+        assertEquals(delegate.getEvents().get(5).getType(), Type.TOPOLOGY_CHANGED);
+
+    }
+
+    @Test
+    public void testChanging0() throws Exception {
+        final TestListener delegate = new TestListener();
+        final Scheduler scheduler = createScheduler();
+        InitDelayingTopologyEventListener listener = new InitDelayingTopologyEventListener(1, delegate, scheduler, logger);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_INIT));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        delegate.assureEventCnt(0, 1000);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        delegate.waitForEventCnt(1, 5000);
+        assertEquals(delegate.getEvents().get(0).getType(), Type.TOPOLOGY_INIT);
+
+        doTestAdditionalEventsAfterInit(delegate, listener);
+    }
+
+    @Test
+    public void testChanging1() throws Exception {
+        final TestListener delegate = new TestListener();
+        final Scheduler scheduler = createScheduler();
+        InitDelayingTopologyEventListener listener = new InitDelayingTopologyEventListener(1, delegate, scheduler, logger);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_INIT));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        delegate.assureEventCnt(0, 1000);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        delegate.waitForEventCnt(1, 5000);
+        assertEquals(delegate.getEvents().get(0).getType(), Type.TOPOLOGY_INIT);
+
+        doTestAdditionalEventsAfterInit(delegate, listener);
+    }
+
+    @Test
+    public void testChanged() throws Exception {
+        final TestListener delegate = new TestListener();
+        final Scheduler scheduler = createScheduler();
+        InitDelayingTopologyEventListener listener = new InitDelayingTopologyEventListener(1, delegate, scheduler, logger);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_INIT));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        delegate.waitForEventCnt(1, 5000);
+        assertEquals(delegate.getEvents().get(0).getType(), Type.TOPOLOGY_INIT);
+
+        doTestAdditionalEventsAfterInit(delegate, listener);
+    }
+
+    @Test
+    public void testProperties() throws Exception {
+        final TestListener delegate = new TestListener();
+        final Scheduler scheduler = createScheduler();
+        InitDelayingTopologyEventListener listener = new InitDelayingTopologyEventListener(1, delegate, scheduler, logger);
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_INIT));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGING));
+        listener.handleTopologyEvent(createEvent(Type.TOPOLOGY_CHANGED));
+        listener.handleTopologyEvent(createEvent(Type.PROPERTIES_CHANGED));
+        delegate.waitForEventCnt(1, 5000);
+        assertEquals(delegate.getEvents().get(0).getType(), Type.TOPOLOGY_INIT);
+
+        doTestAdditionalEventsAfterInit(delegate, listener);
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.