This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git
commit b8fe30ce3973f15a94a2115a3f39721ced7e6d37 Author: Stefan Egli <[email protected]> AuthorDate: Wed Apr 29 08:30:03 2015 +0000 SLING-4665 : adding patch provided by Timothee Maret, many thanks git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1676687 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 82 ++++ .../sling/discovery/commons/InstancesDiff.java | 486 +++++++++++++++++++++ .../sling/discovery/commons/package-info.java | 29 ++ .../sling/discovery/commons/InstancesDiffTest.java | 331 ++++++++++++++ 4 files changed, 928 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7b74420 --- /dev/null +++ b/pom.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>22</version> + <relativePath>../../../../parent/pom.xml</relativePath> + </parent> + + <artifactId>org.apache.sling.discovery.commons</artifactId> + <packaging>bundle</packaging> + <version>1.0.0-SNAPSHOT</version> + + <name>Apache Sling Discovery Commons Bundle</name> + <description> + Commons services related to Sling Discovery. + </description> + + <scm> + <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons</developerConnection> + <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons</url> + </scm> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>biz.aQute</groupId> + <artifactId>bndlib</artifactId> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.discovery.api</artifactId> + <version>1.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>2.0.0</version> + </dependency> + <!-- Testing --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/discovery/commons/InstancesDiff.java b/src/main/java/org/apache/sling/discovery/commons/InstancesDiff.java new file mode 100644 index 0000000..61b2f9c --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/commons/InstancesDiff.java @@ -0,0 +1,486 @@ +/* + * 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; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +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.TopologyView; + +/** + * The {@code InstancesDiff} allows to combine and filter two collections of {@code InstanceDescription} instances, + * an "old" collection and a "new" collection.<p> + * + * The comparison between {@code InstanceDescription} instances is done only on the basis of the Sling identifier. + * Two instances with the same Sling identifier are considered as equal.<p> + * + * <b>Note</b>: Each collection must contain only unique instances (no two instances with the same Sling identifier). + * Using the {@code InstancesDiff} with collections containing duplicated Sling id + * will throw an {@code IllegalArgumentException}.<p> + * + * @since 1.0.0 + */ +public final class InstancesDiff { + + /** + * A filter that keeps local instances (see {@link InstanceDescription#isLocal()}. + */ + private static final InstanceFilter LOCAL_INSTANCE = new LocalInstanceFilter(); + + /** + * A filter that filters out local instances (see {@link InstanceDescription#isLocal()}. + */ + private static final InstanceFilter NOT_LOCAL_INSTANCE = new NotFilter(LOCAL_INSTANCE); + + /** + * A filter that keeps leader instances (see {@link InstanceDescription#isLeader()}. + */ + private static final InstanceFilter LEADER_INSTANCE = new LeaderInstanceFilter(); + + /** + * A filter that filters out leader instances (see {@link InstanceDescription#isLeader()}. + */ + private static final InstanceFilter NOT_LEADER_INSTANCE = new NotFilter(LEADER_INSTANCE); + + /** + * Keeps track of the old {@code InstanceDescription} instances + * + * The map keys are the instance Sling identifiers and values are + * the {@code InstanceDescription} instances descriptions. + */ + private final Map<String, InstanceDescription> oldInstances; + + /** + * Keeps track of the new {@code InstanceDescription} instances + * + * The map keys are the instance Sling identifiers and values are + * the {@code InstanceDescription} instances descriptions. + */ + private final Map<String, InstanceDescription> newInstances; + + /** + * Create a new {@code InstancesDiff} based on the instances from the old and + * new {@code TopologyView} topology views contained in the {@code TopologyEvent} event provided. + * + * @param event the non {@code null} event from which the old and new topology views are used for computing. + * If either of the topology views are {@code null}, then they will be substituted by an + * empty collection of instances. + * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers. + */ + public InstancesDiff(@Nonnull TopologyEvent event) { + this(instancesOrEmpty(event.getOldView()), instancesOrEmpty(event.getNewView())); + } + + /** + * Create a new {@code InstancesDiff} based on the instances from the old and + * new {@code TopologyView} topology views provided. + * + * @param oldView the non {@code null} old topology view from which the old collection is used for computing. + * @param newView the non {@code null} new topology view form which the new collection is used for computing. + * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers. + */ + public InstancesDiff(@Nonnull TopologyView oldView, @Nonnull TopologyView newView) { + this(oldView.getInstances(), newView.getInstances()); + } + + /** + * Create a new {@code InstancesDiff} based on the instances from the old and + * new {@code ClusterView} cluster views provided. + * + * @param oldView the non {@code null} old cluster view used for computing. + * @param newView the non {@code null} new cluster view used for computing. + * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers. + */ + public InstancesDiff(@Nonnull ClusterView oldView, @Nonnull ClusterView newView) { + this(oldView.getInstances(), newView.getInstances()); + } + + /** + * Create a new {@code InstancesDiff} based on the provided old and + * new {@code Collection} collections of instances. + * + * @param oldInstances the non {@code null} old collection of instances used for computing. + * @param newInstances the non {@code null} new collection of instances used for computing. + * @param <T> the type of instance which must extend {@code InstanceDescription}. + * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers. + */ + public <T extends InstanceDescription> InstancesDiff(@Nonnull Collection<T> oldInstances, @Nonnull Collection<T> newInstances) { + this.newInstances = getInstancesMap(newInstances); + this.oldInstances = getInstancesMap(oldInstances); + } + + /** + * Returns the {@code InstanceSet} set containing the {@code InstanceDescription} instances that are + * contained in either the old or the new collection.<p> + * + * For {@code InstanceDescription} instances contained in both the old and + * the new collections, the method will retain those from either of the collections + * depending on the parameter #retainFromNewView.<p> + * + * @param retainFromNewCollection {@code true} in order to retain the instances from the new collection ; + * {@code false} in order to retain the instances from the old collection. + * @return the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances + * from both collections. + */ + @Nonnull + public InstanceCollection all(boolean retainFromNewCollection) { + return new InstanceCollection(partitionAll(retainFromNewCollection)); + } + + /** + * Returns the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances that are + * contained in the new collection but not in the old collection. + * + * @return the {@code InstanceCollection} collection containing the instances in the new + * topology collection but not in the old collection. + */ + @Nonnull + public InstanceCollection added() { + return new InstanceCollection(partitionAdded()); + } + + /** + * Returns the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances that are + * contained in the old collection but not in the new collection. + * + * @return the {@code InstanceSet} set containing the instances in the old collection but not in the new collection. + */ + @Nonnull + public InstanceCollection removed() { + return new InstanceCollection(partitionRemoved()); + } + + /** + * Returns the {@code InstanceSet} collection containing the {@code InstanceDescription} instances that are + * contained in both the old collection and the new collection.<p> + * + * The method will retain the {@code InstanceDescription} instances from either of the collections + * depending on the parameter #retainFromNewView.<p> + * + * @param retainFromNewCollection {@code true} in order to retain the instances from the new collection ; + * {@code false} in order to retain the instances from the old collection. + * @return the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances + * contained in both collections. + */ + @Nonnull + public InstanceCollection retained(boolean retainFromNewCollection) { + return new InstanceCollection(partitionRetained(retainFromNewCollection)); + } + + /** + * Returns the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances that are + * contained in both the old and the new collections.<p> + * + * The method will retain the {@code InstanceDescription} instances from either of the collections + * depending on the parameter #retainFromNewView.<p> + * + * @param retainFromNewCollection {@code true} in order to retain the instances from the new collection ; + * {@code false} in order to retain the instances from the old collection. + * @param propertyChanged {@code true} in order to keep only the instances which + * properties have not changed between the old and new collections ; + * {@code false} in order to keep only the instances which properties have changed. + * @return the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances + * contained in both views. + */ + @Nonnull + public InstanceCollection retained(boolean retainFromNewCollection, boolean propertyChanged) { + return new InstanceCollection(partitionRetained(retainFromNewCollection, propertyChanged)); + } + + // + + @Nonnull + private Map<String, InstanceDescription> partitionAll(boolean retainFromNewCollection) { + Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>(); + if (retainFromNewCollection) { + partition.putAll(oldInstances); + partition.putAll(newInstances); + } else { + partition.putAll(newInstances); + partition.putAll(oldInstances); + } + return partition; + } + + @Nonnull + private Map<String, InstanceDescription> partitionRemoved() { + Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>(oldInstances); + partition.keySet().removeAll(newInstances.keySet()); + return partition; + } + + @Nonnull + private Map<String, InstanceDescription> partitionAdded() { + Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>(newInstances); + partition.keySet().removeAll(oldInstances.keySet()); + return partition; + } + + @Nonnull + private Map<String, InstanceDescription> partitionRetained(boolean retainFromNewCollection, boolean propertyChanged) { + Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>(); + for (Map.Entry<String, InstanceDescription> oldEntry : oldInstances.entrySet()) { + String slingId = oldEntry.getKey(); + InstanceDescription newDescription = newInstances.get(slingId); + if(newDescription != null) { + InstanceDescription oldDescription = oldEntry.getValue(); + boolean propertiesSame = newDescription.getProperties().equals(oldDescription.getProperties()); + if ((propertiesSame && ! propertyChanged) || (! propertiesSame && propertyChanged)) { + partition.put(slingId, retainFromNewCollection ? newDescription : oldDescription); + } + } + } + return partition; + } + + @Nonnull + private Map<String, InstanceDescription> partitionRetained(boolean retainFromNewCollection) { + Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>(); + if (retainFromNewCollection) { + partition.putAll(newInstances); + partition.keySet().retainAll(oldInstances.keySet()); + } else { + partition.putAll(oldInstances); + partition.keySet().retainAll(newInstances.keySet()); + } + return partition; + } + + @Nonnull + private static Set<InstanceDescription> instancesOrEmpty(@Nullable TopologyView topologyView) { + return (topologyView != null) ? topologyView.getInstances() : Collections.<InstanceDescription>emptySet(); + } + + @Nonnull + private static <T extends InstanceDescription> Map<String, InstanceDescription> getInstancesMap(@Nonnull Collection<T> instances) { + Map<String, InstanceDescription> instancesMap = new HashMap<String, InstanceDescription>(); + for (InstanceDescription instance : instances) { + String slingId = instance.getSlingId(); + if (slingId != null) { + if (instancesMap.put(slingId, instance) != null) { + throw new IllegalArgumentException(String.format("Duplicated instance found for slingId: %s", slingId)); + } + } + } + return instancesMap; + } + + private static final class NotFilter implements InstanceFilter { + + final InstanceFilter filter; + + private NotFilter(InstanceFilter filter) { + this.filter = filter; + } + + public boolean accept(InstanceDescription instance) { + return ! filter.accept(instance); + } + } + + private static final class LocalInstanceFilter implements InstanceFilter { + + public boolean accept(InstanceDescription instance) { + return instance.isLocal(); + } + } + + private static final class LeaderInstanceFilter implements InstanceFilter { + + public boolean accept(InstanceDescription instance) { + return instance.isLeader(); + } + } + + private static final class InClusterView implements InstanceFilter { + + private final ClusterView view; + + private InClusterView(ClusterView view) { + this.view = view; + } + + public boolean accept(InstanceDescription instance) { + return view.getId().equals(instance.getClusterView().getId()); + } + } + + /** + * The {@code InstanceCollection} collection allows to filter the instances using a set of custom filter + * either implementing {@code InstanceFilter} or pre-defined ones.<p> + * + * Filters conditions are joined combined together using the logical operator "AND".<p> + */ + public final class InstanceCollection { + + /** + * Holds the instances to be filtered. + * + * The map keys are the instance Sling identifiers and values are + * the {@code InstanceDescription} instances descriptions. + */ + private final Map<String, InstanceDescription> instances; + + /** + * Holds the set of filters to be applied (ANDed). + */ + private final Set<InstanceFilter> filters = new HashSet<InstanceFilter>(); + + /** + * Filter the instances with a custom {@code InstanceFilter} filter. + * + * @param filter the filter to be applied on the instances + * @return {@code this} + */ + @Nonnull + public InstanceCollection filterWith(@Nullable InstanceFilter filter) { + if (filter != null) { + filters.add(filter); + } + return this; + } + + /** + * Keep only the local instance (see {@link InstanceDescription#isLocal()}. + * + * @return {@code this} + */ + @Nonnull + public InstanceCollection isLocal() { + filters.add(LOCAL_INSTANCE); + return this; + } + + /** + * Filter out the local instances (see {@link InstanceDescription#isLocal()}. + * + * @return {@code this} + */ + @Nonnull + public InstanceCollection isNotLocal() { + filters.add(NOT_LOCAL_INSTANCE); + return this; + } + + /** + * Keep only the leader instances (see {@link InstanceDescription#isLeader()}. + * + * @return {@code this} + */ + @Nonnull + public InstanceCollection isLeader() { + filters.add(LEADER_INSTANCE); + return this; + } + + /** + * Filter out the leader instances (see {@link InstanceDescription#isLeader()}. + * + * @return {@code this} + */ + @Nonnull + public InstanceCollection isNotLeader() { + filters.add(NOT_LEADER_INSTANCE); + return this; + } + + /** + * Keep only the instances that are contained in the same {@code ClusterView} cluster view + * as the one provided.<p> + * + * The comparison between cluster views is done on the basis of the cluster + * view identifier. Two cluster views with the same identifier are considered equal.<p> + * + * @param clusterView the cluster view used to filter the instances + * @return {@code this} + */ + @Nonnull + public InstanceCollection isInClusterView(@Nullable ClusterView clusterView) { + if (clusterView != null) { + filters.add(new InClusterView(clusterView)); + } + return this; + } + + /** + * Filter out the instances that are contained in the same {@code ClusterView} cluster view + * as the one provided.<p> + * + * The comparison between cluster views is done on the basis of the cluster + * view identifier. Two cluster views with the same identifier are considered equal.<p> + * + * @param clusterView the cluster view used to filter the instances + * @return {@code this} + */ + @Nonnull + public InstanceCollection isNotInClusterView(@Nullable ClusterView clusterView) { + if (clusterView != null) { + filters.add(new NotFilter(new InClusterView(clusterView))); + } + return this; + } + + /** + * Return the collection of {@code InstanceDescription} instances that have not been filtered out. + * + * @return the filtered collection of instances. + */ + @Nonnull + public Collection<InstanceDescription> get() { + return applyFilters(); + } + + // + + /** + * Instances of this class can only be obtained through the {@code InstancesDiff} class. + * @param instances the map of instances to be filtered + */ + private InstanceCollection(@Nonnull Map<String, InstanceDescription> instances) { + this.instances = instances; + } + + @Nonnull + private Collection<InstanceDescription> applyFilters() { + Iterator<Map.Entry<String, InstanceDescription>> entries = instances.entrySet().iterator(); + for ( ; entries.hasNext() ; ) { + Map.Entry<String, InstanceDescription> entry = entries.next(); + for (InstanceFilter filter : filters) { + if (! filter.accept(entry.getValue())) { + entries.remove(); + break; + } + } + } + return Collections.<InstanceDescription>unmodifiableCollection(instances.values()); + } + } +} diff --git a/src/main/java/org/apache/sling/discovery/commons/package-info.java b/src/main/java/org/apache/sling/discovery/commons/package-info.java new file mode 100644 index 0000000..e104df0 --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/commons/package-info.java @@ -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; + +import aQute.bnd.annotation.Version; + diff --git a/src/test/java/org/apache/sling/discovery/commons/InstancesDiffTest.java b/src/test/java/org/apache/sling/discovery/commons/InstancesDiffTest.java new file mode 100644 index 0000000..a2d5ddc --- /dev/null +++ b/src/test/java/org/apache/sling/discovery/commons/InstancesDiffTest.java @@ -0,0 +1,331 @@ +/* + * 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; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; +import org.apache.sling.discovery.ClusterView; +import org.apache.sling.discovery.InstanceDescription; +import org.apache.sling.discovery.InstanceFilter; +import org.junit.Test; +import org.mockito.Mockito; + +public class InstancesDiffTest { + + @Test(expected = IllegalArgumentException.class) + public void testDuplicatedSlingIds() { + List<Instance> old = Arrays.asList(new Instance("duplicated"), new Instance("one"), new Instance("duplicated")); + new InstancesDiff(old, empty()); + } + + @Test + public void testEmptyCollections() { + InstancesDiff diff = new InstancesDiff(empty(), empty()); + TestCase.assertEquals(0, diff.all(true).get().size()); + TestCase.assertEquals(0, diff.added().get().size()); + TestCase.assertEquals(0, diff.removed().get().size()); + TestCase.assertEquals(0, diff.retained(true).get().size()); + } + + // added + + @Test + public void testAddedFromEmpty() throws Exception { + InstancesDiff diff = new InstancesDiff(empty(), Arrays.asList(new Instance("one"), new Instance("two"))); + TestCase.assertEquals(2, diff.added().get().size()); + } + + @Test + public void testAddedWithEmpty() throws Exception { + InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")), empty()); + TestCase.assertEquals(0, diff.added().get().size()); + } + + @Test + public void testAddedWithoutIntersection() throws Exception { + InstancesDiff diff = new InstancesDiff(Collections.singletonList(new Instance("one")), + Collections.singletonList(new Instance("two"))); + TestCase.assertEquals(1, diff.added().get().size()); + TestCase.assertEquals("two", diff.added().get().iterator().next().getSlingId()); + } + + @Test + public void testAddedWithIntersection() throws Exception { + InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")), + Arrays.asList(new Instance("two"), new Instance("three"))); + TestCase.assertEquals(1, diff.added().get().size()); + TestCase.assertEquals("three", diff.added().get().iterator().next().getSlingId()); + } + + // all + + @Test + public void testAll() throws Exception { + InstancesDiff diff = new InstancesDiff(Collections.singletonList(new Instance("one")), + Arrays.asList(new Instance("two"), new Instance("three"))); + TestCase.assertEquals(3, diff.all(true).get().size()); + } + + @Test + public void testAllRetainedCollection() throws Exception { + Instance oldInstance = new Instance("one"); + Instance newInstance = new Instance("one"); + InstancesDiff diff = new InstancesDiff( + Collections.singletonList(oldInstance), + Collections.singletonList(newInstance)); + TestCase.assertEquals(1, diff.all(true).get().size()); + TestCase.assertEquals(newInstance, diff.all(true).get().iterator().next()); + TestCase.assertEquals(1, diff.all(false).get().size()); + TestCase.assertEquals(oldInstance, diff.all(false).get().iterator().next()); + } + + // removed + + @Test + public void testRemovedFromEmpty() throws Exception { + InstancesDiff diff = new InstancesDiff(empty(), Arrays.asList(new Instance("one"), new Instance("two"))); + TestCase.assertEquals(0, diff.removed().get().size()); + } + + @Test + public void testRemovedWithEmpty() throws Exception { + InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")), empty()); + TestCase.assertEquals(2, diff.removed().get().size()); + } + + @Test + public void testRemovedWithoutIntersection() throws Exception { + InstancesDiff diff = new InstancesDiff(Collections.singletonList(new Instance("one")), + Collections.singletonList(new Instance("two"))); + TestCase.assertEquals(1, diff.removed().get().size()); + TestCase.assertEquals("one", diff.removed().get().iterator().next().getSlingId()); + } + + @Test + public void testRemovedWithIntersection() throws Exception { + InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")), + Arrays.asList(new Instance("two"), new Instance("three"))); + TestCase.assertEquals(1, diff.removed().get().size()); + TestCase.assertEquals("one", diff.removed().get().iterator().next().getSlingId()); + } + + // retained + + @Test + public void testRetainedWithoutIntersection() throws Exception { + InstancesDiff diff = new InstancesDiff(empty(), Arrays.asList(new Instance("one"), new Instance("two"))); + TestCase.assertEquals(0, diff.retained(true).get().size()); + } + + @Test + public void testRetainedWithIntersection() throws Exception { + InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")), + Arrays.asList(new Instance("two"), new Instance("three"))); + TestCase.assertEquals(1, diff.retained(true).get().size()); + TestCase.assertEquals("two", diff.retained(true).get().iterator().next().getSlingId()); + } + + @Test + public void testRetainedCollection() throws Exception { + Instance oldInstance = new Instance("one"); + Instance newInstance = new Instance("one"); + InstancesDiff diff = new InstancesDiff(Collections.singletonList(oldInstance), + Collections.singletonList(newInstance)); + TestCase.assertEquals(1, diff.retained(true).get().size()); + TestCase.assertEquals(newInstance, diff.retained(true).get().iterator().next()); + TestCase.assertEquals(oldInstance, diff.retained(false).get().iterator().next()); + } + + @Test + public void testRetainedByProperties() throws Exception { + InstancesDiff diff = new InstancesDiff( + Arrays.asList(new Instance("one", Collections.singletonMap("p1", "v1")), new Instance("two", Collections.singletonMap("p1", "v1"))), + Arrays.asList(new Instance("one", Collections.singletonMap("p1", "v2")), new Instance("two", Collections.singletonMap("p1", "v1")))); + TestCase.assertEquals(1, diff.retained(true, false).get().size()); + TestCase.assertEquals("two", diff.retained(true, false).get().iterator().next().getSlingId()); + TestCase.assertEquals(1, diff.retained(true, true).get().size()); + TestCase.assertEquals("one", diff.retained(true, true).get().iterator().next().getSlingId()); + } + + // filters + + @Test + public void testEmptyResult() throws Exception { + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one"), new Instance("two")), empty()).all(true).filterWith(new InstanceFilter() { + public boolean accept(InstanceDescription instanceDescription) { + return false; + } + }).get(); + TestCase.assertEquals(0, instances.size()); + } + + @Test + public void testFilterWith() throws Exception { + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one"), new Instance("two")), empty()).all(true).filterWith(new InstanceFilter() { + public boolean accept(InstanceDescription instanceDescription) { + return "one".equals(instanceDescription.getSlingId()); + } + }).get(); + TestCase.assertEquals(1, instances.size()); + TestCase.assertEquals("one", instances.iterator().next().getSlingId()); + } + + @Test + public void testIsLeader() throws Exception { + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"), + new Instance("two", false, false, Collections.<String, String>emptyMap(), "viewId")), empty()) + .all(true) + .isLeader() + .get(); + TestCase.assertEquals(1, instances.size()); + TestCase.assertEquals("one", instances.iterator().next().getSlingId()); + } + + @Test + public void testIsNotLeader() throws Exception { + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"), + new Instance("two", false, false, Collections.<String, String>emptyMap(), "viewId")), empty()) + .all(true) + .isNotLeader() + .get(); + TestCase.assertEquals(1, instances.size()); + TestCase.assertEquals("two", instances.iterator().next().getSlingId()); + } + + @Test + public void testIsLocal() throws Exception { + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"), + new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty()) + .all(true) + .isLocal() + .get(); + TestCase.assertEquals(1, instances.size()); + TestCase.assertEquals("two", instances.iterator().next().getSlingId()); + } + + @Test + public void testIsNotLocal() throws Exception { + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"), + new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty()) + .all(true) + .isNotLocal() + .get(); + TestCase.assertEquals(1, instances.size()); + TestCase.assertEquals("one", instances.iterator().next().getSlingId()); + } + + @Test + public void testIsInClusterView() throws Exception { + ClusterView clusterView = clusterView("viewId"); + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one", true, false, Collections.<String, String>emptyMap(), "otherView"), + new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty()) + .all(true) + .isInClusterView(clusterView) + .get(); + TestCase.assertEquals(1, instances.size()); + TestCase.assertEquals("two", instances.iterator().next().getSlingId()); + } + + @Test + public void testIsNotInClusterView() throws Exception { + ClusterView clusterView = clusterView("yet-another-view"); + Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList( + new Instance("one", true, false, Collections.<String, String>emptyMap(), "otherView"), + new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty()) + .all(true) + .isInClusterView(clusterView) + .get(); + TestCase.assertEquals(0, instances.size()); + } + + private List<Instance> empty() { + return Collections.<Instance>emptyList(); + } + + private class Instance implements InstanceDescription { + + final String slingId; + + final boolean leader; + + final boolean local; + + final Map<String, String> properties; + + final ClusterView clusterView; + + Instance(String slingId) { + this(slingId, false, false, Collections.<String, String>emptyMap(), ""); + } + + Instance(String slingId, Map<String, String> properties) { + this(slingId, false, false, properties, ""); + } + + Instance(String slingId, boolean leader, boolean local, Map<String, String> properties, String clusterViewId) { + this.slingId = slingId; + this.leader = leader; + this.local = local; + this.properties = properties; + clusterView = clusterView(clusterViewId); + } + + public ClusterView getClusterView() { + return clusterView; + } + + public boolean isLeader() { + return leader; + } + + public boolean isLocal() { + return local; + } + + public String getSlingId() { + return slingId; + } + + public String getProperty(String name) { + return properties.get(name); + } + + public Map<String, String> getProperties() { + return properties; + } + } + + private ClusterView clusterView(String clusterViewId) { + ClusterView clusterView = Mockito.mock(ClusterView.class); + Mockito.when(clusterView.getId()).thenReturn(clusterViewId); + return clusterView; + } +} \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
