blob: 978ebaf36eaddc114370cb822e806c3807255b2e [file] [log] [blame]
/*
* 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;
}
}
}