diff --git a/apache-geode-extensions/src/main/java/org/springframework/geode/distributed/event/support/QuorumLostEvent.java b/apache-geode-extensions/src/main/java/org/springframework/geode/distributed/event/support/QuorumLostEvent.java new file mode 100644 index 00000000..a4b91bf4 --- /dev/null +++ b/apache-geode-extensions/src/main/java/org/springframework/geode/distributed/event/support/QuorumLostEvent.java @@ -0,0 +1,161 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.geode.distributed.event.support; + +import java.util.Arrays; +import java.util.Collections; + +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.DistributedSystem; +import org.apache.geode.distributed.internal.DistributionManager; + +import org.springframework.geode.distributed.event.MembershipEvent; + +/** + * {@link QuorumLostEvent} is fired for the losing side of the {@link DistributedSystem} when + * a network partition occurs. + * + * @author John Blum + * @see org.apache.geode.distributed.DistributedMember + * @see org.apache.geode.distributed.DistributedSystem + * @see org.springframework.geode.distributed.event.MembershipEvent + * @since 1.3.0 + */ +public class QuorumLostEvent extends MembershipEvent { + + private Iterable remainingMembers = Collections.emptyList(); + + private Iterable failedMembers = Collections.emptySet(); + + /** + * Constructs a new instance of {@link QuorumLostEvent} initialized with the given {@link DistributionManager}. + * + * @param distributionManager {@link DistributionManager} used as the {@link #getSource() source} of this event; + * must not be {@literal null}. + * @throws IllegalArgumentException if {@link DistributionManager} is {@literal null}. + * @see org.apache.geode.distributed.internal.DistributionManager + */ + public QuorumLostEvent(DistributionManager distributionManager) { + super(distributionManager); + } + + /** + * Gets the configured {@link Iterable} of failed {@link DistributedMember peer members} + * in the {@link DistributedSystem} that are on the losing side of a network partition. + * + * @return an {@link Iterable} of failed {@link DistributedMember peer members}; never {@literal null}. + * @see org.apache.geode.distributed.DistributedMember + * @see #getRemainingMembers() + * @see java.lang.Iterable + */ + public Iterable getFailedMembers() { + return this.failedMembers; + } + + /** + * Gets the configured {@link Iterable} of remaining {@link DistributedMember peer members} + * in the {@link DistributedSystem} that are on the winning side of a network partition. + * + * @return an {@link Iterable} of remaining {@link DistributedMember peer members}; never {@literal null}. + * @see org.apache.geode.distributed.DistributedMember + * @see #getFailedMembers() + * @see java.lang.Iterable + */ + public Iterable getRemainingMembers() { + return this.remainingMembers; + } + + /** + * Null-safe builder method used to configure an array of failing {@link DistributedMember peer members} + * in the {@link DistributedSystem} on the losing side of a network partition. + * + * @param failedMembers array of failed {@link DistributedMember peer members}; may be {@literal null}. + * @return this {@link QuorumLostEvent}. + * @see org.apache.geode.distributed.DistributedMember + * @see #withFailedMembers(Iterable) + * @see #getFailedMembers() + */ + public QuorumLostEvent withFailedMembers(DistributedMember... failedMembers) { + + return withFailedMembers(failedMembers != null + ? Arrays.asList(failedMembers) + : Collections.emptySet()); + } + + /** + * Null-safe builder method used to configure an {@link Iterable} of failing {@link DistributedMember peer members} + * in the {@link DistributedSystem} on the losing side of a network partition. + * + * @param failedMembers {@link Iterable} of failed {@link DistributedMember peer members}; may be {@literal null}. + * @return this {@link QuorumLostEvent}. + * @see org.apache.geode.distributed.DistributedMember + * @see #getFailedMembers() + * @see java.lang.Iterable + */ + public QuorumLostEvent withFailedMembers(Iterable failedMembers) { + + this.failedMembers = failedMembers != null + ? failedMembers + : Collections.emptySet(); + + return this; + } + + /** + * Null-safe builder method used to configure an array of remaining {@link DistributedMember peer members} + * in the {@link DistributedSystem} on the winning side of a network partition. + * + * @param remainingMembers array of remaining {@link DistributedMember peer members}; may be {@literal null}. + * @return this {@link QuorumLostEvent}. + * @see org.apache.geode.distributed.DistributedMember + * @see #withRemainingMembers(Iterable) + * @see #getRemainingMembers() + */ + public QuorumLostEvent withRemainingMembers(DistributedMember... remainingMembers) { + + return withRemainingMembers(remainingMembers != null + ? Arrays.asList(remainingMembers) + : Collections.emptyList()); + } + + /** + * Null-safe builder method used to configure an {@link Iterable} of remaining {@link DistributedMember peer members} + * in the {@link DistributedSystem} on the winning side of a network partition. + * + * @param remainingMembers {@link Iterable} of remaining {@link DistributedMember peer members}; + * may be {@literal null}. + * @return this {@link QuorumLostEvent}. + * @see org.apache.geode.distributed.DistributedMember + * @see #getRemainingMembers() + * @see java.lang.Iterable + */ + public QuorumLostEvent withRemainingMembers(Iterable remainingMembers) { + + this.remainingMembers = remainingMembers != null + ? remainingMembers + : Collections.emptyList(); + + return this; + } + + /** + * @inheritDoc + */ + @Override + public final Type getType() { + return Type.QUORUM_LOST; + } +} diff --git a/apache-geode-extensions/src/test/java/org/springframework/geode/distributed/event/support/QuorumLostEventUnitTests.java b/apache-geode-extensions/src/test/java/org/springframework/geode/distributed/event/support/QuorumLostEventUnitTests.java new file mode 100644 index 00000000..6e0e3265 --- /dev/null +++ b/apache-geode-extensions/src/test/java/org/springframework/geode/distributed/event/support/QuorumLostEventUnitTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.geode.distributed.event.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; + +import java.util.Collections; + +import org.junit.Test; + +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.internal.DistributionManager; + +import org.springframework.geode.distributed.event.MembershipEvent; + +/** + * Unit Tests for {@link QuorumLostEvent}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.distributed.DistributedMember + * @see org.springframework.geode.distributed.event.MembershipEvent + * @see org.springframework.geode.distributed.event.support.QuorumLostEvent + * @since 1.3.0 + */ +public class QuorumLostEventUnitTests { + + @SuppressWarnings("unchecked") + private Iterable normalize(Iterable iterable) { + return (Iterable) iterable; + } + + @Test + public void constructsQuorumLostEvent() { + + DistributionManager mockDistributionManager = mock(DistributionManager.class); + + QuorumLostEvent event = new QuorumLostEvent(mockDistributionManager); + + assertThat(event).isNotNull(); + assertThat(event.getDistributionManager()).isEqualTo(mockDistributionManager); + assertThat(event.getFailedMembers()).isEmpty(); + assertThat(event.getRemainingMembers()).isEmpty(); + assertThat(event.getType()).isEqualTo(MembershipEvent.Type.QUORUM_LOST); + + verifyNoInteractions(mockDistributionManager); + } + + @Test + public void setAndGetFailedMembers() { + + DistributedMember mockMemberOne = mock(DistributedMember.class); + DistributedMember mockMemberTwo = mock(DistributedMember.class); + + DistributionManager mockDistributionManager = mock(DistributionManager.class); + + QuorumLostEvent event = new QuorumLostEvent(mockDistributionManager); + + assertThat(event).isNotNull(); + assertThat(event.getFailedMembers()).isNotNull(); + assertThat(event.getFailedMembers()).isEmpty(); + + assertThat(event.withFailedMembers(mockMemberOne, mockMemberTwo)).isSameAs(event); + assertThat(this.normalize(event.getFailedMembers())) + .containsExactlyInAnyOrder(mockMemberOne, mockMemberTwo); + + assertThat(event.withFailedMembers((DistributedMember[]) null)).isSameAs(event); + assertThat(event.getFailedMembers()).isNotNull(); + assertThat(event.getFailedMembers()).isEmpty(); + + assertThat(event.withFailedMembers(mockMemberOne)).isSameAs(event); + assertThat(this.normalize(event.getFailedMembers())).containsExactly(mockMemberOne); + + assertThat(event.withFailedMembers((Iterable) null)).isSameAs(event); + assertThat(event.getFailedMembers()).isNotNull(); + assertThat(event.getFailedMembers()).isEmpty(); + + assertThat(event.withFailedMembers(Collections.singleton(mockMemberTwo))).isSameAs(event); + assertThat(this.normalize(event.getFailedMembers())).containsExactly(mockMemberTwo); + + assertThat(event.withFailedMembers()).isSameAs(event); + assertThat(event.getFailedMembers()).isNotNull(); + assertThat(event.getFailedMembers()).isEmpty(); + } + + @Test + public void setAndGetRemainingMembers() { + + DistributedMember mockMemberOne = mock(DistributedMember.class); + DistributedMember mockMemberTwo = mock(DistributedMember.class); + + DistributionManager mockDistributionManager = mock(DistributionManager.class); + + QuorumLostEvent event = new QuorumLostEvent(mockDistributionManager); + + assertThat(event).isNotNull(); + assertThat(event.getRemainingMembers()).isNotNull(); + assertThat(event.getRemainingMembers()).isEmpty(); + + assertThat(event.withRemainingMembers(mockMemberOne, mockMemberTwo)).isSameAs(event); + assertThat(this.normalize(event.getRemainingMembers())) + .containsExactlyInAnyOrder(mockMemberOne, mockMemberTwo); + + assertThat(event.withRemainingMembers((DistributedMember[]) null)).isSameAs(event); + assertThat(event.getRemainingMembers()).isNotNull(); + assertThat(event.getRemainingMembers()).isEmpty(); + + assertThat(event.withRemainingMembers(mockMemberOne)).isSameAs(event); + assertThat(this.normalize(event.getRemainingMembers())) + .containsExactlyInAnyOrder(mockMemberOne); + + assertThat(event.withRemainingMembers((Iterable) null)).isSameAs(event); + assertThat(event.getRemainingMembers()).isNotNull(); + assertThat(event.getRemainingMembers()).isEmpty(); + + assertThat(event.withRemainingMembers(Collections.singletonList(mockMemberTwo))).isSameAs(event); + assertThat(this.normalize(event.getRemainingMembers())) + .containsExactlyInAnyOrder(mockMemberTwo); + + assertThat(event.withRemainingMembers()).isSameAs(event); + assertThat(event.getRemainingMembers()).isNotNull(); + assertThat(event.getRemainingMembers()).isEmpty(); + } +}