Author: stefanegli
Date: Mon May 4 10:11:10 2015
New Revision: 1677574
URL: http://svn.apache.org/r1677574
Log:
SLING-4685 : adding initial version of ViewStateManager - a shared
implementation of TopologyEventListener-handling and sending of events based on
activate/deactivate/changing/newView triggers - intended for use by
implementors of the discovery.api (not clients of it)
Added:
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
(with props)
sling/trunk/bundles/extensions/discovery/commons/src/test/java/org/apache/sling/discovery/commons/providers/
sling/trunk/bundles/extensions/discovery/commons/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
Modified:
sling/trunk/bundles/extensions/discovery/commons/pom.xml
Modified: sling/trunk/bundles/extensions/discovery/commons/pom.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons/pom.xml?rev=1677574&r1=1677573&r2=1677574&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/discovery/commons/pom.xml (original)
+++ sling/trunk/bundles/extensions/discovery/commons/pom.xml Mon May 4
10:11:10 2015
@@ -67,6 +67,11 @@
<artifactId>jsr305</artifactId>
<version>2.0.0</version>
</dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.6.1</version>
+ </dependency>
<!-- Testing -->
<dependency>
<groupId>junit</groupId>
Added:
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java?rev=1677574&view=auto
==============================================================================
---
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
(added)
+++
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
Mon May 4 10:11:10 2015
@@ -0,0 +1,52 @@
+/*
+ * 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.providers;
+
+import org.apache.sling.discovery.TopologyView;
+
+/**
+ * Very simple abstract base class for the TopologyView which
+ * comes with the 'setNotCurrent()' method - that allows the
+ * ViewStateManager to mark a topologyView as no longer current
+ * - and the isCurrent() is handled accordingly.
+ */
+public abstract class BaseTopologyView implements TopologyView {
+
+ /** Whether or not this topology is considered 'current' / ie currently
valid **/
+ private volatile boolean current = true;
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isCurrent() {
+ return current;
+ }
+
+ /**
+ * Marks this view as no longer current - this typically
+ * results in a TOPOLOGY_CHANGING event to be sent.
+ * <p>
+ * Note that once marked as not current, it can no longer
+ * be reverted to current==true
+ */
+ public void setNotCurrent() {
+ current = false;
+ }
+
+}
Added:
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java?rev=1677574&view=auto
==============================================================================
---
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
(added)
+++
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
Mon May 4 10:11:10 2015
@@ -0,0 +1,352 @@
+/*
+ * 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.providers;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+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;
+
+/**
+ * The ViewStateManager is at the core of managing TopologyEventListeners,
+ * the 'view state' (changing vs changed) and sending out the appropriate
+ * and according TopologyEvents to the registered listeners.
+ * <p>
+ * Note that this class is completely unsynchronized and the idea is that
+ * (since this class is only of interest to other implementors/providers of
+ * the discovery.api, not to users of the discovery.api however) that those
+ * other implementors take care of proper synchronization. Without
synchronization
+ * this class is prone to threading issues! The most simple form of
+ * synchronization is to synchronize all of the public (non-static..) methods
+ * with the same monitor object.
+ */
+public class ViewStateManager {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * List of bound listeners that have already received their INIT event -
others are in unInitializedEventListeners.
+ * @see ViewStateManager#unInitializedEventListeners
+ */
+ private List<TopologyEventListener> eventListeners = new
ArrayList<TopologyEventListener>();
+
+ /**
+ * List of bound listeners that have not yet received their TOPOLOGY_INIT
event -
+ * once they are sent the TOPOLOGY_INIT event they are moved to
eventListeners (and stay there).
+ * <p>
+ * This list becomes necessary for cases where the bind() happens before
activate, or after activate but at a time
+ * when the topology is TOPOLOGY_CHANGING - at which point an
TOPOLOGY_INIT event can not yet be sent.
+ * @see ViewStateManager#eventListeners
+ */
+ private List<TopologyEventListener> unInitializedEventListeners = new
ArrayList<TopologyEventListener>();
+
+ /**
+ * Set true when the bundle.activate() was called, false if not yet or
when deactivate() is called.
+ * <p>
+ * This controls whether handleChanging() and handleNewView() should cause
any events
+ * to be sent - which they do not if called before handleActivated() (or
after handleDeactivated())
+ * @see ViewStateManager#handleActivated()
+ * @see ViewStateManager#handleChanging()
+ * @see ViewStateManager#handleNewView(BaseTopologyView)
+ * @see ViewStateManager#handleDeactivated()
+ */
+ private boolean activated;
+
+ /**
+ * Represents the 'newView' passed to handleNewTopologyView at the most
recent invocation.
+ * <p>
+ * This is used for:
+ * <ul>
+ * <li>sending with the TOPOLOGY_INIT event to newly bound listeners or
at activate time</li>
+ * <li>sending as oldView (marked not current) with the TOPOLOGY_CHANGING
event</li>
+ * <li>sending as oldView (marked not current in case handleChanging()
was not invoked) with the TOPOLOGY_CHANGED event</li>
+ * </ul>
+ */
+ private BaseTopologyView previousView;
+
+ /**
+ * Set to true when handleChanging is called - set to false in
handleNewView.
+ * When this goes true, a TOPOLOGY_CHANGING is sent.
+ * When this goes false, a TOPOLOGY_CHANGED is sent.
+ */
+ private boolean isChanging;
+
+ /**
+ * Binds the given eventListener, sending it an INIT event if applicable.
+ * <p>
+ * Note: no synchronization done in ViewStateManager, <b>must</b> be done
externally
+ * @param eventListener the eventListener that is to bind
+ */
+ public void bind(final TopologyEventListener eventListener) {
+
+ logger.debug("bind: Binding TopologyEventListener {}",
+ eventListener);
+
+ if (eventListeners.contains(eventListener) ||
unInitializedEventListeners.contains(eventListener)) {
+ logger.info("bind: TopologyEventListener already registered:
"+eventListener);
+ return;
+ }
+
+ if (activated) {
+ // check to see in which state we are
+ if (isChanging || (previousView==null)) {
+ // then we cannot send the TOPOLOGY_INIT at this point - need
to delay this
+ unInitializedEventListeners.add(eventListener);
+ } else {
+ // otherwise we can send the TOPOLOGY_INIT now
+ sendEvent(eventListener, newInitEvent(previousView));
+ eventListeners.add(eventListener);
+ }
+ } else {
+ unInitializedEventListeners.add(eventListener);
+ }
+ }
+
+ /**
+ * Unbinds the given eventListener, returning whether or not it was bound
at all.
+ * <p>
+ * Note: no synchronization done in ViewStateManager, <b>must</b> be done
externally
+ * @param eventListener the eventListner that is to unbind
+ * @return whether or not the listener was added in the first place
+ */
+ public boolean unbind(final TopologyEventListener eventListener) {
+
+ logger.debug("unbind: Releasing TopologyEventListener {}",
+ eventListener);
+
+ // even though a listener must always only ever exist in one of the
two,
+ // the unbind we do - for safety-paranoia-reasons - remove them from
both
+ final boolean a = eventListeners.remove(eventListener);
+ final boolean b = unInitializedEventListeners.remove(eventListener);
+ return a || b;
+ }
+
+ /** Internal helper method that sends a given event to a list of listeners
**/
+ private void sendEvent(final List<TopologyEventListener> audience, final
TopologyEvent event) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("sendEvent: sending topologyEvent {}, to all ({})
listeners", event, audience.size());
+ }
+ for (Iterator<TopologyEventListener> it = audience.iterator();
it.hasNext();) {
+ TopologyEventListener topologyEventListener = it.next();
+ sendEvent(topologyEventListener, event);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("sendEvent: sent topologyEvent {}, to all ({})
listeners", event, audience.size());
+ }
+ }
+
+ /** Internal helper method that sends a given event to a particular
listener **/
+ private void sendEvent(final TopologyEventListener da, final TopologyEvent
event) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("sendEvent: sending topologyEvent {}, to {}", event,
da);
+ }
+ try{
+ da.handleTopologyEvent(event);
+ } catch(final Exception e) {
+ logger.warn("sendEvent: handler threw exception. handler: "+da+",
exception: "+e, e);
+ }
+ }
+
+ /** Note: no synchronization done in ViewStateManager, <b>must</b> be done
externally **/
+ public void handleActivated() {
+ logger.debug("handleActivated: activating the ViewStateManager");
+ activated = true;
+
+ if (previousView!=null && !isChanging) {
+ sendEvent(unInitializedEventListeners, newInitEvent(previousView));
+ eventListeners.addAll(unInitializedEventListeners);
+ unInitializedEventListeners.clear();
+ }
+ logger.debug("handleActivated: activated the ViewStateManager");
+ }
+
+ /** Simple factory method for creating a TOPOLOGY_INIT event with the
given newView **/
+ public static TopologyEvent newInitEvent(final BaseTopologyView newView) {
+ if (newView==null) {
+ throw new IllegalStateException("newView must not be null");
+ }
+ if (!newView.isCurrent()) {
+ throw new IllegalStateException("newView must be current");
+ }
+ return new TopologyEvent(Type.TOPOLOGY_INIT, null, newView);
+ }
+
+ /** Simple factory method for creating a TOPOLOGY_CHANGING event with the
given oldView **/
+ public static TopologyEvent newChangingEvent(final BaseTopologyView
oldView) {
+ if (oldView==null) {
+ throw new IllegalStateException("oldView must not be null");
+ }
+ if (oldView.isCurrent()) {
+ throw new IllegalStateException("oldView must not be current");
+ }
+ return new TopologyEvent(Type.TOPOLOGY_CHANGING, oldView, null);
+ }
+
+ /** Simple factory method for creating a TOPOLOGY_CHANGED event with the
given old and new views **/
+ public static TopologyEvent newChangedEvent(final BaseTopologyView
oldView, final BaseTopologyView newView) {
+ if (oldView==null) {
+ throw new IllegalStateException("oldView must not be null");
+ }
+ if (oldView.isCurrent()) {
+ throw new IllegalStateException("oldView must not be current");
+ }
+ if (newView==null) {
+ throw new IllegalStateException("newView must not be null");
+ }
+ if (!newView.isCurrent()) {
+ throw new IllegalStateException("newView must be current");
+ }
+ return new TopologyEvent(Type.TOPOLOGY_CHANGED, oldView, newView);
+ }
+
+ /**
+ * Must be called when the corresponding service (typically a
DiscoveryService implementation)
+ * is deactivated.
+ * <p>
+ * Will mark this manager as deactivated and flags the last available view
as not current.
+ * <p>
+ * Note: no synchronization done in ViewStateManager, <b>must</b> be done
externally
+ */
+ public void handleDeactivated() {
+ logger.debug("handleDeactivated: deactivating the ViewStateManager");
+ activated = false;
+
+ if (previousView!=null) {
+ previousView.setNotCurrent();
+ previousView = null;
+ }
+ isChanging = false;
+
+ eventListeners.clear();
+ unInitializedEventListeners.clear();
+ logger.debug("handleDeactivated: deactivated the ViewStateManager");
+ }
+
+ /** Note: no synchronization done in ViewStateManager, <b>must</b> be done
externally **/
+ public void handleChanging() {
+ logger.debug("handleChanging: start");
+
+ if (isChanging) {
+ // if isChanging: then this is no news
+ // hence: return asap
+ logger.debug("handleChanging: was already changing - ignoring.");
+ return;
+ }
+
+ // whether activated or not: set isChanging to true now
+ isChanging = true;
+
+ if (!activated) {
+ // if not activated: we can only start sending events once
activated
+ // hence returning here - after isChanging was set to true
accordingly
+
+ // note however, that if !activated, there should be no
eventListeners yet
+ // all of them should be in unInitializedEventListeners at the
moment
+ // waiting for activate() and handleNewTopologyView
+ logger.debug("handleChanging: not yet activated - ignoring.");
+ return;
+ }
+
+ if (previousView==null) {
+ // then nothing further to do - this is a very early changing event
+ // before even the first view was available
+ logger.debug("handleChanging: no previousView set - ignoring.");
+ return;
+ }
+
+ logger.debug("handleChanging: sending TOPOLOGY_CHANGING to initialized
listeners");
+ previousView.setNotCurrent();
+ sendEvent(eventListeners, newChangingEvent(previousView));
+ logger.debug("handleChanging: end");
+ }
+
+ /** Note: no synchronization done in ViewStateManager, <b>must</b> be done
externally **/
+ public void handleNewView(BaseTopologyView newView) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("handleNewView: start, newView={}", newView);
+ }
+ if (!newView.isCurrent()) {
+ logger.error("handleNewView: newView must be current");
+ throw new IllegalArgumentException("newView must be current");
+ }
+
+ if (!isChanging) {
+ // verify if there is actually a change between previousView and
newView
+ // if there isn't, then there is not much point in sending a
CHANGING/CHANGED tuple
+ // at all
+ if (previousView!=null && previousView.equals(newView)) {
+ // then nothing to send - the view has not changed, and we
haven't
+ // sent the CHANGING event - so we should not do anything here
+ logger.debug("handleNewView: we were not in changing state and
new view matches old, so - ignoring");
+ return;
+ }
+ logger.debug("handleNewView: simulating a handleChanging as we
were not in changing state");
+ handleChanging();
+ logger.debug("handleNewView: simulation of a handleChanging done");
+ }
+
+ // whether activated or not: set isChanging to false, first thing
+ isChanging = false;
+
+ if (!activated) {
+ // then all we can do is to pass this on to previoueView
+ previousView = newView;
+ // other than that, we can't currently send any event, before
activate
+ logger.debug("handleNewView: not yet activated - ignoring");
+ return;
+ }
+
+ if (previousView==null) {
+ // this is the first time handleNewTopologyView is called
+
+ if (eventListeners.size()>0) {
+ logger.info("handleNewTopologyView: no previous view available
even though listeners already got CHANGED event");
+ }
+
+ // otherwise this is the normal case where there are uninitialized
event listeners waiting below
+
+ } else {
+ logger.debug("handleNewView: sending TOPOLOGY_CHANGED to
initialized listeners");
+ previousView.setNotCurrent();
+ sendEvent(eventListeners, newChangedEvent(previousView, newView));
+ }
+
+ if (unInitializedEventListeners.size()>0) {
+ // then there were bindTopologyEventListener calls coming in while
+ // we were in CHANGING state - so we must send those the INIT they
were
+ // waiting for oh so long
+ if (logger.isDebugEnabled()) {
+ logger.debug("handleNewView: sending TOPOLOGY_INIT to
uninitialized listeners ({})",
+ unInitializedEventListeners.size());
+ }
+ sendEvent(unInitializedEventListeners, newInitEvent(newView));
+ eventListeners.addAll(unInitializedEventListeners);
+ unInitializedEventListeners.clear();
+ }
+
+ previousView = newView;
+ logger.debug("handleNewView: end");
+ }
+
+}
Added:
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java?rev=1677574&view=auto
==============================================================================
---
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
(added)
+++
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
Mon May 4 10:11:10 2015
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides commons utility for the Discovery API.
+ *
+ * @version 1.0.0
+ */
+@Version("1.0.0")
+package org.apache.sling.discovery.commons.providers;
+
+import aQute.bnd.annotation.Version;
+
Propchange:
sling/trunk/bundles/extensions/discovery/commons/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
sling/trunk/bundles/extensions/discovery/commons/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java?rev=1677574&view=auto
==============================================================================
---
sling/trunk/bundles/extensions/discovery/commons/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
(added)
+++
sling/trunk/bundles/extensions/discovery/commons/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
Mon May 4 10:11:10 2015
@@ -0,0 +1,569 @@
+/*
+ * 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.providers;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.ViewStateManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestViewStateManager {
+
+ private class Listener implements TopologyEventListener {
+
+ private List<TopologyEvent> events = new LinkedList<TopologyEvent>();
+ private TopologyEvent lastEvent;
+
+ public synchronized void handleTopologyEvent(TopologyEvent event) {
+ events.add(event);
+ lastEvent = event;
+ }
+
+ public synchronized List<TopologyEvent> getEvents() {
+ return Collections.unmodifiableList(events);
+ }
+
+ public synchronized int countEvents() {
+ return events.size();
+ }
+
+ public synchronized TopologyEvent getLastEvent() {
+ return lastEvent;
+ }
+
+ public synchronized void clearEvents() {
+ events.clear();
+ }
+
+ public BaseTopologyView getLastView() {
+ if (lastEvent==null) {
+ return null;
+ } else {
+ switch(lastEvent.getType()) {
+ case TOPOLOGY_INIT:
+ case PROPERTIES_CHANGED:
+ case TOPOLOGY_CHANGED: {
+ return (BaseTopologyView) lastEvent.getNewView();
+ }
+ case TOPOLOGY_CHANGING:{
+ return (BaseTopologyView) lastEvent.getOldView();
+ }
+ default: {
+ fail("no other types supported yet");
+ }
+ }
+ }
+ return null;
+ }
+
+ }
+
+ private class View extends BaseTopologyView {
+
+ private final BaseTopologyView clonedView;
+
+ public View() {
+ clonedView = null;
+ }
+
+ public View(BaseTopologyView clonedView) {
+ this.clonedView = clonedView;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof View)) {
+ return false;
+ }
+ final View other = (View) obj;
+ if (clonedView!=null) {
+ if (obj==clonedView) {
+ return true;
+ }
+ } else if (other.clonedView==this) {
+ return true;
+ }
+ return super.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ if (clonedView!=null) {
+ return clonedView.hashCode();
+ }
+ return super.hashCode();
+ }
+
+ public View addInstance() {
+ return this;
+ }
+
+ public InstanceDescription getLocalInstance() {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ public Set<InstanceDescription> getInstances() {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ public Set<InstanceDescription> findInstances(InstanceFilter filter) {
+ throw new IllegalStateException("not yet implemented");
+ }
+
+ public Set<ClusterView> getClusterViews() {
+ throw new IllegalStateException("not yet implemented");
+ }
+ }
+
+ private ViewStateManager mgr;
+
+ private Random defaultRandom;
+
+ @Before
+ public void setup() throws Exception {
+ mgr = new ViewStateManager();
+ defaultRandom = new Random(1234123412); // I want randomness yes, but
deterministic, for some methods at least
+ }
+
+ @After
+ public void teardown() throws Exception {
+ mgr = null;
+ defaultRandom= null;
+ }
+
+ private void assertNoEvents(Listener listener) {
+ assertEquals(0, listener.countEvents());
+ }
+
+ private void assertEvents(Listener listener, TopologyEvent... events) {
+ assertEquals(events.length, listener.countEvents());
+ for (int i = 0; i < events.length; i++) {
+ TopologyEvent e = events[i];
+ assertEquals(e.getType(), listener.getEvents().get(i).getType());
+ switch(e.getType()) {
+ case TOPOLOGY_INIT: {
+ assertNull(listener.getEvents().get(i).getOldView());
+ assertEquals(e.getNewView(),
listener.getEvents().get(i).getNewView());
+ break;
+ }
+ case TOPOLOGY_CHANGING: {
+ assertEquals(e.getOldView(),
listener.getEvents().get(i).getOldView());
+ assertNull(listener.getEvents().get(i).getNewView());
+ break;
+ }
+ case PROPERTIES_CHANGED:
+ case TOPOLOGY_CHANGED: {
+ assertEquals(e.getOldView(),
listener.getEvents().get(i).getOldView());
+ assertEquals(e.getNewView(),
listener.getEvents().get(i).getNewView());
+ break;
+ }
+ default: {
+ fail("no other type supported yet");
+ }
+ }
+ }
+ listener.clearEvents();
+ }
+
+ /** does couple loops randomly calling handleChanging() (or not) and then
handleNewView().
+ * Note: random is passed to allow customizing and not hardcoding this
method to a particular random **/
+ private void randomEventLoop(final Random random, Listener... listeners) {
+ for(int i=0; i<100; i++) {
+ final boolean shouldCallChanging = random.nextBoolean();
+ if (shouldCallChanging) {
+ // dont always do a changing
+ mgr.handleChanging();
+ for(int j=0; j<listeners.length; j++) {
+ assertEvents(listeners[j],
ViewStateManager.newChangingEvent(listeners[j].getLastView()));
+ }
+ } else {
+ for(int j=0; j<listeners.length; j++) {
+ assertNoEvents(listeners[j]);
+ }
+ }
+ final BaseTopologyView view = new View().addInstance();
+ BaseTopologyView[] lastViews = new
BaseTopologyView[listeners.length];
+ for(int j=0; j<listeners.length; j++) {
+ lastViews[j] = listeners[j].getLastView();
+ }
+ mgr.handleNewView(view);
+ if (!shouldCallChanging) {
+ // in that case I should still get a CHANGING - by contract
+ for(int j=0; j<listeners.length; j++) {
+ assertEvents(listeners[j],
ViewStateManager.newChangingEvent(lastViews[j]),
ViewStateManager.newChangedEvent(lastViews[j], view));
+ }
+ } else {
+ for(int j=0; j<listeners.length; j++) {
+ assertEvents(listeners[j],
ViewStateManager.newChangedEvent(lastViews[j], view));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testDuplicateListeners() throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ mgr.bind(listener); // we should be generous and allow duplicate
registration
+ assertTrue(mgr.unbind(listener));
+ assertFalse(mgr.unbind(listener));
+
+ mgr.handleActivated();
+ assertFalse(mgr.unbind(listener));
+ mgr.bind(listener);
+ mgr.bind(listener); // we should be generous and allow duplicate
registration
+ assertTrue(mgr.unbind(listener));
+ assertFalse(mgr.unbind(listener));
+ }
+
+ @Test
+ public void testBindActivateChangingChanged() throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testBindChangingActivateChanged() throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testBindChangingChangedActivate() throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testBindChangingChangedChangingActivate() throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view2 = new View().addInstance();
+ mgr.handleNewView(view2);
+ assertEvents(listener, ViewStateManager.newInitEvent(view2));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testBindChangedChangingActivate() throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view2 = new View().addInstance();
+ mgr.handleNewView(view2);
+ assertEvents(listener, ViewStateManager.newInitEvent(view2));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testActivateBindChangingChanged() throws Exception {
+ final Listener listener = new Listener();
+ // first activate
+ mgr.handleActivated();
+ assertNoEvents(listener); // paranoia
+ // then bind
+ mgr.bind(listener);
+ assertNoEvents(listener); // there was no changing or changed yet
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testActivateChangingBindChanged() throws Exception {
+ final Listener listener = new Listener();
+ // first activate
+ mgr.handleActivated();
+ assertNoEvents(listener); // paranoia
+ mgr.handleChanging();
+ assertNoEvents(listener); // no listener yet
+ // then bind
+ mgr.bind(listener);
+ assertNoEvents(listener); // no changed event yet
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testActivateChangingChangedBind() throws Exception {
+ final Listener listener = new Listener();
+ // first activate
+ mgr.handleActivated();
+ assertNoEvents(listener); // paranoia
+ mgr.handleChanging();
+ assertNoEvents(listener); // no listener yet
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertNoEvents(listener); // no listener yet
+ // then bind
+ mgr.bind(listener);
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testBindActivateBindChangingChanged() throws Exception {
+ final Listener listener1 = new Listener();
+ final Listener listener2 = new Listener();
+
+ mgr.bind(listener1);
+ assertNoEvents(listener1);
+ mgr.handleActivated();
+ assertNoEvents(listener1);
+ mgr.bind(listener2);
+ assertNoEvents(listener1);
+ assertNoEvents(listener2);
+ mgr.handleChanging();
+ assertNoEvents(listener1);
+ assertNoEvents(listener2);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener1, ViewStateManager.newInitEvent(view));
+ assertEvents(listener2, ViewStateManager.newInitEvent(view));
+
+ randomEventLoop(defaultRandom, listener1, listener2);
+ }
+
+ @Test
+ public void testBindActivateChangingBindChanged() throws Exception {
+ final Listener listener1 = new Listener();
+ final Listener listener2 = new Listener();
+
+ mgr.bind(listener1);
+ assertNoEvents(listener1);
+ mgr.handleActivated();
+ assertNoEvents(listener1);
+ mgr.handleChanging();
+ assertNoEvents(listener1);
+ mgr.bind(listener2);
+ assertNoEvents(listener1);
+ assertNoEvents(listener2);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener1, ViewStateManager.newInitEvent(view));
+ assertEvents(listener2, ViewStateManager.newInitEvent(view));
+
+ randomEventLoop(defaultRandom, listener1, listener2);
+ }
+
+ @Test
+ public void testActivateBindChangingDuplicateHandleNewView() throws
Exception {
+ final Listener listener = new Listener();
+ mgr.handleActivated();
+ mgr.bind(listener);
+ mgr.handleChanging();
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ mgr.handleNewView(clone(view));
+ assertNoEvents(listener);
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testActivateBindChangingChangedBindDuplicateHandleNewView()
throws Exception {
+ final Listener listener1 = new Listener();
+ mgr.handleActivated();
+ mgr.bind(listener1);
+ mgr.handleChanging();
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertEvents(listener1, ViewStateManager.newInitEvent(view));
+
+ final Listener listener2 = new Listener();
+ mgr.bind(listener2);
+ mgr.handleNewView(clone(view));
+ assertNoEvents(listener1);
+ assertEvents(listener2, ViewStateManager.newInitEvent(view));
+ randomEventLoop(defaultRandom, listener1, listener2);
+ }
+
+ @Test
+ public void testActivateChangedBindDuplicateHandleNewView() throws
Exception {
+ final Listener listener = new Listener();
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view = new View().addInstance();
+ mgr.handleNewView(view);
+ assertNoEvents(listener);
+ mgr.bind(listener);
+ assertEvents(listener, ViewStateManager.newInitEvent(view));
+ mgr.handleNewView(clone(view));
+ assertNoEvents(listener);
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testBindActivateChangedChanged() throws Exception {
+ final Listener listener = new Listener();
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ final BaseTopologyView view1 = new View().addInstance();
+ mgr.handleNewView(view1);
+ assertEvents(listener, ViewStateManager.newInitEvent(view1));
+ final BaseTopologyView view2 = new View().addInstance();
+ mgr.handleNewView(view2);
+ assertEvents(listener, ViewStateManager.newChangingEvent(view1),
ViewStateManager.newChangedEvent(view1, view2));
+ randomEventLoop(defaultRandom, listener);
+ }
+
+ @Test
+ public void testBindActivateChangedDeactivateChangingActivateChanged()
throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view1 = new View().addInstance();
+ mgr.handleNewView(view1);
+ assertEvents(listener, ViewStateManager.newInitEvent(view1));
+ mgr.handleDeactivated();
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ mgr.bind(listener); // need to bind again after deactivate
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view2 = new View().addInstance();
+ mgr.handleNewView(view2);
+ assertEvents(listener, ViewStateManager.newInitEvent(view2));
+ }
+
+ @Test
+ public void testBindActivateChangedDeactivateChangedActivateChanged()
throws Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view1 = new View().addInstance();
+ mgr.handleNewView(view1);
+ assertEvents(listener, ViewStateManager.newInitEvent(view1));
+ mgr.handleDeactivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view2 = new View().addInstance();
+ mgr.handleNewView(view2);
+ assertNoEvents(listener);
+ mgr.bind(listener); // need to bind again after deactivate
+ mgr.handleActivated();
+ assertEvents(listener, ViewStateManager.newInitEvent(view2));
+ final BaseTopologyView view3 = new View().addInstance();
+ mgr.handleNewView(view3);
+ assertEvents(listener, ViewStateManager.newChangingEvent(view2),
ViewStateManager.newChangedEvent(view2, view3));
+ }
+
+ @Test
+ public void
testBindActivateChangedChangingDeactivateActivateChangingChanged() throws
Exception {
+ final Listener listener = new Listener();
+ mgr.bind(listener);
+ assertNoEvents(listener);
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ final BaseTopologyView view1 = new View().addInstance();
+ mgr.handleNewView(view1);
+ assertEvents(listener, ViewStateManager.newInitEvent(view1));
+ mgr.handleChanging();
+ assertEvents(listener, ViewStateManager.newChangingEvent(view1));
+ mgr.handleDeactivated();
+ assertNoEvents(listener);
+ mgr.bind(listener); // need to bind again after deactivate
+ mgr.handleActivated();
+ assertNoEvents(listener);
+ mgr.handleChanging();
+ assertNoEvents(listener);
+ final BaseTopologyView view2 = new View().addInstance();
+ mgr.handleNewView(view2);
+ assertEvents(listener, ViewStateManager.newInitEvent(view2));
+ }
+
+ private BaseTopologyView clone(final BaseTopologyView view) {
+ return new View(view);
+ }
+}