/* * 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.base.commons; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; 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.Type; import org.apache.sling.discovery.commons.providers.BaseTopologyView; import org.apache.sling.discovery.commons.providers.spi.LocalClusterView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default Implementation of the topology view interface */ public class DefaultTopologyView extends BaseTopologyView { private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** the instances that are part of this topology **/ private final Set<InstanceDescription> instances = new HashSet<InstanceDescription>(); private String localClusterSyncTokenId; /** Create a new empty topology **/ public DefaultTopologyView() { // nothing to be initialized then } /** Create a new topology filled with the given list of instances **/ public DefaultTopologyView(final Collection<InstanceDescription> instances) { if (instances != null) { this.instances.addAll(instances); } } /** * Compare this topology with the given one and determine how they compare * @param other the other topology against which to compare * @return the type describing how these two compare * @see Type */ public Type compareTopology(final DefaultTopologyView other) { if (other == null) { throw new IllegalArgumentException("other must not be null"); } if ((localClusterSyncTokenId == null && other.localClusterSyncTokenId != null) || (other.localClusterSyncTokenId == null && localClusterSyncTokenId != null) || (localClusterSyncTokenId != null && !localClusterSyncTokenId.equals(other.localClusterSyncTokenId))) { logger.debug("compareTopology: different localClusterSyncTokenId"); return Type.TOPOLOGY_CHANGED; } if (this.instances.size() != other.instances.size()) { logger.debug("compareTopology: different number of instances"); return Type.TOPOLOGY_CHANGED; } boolean propertiesChanged = false; for(final InstanceDescription instance : this.instances) { final Iterator<InstanceDescription> it2 = other.instances.iterator(); InstanceDescription matchingInstance = null; while (it2.hasNext()) { final InstanceDescription otherInstance = it2.next(); if (instance.getSlingId().equals(otherInstance.getSlingId())) { matchingInstance = otherInstance; break; } } if (matchingInstance == null) { if (logger.isDebugEnabled()) { logger.debug("compareTopology: no matching instance found for {}", instance); } return Type.TOPOLOGY_CHANGED; } if (!instance.getClusterView().getId() .equals(matchingInstance.getClusterView().getId())) { logger.debug("compareTopology: cluster view id does not match"); return Type.TOPOLOGY_CHANGED; } if (!instance.isLeader()==matchingInstance.isLeader()) { logger.debug("compareTopology: leaders differ"); return Type.TOPOLOGY_CHANGED; } if (!instance.getProperties().equals( matchingInstance.getProperties())) { propertiesChanged = true; } } if (propertiesChanged) { return Type.PROPERTIES_CHANGED; } else { return null; } } @Override public boolean equals(final Object obj) { if (obj == null || !(obj instanceof DefaultTopologyView)) { return false; } DefaultTopologyView other = (DefaultTopologyView) obj; if (this.isCurrent() != other.isCurrent()) { return false; } Type diff = compareTopology(other); return diff == null; } @Override public int hashCode() { int code = 0; for (Iterator<InstanceDescription> it = instances.iterator(); it .hasNext();) { InstanceDescription instance = it.next(); code += instance.hashCode(); } return code; } /** * @see org.apache.sling.discovery.TopologyView#getLocalInstance() */ public InstanceDescription getLocalInstance() { for (Iterator<InstanceDescription> it = instances.iterator(); it .hasNext();) { InstanceDescription instance = it.next(); if (instance.isLocal()) { return instance; } } return null; } /** * @see org.apache.sling.discovery.TopologyView#getInstances() */ public Set<InstanceDescription> getInstances() { return Collections.unmodifiableSet(instances); } /** * Adds the instances of the given ClusterView to this topology */ public void setLocalClusterView(final LocalClusterView localClusterView) { if (localClusterView == null) { throw new IllegalArgumentException("localClusterView must not be null"); } final List<InstanceDescription> instances = localClusterView.getInstances(); addInstances(instances); this.localClusterSyncTokenId = localClusterView.getLocalClusterSyncTokenId(); } /** * Adds the given instances to this topology */ public void addInstances(final Collection<InstanceDescription> instances) { if (instances == null) { return; } outerLoop: for (Iterator<InstanceDescription> it = instances.iterator(); it .hasNext();) { InstanceDescription instanceDescription = it.next(); for (Iterator<InstanceDescription> it2 = this.instances.iterator(); it2.hasNext();) { InstanceDescription existingInstance = it2.next(); if (existingInstance.getSlingId().equals(instanceDescription.getSlingId())) { // SLING-3726: // while 'normal duplicate instances' are filtered out here correctly, // 'hidden duplicate instances' that are added via this instanceDescription's // cluster, are not caught. // there is, however, no simple fix for this. Since the reason is // inconsistent state information in /var/discovery/impl - either // due to stale-announcements (SLING-4139) - or by some manualy // copying of data from one cluster to the next (which will also // be cleaned up by SLING-4139 though) // so the fix for avoiding duplicate instances is really SLING-4139 logger.info("addInstance: cannot add same instance twice: " + instanceDescription); continue outerLoop; } } this.instances.add(instanceDescription); } } /** * @see org.apache.sling.discovery.TopologyView#findInstances(org.apache.sling.discovery.InstanceFilter) */ public Set<InstanceDescription> findInstances(final InstanceFilter picker) { if (picker == null) { throw new IllegalArgumentException("picker must not be null"); } Set<InstanceDescription> result = new HashSet<InstanceDescription>(); for (Iterator<InstanceDescription> it = instances.iterator(); it .hasNext();) { InstanceDescription instance = it.next(); if (picker.accept(instance)) { result.add(instance); } } return result; } /** * @see org.apache.sling.discovery.TopologyView#getClusterViews() */ public Set<ClusterView> getClusterViews() { Set<ClusterView> result = new HashSet<ClusterView>(); for (Iterator<InstanceDescription> it = instances.iterator(); it .hasNext();) { InstanceDescription instance = it.next(); ClusterView cluster = instance.getClusterView(); if (cluster != null) { result.add(cluster); } } return new HashSet<ClusterView>(result); } @Override public String toString() { StringBuilder sb = new StringBuilder(); try{ boolean firstCluster = true; for (ClusterView clusterView : getClusterViews()) { if (!firstCluster) { sb.append(", "); } firstCluster = false; sb.append("[clusterId=" + clusterView.getId() + ", instances="); boolean firstInstance = true; for (InstanceDescription id : clusterView.getInstances()) { if (!firstInstance) { sb.append(", "); } firstInstance = false; sb.append("[id=" + id.getSlingId() + ", isLeader=" + id.isLeader() + ", isLocal=" + id.isLocal() + "]"); } sb.append("]"); } } catch(Exception e) { // paranoia fallback sb = new StringBuilder(instances.toString()); } return "DefaultTopologyView[current=" + isCurrent() + ", num=" + instances.size() + ", instances=" + sb + "]"; } @Override public String getLocalClusterSyncTokenId() { if (localClusterSyncTokenId==null) { throw new IllegalStateException("no syncToken set"); } else { return localClusterSyncTokenId; } } }