This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.discovery.standalone-1.0.2 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-standalone.git
commit 13ef8b2c39b28eade983c065eeacbab536b20a6d Author: Carsten Ziegeler <[email protected]> AuthorDate: Tue Dec 29 13:35:14 2015 +0000 SLING-5405 : TopologyView contract is not correctly followed on property changes git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/standalone@1722125 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 10 + .../discovery/impl/standalone/ClusterViewImpl.java | 49 ++++ .../impl/standalone/InstanceDescriptionImpl.java | 67 +++++ .../impl/standalone/NoClusterDiscoveryService.java | 276 +++++---------------- .../discovery/impl/standalone/ProviderInfo.java | 89 +++++++ .../impl/standalone/TopologyViewImpl.java | 71 ++++++ .../standalone/NoClusterDiscoveryServiceTest.java | 234 +++++++++++++++++ 7 files changed, 584 insertions(+), 212 deletions(-) diff --git a/pom.xml b/pom.xml index c2b7c08..7f8f02b 100644 --- a/pom.xml +++ b/pom.xml @@ -80,5 +80,15 @@ <version>1.1.0</version> <scope>provided</scope> </dependency> + + <!-- Testing --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> </dependencies> </project> diff --git a/src/main/java/org/apache/sling/discovery/impl/standalone/ClusterViewImpl.java b/src/main/java/org/apache/sling/discovery/impl/standalone/ClusterViewImpl.java new file mode 100644 index 0000000..4d20e25 --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/impl/standalone/ClusterViewImpl.java @@ -0,0 +1,49 @@ +/* + * 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.impl.standalone; + +import java.util.Collections; +import java.util.List; + +import org.apache.sling.discovery.ClusterView; +import org.apache.sling.discovery.InstanceDescription; + +public class ClusterViewImpl implements ClusterView { + + private final InstanceDescription myInstance; + + public ClusterViewImpl(final InstanceDescription myInstance) { + this.myInstance = myInstance; + } + + @Override + public InstanceDescription getLeader() { + return myInstance; + } + + @Override + public List<InstanceDescription> getInstances() { + return Collections.singletonList(myInstance); + } + + @Override + public String getId() { + return "0"; + } +} diff --git a/src/main/java/org/apache/sling/discovery/impl/standalone/InstanceDescriptionImpl.java b/src/main/java/org/apache/sling/discovery/impl/standalone/InstanceDescriptionImpl.java new file mode 100644 index 0000000..96d9b9e --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/impl/standalone/InstanceDescriptionImpl.java @@ -0,0 +1,67 @@ +/* + * 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.impl.standalone; + +import java.util.Collections; +import java.util.Map; + +import org.apache.sling.discovery.ClusterView; +import org.apache.sling.discovery.InstanceDescription; + +public class InstanceDescriptionImpl implements InstanceDescription { + + private final String id; + + private final Map<String, String> properties; + + public InstanceDescriptionImpl(final String id, final Map<String, String> properties) { + this.id = id; + this.properties = Collections.unmodifiableMap(properties); + } + + @Override + public boolean isLocal() { + return true; + } + + @Override + public boolean isLeader() { + return true; + } + + @Override + public String getSlingId() { + return id; + } + + @Override + public String getProperty(final String name) { + return properties.get(name); + } + + @Override + public Map<String, String> getProperties() { + return properties; + } + + @Override + public ClusterView getClusterView() { + return new ClusterViewImpl(this); + } +} diff --git a/src/main/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryService.java b/src/main/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryService.java index e495e99..209a832 100644 --- a/src/main/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryService.java +++ b/src/main/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryService.java @@ -20,15 +20,10 @@ package org.apache.sling.discovery.impl.standalone; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; @@ -37,26 +32,22 @@ import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.felix.scr.annotations.Service; -import org.apache.sling.discovery.ClusterView; import org.apache.sling.discovery.DiscoveryService; import org.apache.sling.discovery.InstanceDescription; -import org.apache.sling.discovery.InstanceFilter; import org.apache.sling.discovery.PropertyProvider; 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.apache.sling.settings.SlingSettingsService; -import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is a simple implementation of the discovery service * which can be used for a cluster less installation (= single instance). - * It is disabled by default and can be enabled through a OSGi configuration. */ -@Component(immediate=true) +@Component(immediate=true) // immediate as this is component is also handling the listeners @Service(value = {DiscoveryService.class}) public class NoClusterDiscoveryService implements DiscoveryService { @@ -90,9 +81,9 @@ public class NoClusterDiscoveryService implements DiscoveryService { /** * The current topology view. */ - private TopologyView topologyView; + private volatile TopologyViewImpl currentTopologyView; - private Map<String, String> cachedProperties = new HashMap<String, String>(); + private volatile Map<String, String> cachedProperties = Collections.emptyMap(); /** * Activate this service @@ -101,96 +92,7 @@ public class NoClusterDiscoveryService implements DiscoveryService { @Activate protected void activate() { logger.debug("NoClusterDiscoveryService started."); - final InstanceDescription myDescription = new InstanceDescription() { - - public boolean isLocal() { - return true; - } - - public boolean isLeader() { - return true; - } - - public String getSlingId() { - return settingsService.getSlingId(); - } - - public String getProperty(final String name) { - synchronized(lock) { - return cachedProperties.get(name); - } - } - - public Map<String, String> getProperties() { - synchronized(lock) { - return Collections.unmodifiableMap(cachedProperties); - } - } - - public ClusterView getClusterView() { - final Collection<ClusterView> clusters = topologyView.getClusterViews(); - if (clusters==null || clusters.size()==0) { - return null; - } - return clusters.iterator().next(); - } - }; - final Set<InstanceDescription> instances = new HashSet<InstanceDescription>(); - instances.add(myDescription); - - final TopologyEventListener[] registeredServices; - synchronized ( lock ) { - registeredServices = this.listeners; - final ClusterView clusterView = new ClusterView() { - - public InstanceDescription getLeader() { - return myDescription; - } - - public List<InstanceDescription> getInstances() { - return new LinkedList<InstanceDescription>(instances); - } - - public String getId() { - return "0"; - } - }; - this.topologyView = new TopologyView() { - - public InstanceDescription getLocalInstance() { - return myDescription; - } - - public boolean isCurrent() { - return true; - } - - public Set<InstanceDescription> getInstances() { - return instances; - } - - public Set<InstanceDescription> findInstances(InstanceFilter picker) { - Set<InstanceDescription> result = new HashSet<InstanceDescription>(); - for (Iterator<InstanceDescription> it = getTopology().getInstances().iterator(); it.hasNext();) { - InstanceDescription instance = it.next(); - if (picker.accept(instance)) { - result.add(instance); - } - } - return result; - } - - public Set<ClusterView> getClusterViews() { - Set<ClusterView> clusters = new HashSet<ClusterView>(); - clusters.add(clusterView); - return clusters; - } - - }; - } - for(final TopologyEventListener da: registeredServices) { - da.handleTopologyEvent(new TopologyEvent(Type.TOPOLOGY_INIT, null, topologyView)); - } + createNewView(Type.TOPOLOGY_INIT, true); } /** @@ -198,32 +100,55 @@ public class NoClusterDiscoveryService implements DiscoveryService { */ @Deactivate protected void deactivate() { + synchronized ( lock ) { + if ( this.currentTopologyView != null ) { + this.currentTopologyView.invalidate(); + this.currentTopologyView = null; + } + this.cachedProperties = null; + } logger.debug("NoClusterDiscoveryService stopped."); - this.topologyView = null; + } + + private void createNewView(final Type eventType, boolean inform) { + final TopologyEventListener[] registeredServices; + final TopologyView newView; + final TopologyView oldView; + synchronized ( lock ) { + // invalidate old view + if ( this.currentTopologyView != null ) { + this.currentTopologyView.invalidate(); + oldView = currentTopologyView; + } else { + oldView = null; + } + final InstanceDescription myInstanceDescription = new InstanceDescriptionImpl(this.settingsService.getSlingId(), + this.cachedProperties); + this.currentTopologyView = new TopologyViewImpl(myInstanceDescription); + registeredServices = this.listeners; + newView = this.currentTopologyView; + + if ( inform ) { + for(final TopologyEventListener da: registeredServices) { + da.handleTopologyEvent(new TopologyEvent(eventType, oldView, newView)); + } + } + } } /** * Bind a new property provider. */ - @SuppressWarnings("unused") - private void bindPropertyProvider(final PropertyProvider propertyProvider, final Map<String, Object> props) { - logger.debug("bindPropertyProvider: Binding PropertyProvider {}", propertyProvider); + private void bindPropertyProvider(final PropertyProvider propertyProvider, final Map<String, Object> props) { + logger.debug("Binding PropertyProvider {}", propertyProvider); - final TopologyEventListener[] awares; synchronized (lock) { final ProviderInfo info = new ProviderInfo(propertyProvider, props); this.providerInfos.add(info); Collections.sort(this.providerInfos); this.updatePropertiesCache(); - if ( this.topologyView == null ) { - awares = null; - } else { - awares = this.listeners; - } - } - if ( awares != null ) { - for(final TopologyEventListener da : awares) { - da.handleTopologyEvent(new TopologyEvent(Type.PROPERTIES_CHANGED, this.topologyView, this.topologyView)); + if ( this.currentTopologyView != null ) { + this.createNewView(Type.PROPERTIES_CHANGED, true); } } } @@ -233,10 +158,12 @@ public class NoClusterDiscoveryService implements DiscoveryService { */ @SuppressWarnings("unused") private void updatedPropertyProvider(final PropertyProvider propertyProvider, final Map<String, Object> props) { - logger.debug("bindPropertyProvider: Updating PropertyProvider {}", propertyProvider); + logger.debug("Updating PropertyProvider {}", propertyProvider); - this.unbindPropertyProvider(propertyProvider, props, false); - this.bindPropertyProvider(propertyProvider, props); + synchronized (lock) { + this.unbindPropertyProvider(propertyProvider, props, false); + this.bindPropertyProvider(propertyProvider, props); + } } /** @@ -250,30 +177,24 @@ public class NoClusterDiscoveryService implements DiscoveryService { /** * Unbind a property provider */ - @SuppressWarnings("unused") private void unbindPropertyProvider(final PropertyProvider propertyProvider, final Map<String, Object> props, final boolean inform) { - logger.debug("unbindPropertyProvider: Releasing PropertyProvider {}", propertyProvider); + logger.debug("Releasing PropertyProvider {}", propertyProvider); - final TopologyEventListener[] awares; synchronized (lock) { final ProviderInfo info = new ProviderInfo(propertyProvider, props); this.providerInfos.remove(info); this.updatePropertiesCache(); - if ( this.topologyView == null ) { - awares = null; - } else { - awares = this.listeners; - } - } - if ( inform && awares != null ) { - for(final TopologyEventListener da : awares) { - da.handleTopologyEvent(new TopologyEvent(Type.PROPERTIES_CHANGED, this.topologyView, this.topologyView)); + if ( this.currentTopologyView != null ) { + this.createNewView(Type.PROPERTIES_CHANGED, inform); } } } + /** + * Update the properties cache. + */ private void updatePropertiesCache() { final Map<String, String> newProps = new HashMap<String, String>(); for(final ProviderInfo info : this.providerInfos) { @@ -286,35 +207,28 @@ public class NoClusterDiscoveryService implements DiscoveryService { } @SuppressWarnings("unused") - private void bindTopologyEventListener(final TopologyEventListener clusterAware) { - - logger.debug("bindTopologyEventListener: Binding TopologyEventListener {}", clusterAware); + private void bindTopologyEventListener(final TopologyEventListener listener) { + logger.debug("Binding TopologyEventListener {}", listener); boolean inform = true; synchronized (lock) { - List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>( + final List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>( Arrays.asList(listeners)); - currentList.add(clusterAware); + currentList.add(listener); this.listeners = currentList.toArray(new TopologyEventListener[currentList.size()]); - if ( this.topologyView == null ) { - inform = false; + if ( this.currentTopologyView != null ) { + listener.handleTopologyEvent(new TopologyEvent(Type.TOPOLOGY_INIT, null, this.currentTopologyView)); } } - - if ( inform ) { - clusterAware.handleTopologyEvent(new TopologyEvent(Type.TOPOLOGY_INIT, null, topologyView)); - } } @SuppressWarnings("unused") - private void unbindTopologyEventListener(final TopologyEventListener clusterAware) { - - logger.debug("unbindTopologyEventListener: Releasing TopologyEventListener {}", clusterAware); + private void unbindTopologyEventListener(final TopologyEventListener listener) { + logger.debug("Releasing TopologyEventListener {}", listener); synchronized (lock) { - List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>( - Arrays.asList(listeners)); - currentList.remove(clusterAware); + final List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>(Arrays.asList(listeners)); + currentList.remove(listener); this.listeners = currentList.toArray(new TopologyEventListener[currentList.size()]); } } @@ -322,70 +236,8 @@ public class NoClusterDiscoveryService implements DiscoveryService { /** * @see DiscoveryService#getTopology() */ + @Override public TopologyView getTopology() { - return topologyView; - } - - /** - * Internal class caching some provider infos like service id and ranking. - */ - private final static class ProviderInfo implements Comparable<ProviderInfo> { - - public final PropertyProvider provider; - public final int ranking; - public final long serviceId; - public final Map<String, String> properties = new HashMap<String, String>(); - - public ProviderInfo(final PropertyProvider provider, final Map<String, Object> serviceProps) { - this.provider = provider; - final Object sr = serviceProps.get(Constants.SERVICE_RANKING); - if ( sr == null || !(sr instanceof Integer)) { - this.ranking = 0; - } else { - this.ranking = (Integer)sr; - } - this.serviceId = (Long)serviceProps.get(Constants.SERVICE_ID); - final Object namesObj = serviceProps.get(PropertyProvider.PROPERTY_PROPERTIES); - if ( namesObj instanceof String ) { - final String val = provider.getProperty((String)namesObj); - if ( val != null ) { - this.properties.put((String)namesObj, val); - } - } else if ( namesObj instanceof String[] ) { - for(final String name : (String[])namesObj ) { - final String val = provider.getProperty(name); - if ( val != null ) { - this.properties.put(name, val); - } - } - } - } - - /** - * @see java.lang.Comparable#compareTo(java.lang.Object) - */ - public int compareTo(final ProviderInfo o) { - // Sort by rank in ascending order. - if ( this.ranking < o.ranking ) { - return -1; // lower rank - } else if (this.ranking > o.ranking ) { - return 1; // higher rank - } - // If ranks are equal, then sort by service id in descending order. - return (this.serviceId < o.serviceId) ? 1 : -1; - } - - @Override - public boolean equals(final Object obj) { - if ( obj instanceof ProviderInfo ) { - return ((ProviderInfo)obj).serviceId == this.serviceId; - } - return false; - } - - @Override - public int hashCode() { - return provider.hashCode(); - } + return this.currentTopologyView; } } diff --git a/src/main/java/org/apache/sling/discovery/impl/standalone/ProviderInfo.java b/src/main/java/org/apache/sling/discovery/impl/standalone/ProviderInfo.java new file mode 100644 index 0000000..ed10061 --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/impl/standalone/ProviderInfo.java @@ -0,0 +1,89 @@ +/* + * 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.impl.standalone; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.discovery.PropertyProvider; +import org.osgi.framework.Constants; + +/** + * Internal class caching some provider infos like service id and ranking. + */ +public class ProviderInfo implements Comparable<ProviderInfo> { + + public final PropertyProvider provider; + public final int ranking; + public final long serviceId; + public final Map<String, String> properties = new HashMap<String, String>(); + + public ProviderInfo(final PropertyProvider provider, final Map<String, Object> serviceProps) { + this.provider = provider; + final Object sr = serviceProps.get(Constants.SERVICE_RANKING); + if ( sr == null || !(sr instanceof Integer)) { + this.ranking = 0; + } else { + this.ranking = (Integer)sr; + } + this.serviceId = (Long)serviceProps.get(Constants.SERVICE_ID); + final Object namesObj = serviceProps.get(PropertyProvider.PROPERTY_PROPERTIES); + if ( namesObj instanceof String ) { + final String val = provider.getProperty((String)namesObj); + if ( val != null ) { + this.properties.put((String)namesObj, val); + } + } else if ( namesObj instanceof String[] ) { + for(final String name : (String[])namesObj ) { + final String val = provider.getProperty(name); + if ( val != null ) { + this.properties.put(name, val); + } + } + } + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(final ProviderInfo o) { + // Sort by rank in ascending order. + if ( this.ranking < o.ranking ) { + return -1; // lower rank + } else if (this.ranking > o.ranking ) { + return 1; // higher rank + } + // If ranks are equal, then sort by service id in descending order. + return (this.serviceId < o.serviceId) ? 1 : -1; + } + + @Override + public boolean equals(final Object obj) { + if ( obj instanceof ProviderInfo ) { + return ((ProviderInfo)obj).serviceId == this.serviceId; + } + return false; + } + + @Override + public int hashCode() { + return provider.hashCode(); + } +} diff --git a/src/main/java/org/apache/sling/discovery/impl/standalone/TopologyViewImpl.java b/src/main/java/org/apache/sling/discovery/impl/standalone/TopologyViewImpl.java new file mode 100644 index 0000000..e76ecf7 --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/impl/standalone/TopologyViewImpl.java @@ -0,0 +1,71 @@ +/* + * 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.impl.standalone; + +import java.util.Collections; +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.TopologyView; + +public class TopologyViewImpl implements TopologyView { + + private volatile boolean current = true; + + private final InstanceDescription myInstance; + + public TopologyViewImpl(final InstanceDescription myInstance) { + this.myInstance = myInstance; + } + + @Override + public InstanceDescription getLocalInstance() { + return myInstance; + } + + @Override + public boolean isCurrent() { + return current; + } + + public void invalidate() { + this.current = false; + } + + @Override + public Set<InstanceDescription> getInstances() { + return Collections.singleton(this.myInstance); + } + + @Override + public Set<InstanceDescription> findInstances(final InstanceFilter picker) { + if ( picker.accept(this.myInstance) ) { + return getInstances(); + } + return Collections.emptySet(); + } + + @Override + public Set<ClusterView> getClusterViews() { + final ClusterView clusterView = new ClusterViewImpl(myInstance); + return Collections.singleton(clusterView); + } +} diff --git a/src/test/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryServiceTest.java b/src/test/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryServiceTest.java new file mode 100644 index 0000000..7247d60 --- /dev/null +++ b/src/test/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryServiceTest.java @@ -0,0 +1,234 @@ +/* + * 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.impl.standalone; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.sling.discovery.DiscoveryService; +import org.apache.sling.discovery.PropertyProvider; +import org.apache.sling.discovery.TopologyEvent; +import org.apache.sling.discovery.TopologyEventListener; +import org.apache.sling.settings.SlingSettingsService; +import org.junit.Test; +import org.osgi.framework.Constants; + +public class NoClusterDiscoveryServiceTest { + + private void invoke(final Object obj, final String methodName) { + invoke(obj, methodName, null, null); + } + + private void invoke(final Object obj, final String methodName, final Class[] params, final Object[] args) { + try { + final Method activate = obj.getClass().getDeclaredMethod(methodName, params); + activate.setAccessible(true); + activate.invoke(obj, args); + } catch (final Exception e) { + throw new RuntimeException("Unable to invoke method " + methodName + " on " + obj, e); + } + } + + private Object setField(final Object obj, final String fieldName, final Object value) { + Class<?> clazz = obj.getClass(); + while ( clazz != null ) { + try { + final Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + + field.set(obj, value); + return null; + } catch ( final Exception ignore ) { + // ignore + } + clazz = clazz.getSuperclass(); + } + throw new RuntimeException("Field " + fieldName + " not found on object " + obj); + } + + private DiscoveryService createService(final boolean activate) { + final DiscoveryService service = new NoClusterDiscoveryService(); + + setField(service, "settingsService", new SlingSettingsService() { + + @Override + public String getSlingId() { + return "my-sling-id"; + } + + @Override + public String getSlingHomePath() { + return null; + } + + @Override + public URL getSlingHome() { + return null; + } + + @Override + public Set<String> getRunModes() { + return null; + } + + @Override + public String getAbsolutePathWithinSlingHome(String relativePath) { + return null; + } + }); + if ( activate ) { + invoke(service, "activate"); + } + + return service; + } + + @Test public void testBasics() throws Exception { + final DiscoveryService service = this.createService(true); + + assertNotNull(service.getTopology()); + assertTrue(service.getTopology().isCurrent()); + + invoke(service, "deactivate"); + + assertNull(service.getTopology()); + } + + @Test public void testListenerAfter() throws Exception { + final DiscoveryService service = this.createService(true); + + final List<TopologyEvent> events = new ArrayList<TopologyEvent>(); + + final TopologyEventListener listener = new TopologyEventListener() { + + @Override + public void handleTopologyEvent(final TopologyEvent event) { + events.add(event); + } + }; + invoke(service, "bindTopologyEventListener", new Class[] {TopologyEventListener.class}, new Object[] {listener}); + assertEquals(1, events.size()); + assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, events.get(0).getType()); + assertNotNull(events.get(0).getNewView()); + assertNull(events.get(0).getOldView()); + } + + @Test public void testListenerBefore() throws Exception { + final DiscoveryService service = this.createService(false); + + final List<TopologyEvent> events = new ArrayList<TopologyEvent>(); + + final TopologyEventListener listener = new TopologyEventListener() { + + @Override + public void handleTopologyEvent(final TopologyEvent event) { + events.add(event); + } + }; + invoke(service, "bindTopologyEventListener", new Class[] {TopologyEventListener.class}, new Object[] {listener}); + assertEquals(0, events.size()); + + invoke(service, "activate"); + assertEquals(1, events.size()); + assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, events.get(0).getType()); + assertNotNull(events.get(0).getNewView()); + assertNull(events.get(0).getOldView()); + } + + @Test public void testPropertyChanges() throws Exception { + final DiscoveryService service = this.createService(true); + + final List<TopologyEvent> events = new ArrayList<TopologyEvent>(); + + final TopologyEventListener listener = new TopologyEventListener() { + + @Override + public void handleTopologyEvent(final TopologyEvent event) { + events.add(event); + } + }; + invoke(service, "bindTopologyEventListener", new Class[] {TopologyEventListener.class}, new Object[] {listener}); + events.clear(); + + final PropertyProvider provider = new PropertyProvider() { + + @Override + public String getProperty(final String name) { + if ( "a".equals(name) ) { + return "1"; + } + if ( "b".equals(name) ) { + return "2"; + } + if ( "c".equals(name) ) { + return "3"; + } + return null; + } + }; + final Map<String, Object> properties = new HashMap<String, Object>(); + properties.put(PropertyProvider.PROPERTY_PROPERTIES, new String[] {"a", "b", "c"}); + properties.put(Constants.SERVICE_ID, 1L); + + invoke(service, "bindPropertyProvider", new Class[] {PropertyProvider.class, Map.class}, new Object[] {provider, properties}); + + assertEquals(1, events.size()); + assertEquals(TopologyEvent.Type.PROPERTIES_CHANGED, events.get(0).getType()); + assertNotNull(events.get(0).getNewView()); + assertTrue(events.get(0).getNewView().isCurrent()); + assertNotNull(events.get(0).getOldView()); + assertFalse(events.get(0).getOldView().isCurrent()); + + // test properties + assertEquals("1", events.get(0).getNewView().getLocalInstance().getProperty("a")); + assertEquals("2", events.get(0).getNewView().getLocalInstance().getProperty("b")); + assertEquals("3", events.get(0).getNewView().getLocalInstance().getProperty("c")); + assertNull(events.get(0).getOldView().getLocalInstance().getProperty("a")); + assertNull(events.get(0).getOldView().getLocalInstance().getProperty("b")); + assertNull(events.get(0).getOldView().getLocalInstance().getProperty("c")); + + events.clear(); + invoke(service, "unbindPropertyProvider", new Class[] {PropertyProvider.class, Map.class}, new Object[] {provider, properties}); + assertEquals(1, events.size()); + assertEquals(TopologyEvent.Type.PROPERTIES_CHANGED, events.get(0).getType()); + assertNotNull(events.get(0).getNewView()); + assertTrue(events.get(0).getNewView().isCurrent()); + assertNotNull(events.get(0).getOldView()); + assertFalse(events.get(0).getOldView().isCurrent()); + + assertEquals("1", events.get(0).getOldView().getLocalInstance().getProperty("a")); + assertEquals("2", events.get(0).getOldView().getLocalInstance().getProperty("b")); + assertEquals("3", events.get(0).getOldView().getLocalInstance().getProperty("c")); + assertNull(events.get(0).getNewView().getLocalInstance().getProperty("a")); + assertNull(events.get(0).getNewView().getLocalInstance().getProperty("b")); + assertNull(events.get(0).getNewView().getLocalInstance().getProperty("c")); + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
