| /* |
| * 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.event.impl.discovery; |
| |
| import java.util.Timer; |
| import java.util.TimerTask; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| 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. |
| * |
| * 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! |
| * |
| * This class is a cleaned copy of <code>InitDelayingTopologyEventListener</code> from discovery commons. |
| * |
| **/ |
| public class InitDelayingTopologyEventListener implements TopologyEventListener { |
| |
| /** 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 volatile boolean delaying = true; |
| |
| /** the last pending delayed event - we only have to keep the last event to support all different cases **/ |
| private volatile TopologyEvent pendingDelayedEvent; |
| |
| private final Logger logger = LoggerFactory.getLogger(InitDelayingTopologyEventListener.class); |
| |
| /** |
| * Creates a new init-delaying listener with the given delay and delegate. |
| * <p> |
| * For properly disposing the caller should use the dispose method! |
| * @param startupDelay The startup delay in seconds |
| * @param delegate The topology event listener |
| * @see #dispose() |
| */ |
| public InitDelayingTopologyEventListener(final long startupDelay, final TopologyEventListener delegate) { |
| |
| 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; |
| |
| 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 |
| final Timer timer = new Timer(); |
| final TimerTask task = new TimerTask() { |
| |
| @Override |
| public void run() { |
| r.run(); |
| } |
| }; |
| timer.schedule(task, startupDelay * 1000); |
| // 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); |
| } |
| } |
| } |
| } |