diff --git a/apache-geode-extensions/src/main/java/org/springframework/geode/distributed/event/MembershipEvent.java b/apache-geode-extensions/src/main/java/org/springframework/geode/distributed/event/MembershipEvent.java new file mode 100644 index 00000000..3d0abba1 --- /dev/null +++ b/apache-geode-extensions/src/main/java/org/springframework/geode/distributed/event/MembershipEvent.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; + +import java.util.EventObject; +import java.util.Optional; + +import org.apache.geode.cache.Cache; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.DistributedSystem; +import org.apache.geode.distributed.internal.DistributionManager; + +/** + * {@link EventObject} implementation indicating a membership event in the {@link DistributedSystem}. + * + * @author John Blum + * @param specific {@link Class type} of {@link MembershipEvent}. + * @see java.util.EventObject + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.distributed.DistributedMember + * @see org.apache.geode.distributed.DistributedSystem + * @see org.apache.geode.distributed.internal.DistributionManager + * @since 1.3.0 + */ +@SuppressWarnings("unused") +public abstract class MembershipEvent> extends EventObject { + + private DistributedMember distributedMember; + + /** + * Asserts that the given {@link Object target} is not {@literal null}. + * + * @param {@link Class type} of the {@link Object target}. + * @param target {@link Object} to evaluate. + * @param message {@link String} containing the message for the {@link IllegalArgumentException}. + * @param arguments array of {@link Object arguments} to populate the placeholders in the {@link String message}. + * @return the {@link Object target}. + * @throws IllegalArgumentException if {@link Object target} is {@literal null}. + */ + protected static T assertNotNull(T target, String message, Object... arguments) { + + if (target == null) { + throw new IllegalArgumentException(String.format(message, arguments)); + } + + return target; + } + + /** + * Constructs a new instance of {@link MembershipEvent} initialized with the given {@link DistributionManager}. + * + * @param distributionManager {@link DistributionManager} used to acquire the {@link Cache}, which is used + * as the {@literal source} of this event. + * @throws IllegalArgumentException if {@link DistributionManager} is {@literal null}. + * @see org.apache.geode.distributed.internal.DistributionManager + */ + public MembershipEvent(DistributionManager distributionManager) { + super(assertNotNull(distributionManager, "DistributionManager must not be null")); + } + + /** + * Returns an {@link Optional} reference to the {@literal peer} {@link Cache}. + * + * @return an {@link Optional} reference to the {@literal peer} {@link Cache}. + * @see org.apache.geode.cache.Cache + * @see #getDistributionManager() + * @see java.util.Optional + */ + public Optional getCache() { + return Optional.ofNullable(getDistributionManager().getCache()); + } + + /** + * Returns an {@link Optional} reference to the {@link DistributedMember} that is the subject + * of this {@link MembershipEvent}. + * + * @return an {@link Optional} reference to the {@link DistributedMember} that is the subject of this event. + * @see org.apache.geode.distributed.DistributedMember + * @see #getDistributionManager() + * @see java.util.Optional + */ + public Optional getDistributedMember() { + return Optional.ofNullable(this.distributedMember); + } + + /** + * Returns an {@link Optional} reference to the {@link DistributedSystem} (cluster) to which the {@literal peer} + * {@link Cache} is connected. + * + * @return an {@link Optional} reference to the {@link DistributedSystem}. + * @see org.apache.geode.distributed.DistributedSystem + * @see #getDistributionManager() + * @see java.util.Optional + */ + public Optional getDistributedSystem() { + return Optional.ofNullable(getDistributionManager().getSystem()); + } + + /** + * Returns a reference to the configured {@link DistributionManager} which is use as the {@link #getSource() source} + * of this event. + * + * @return a reference to the {@link DistributionManager}. + * @see org.apache.geode.distributed.internal.DistributionManager + */ + public DistributionManager getDistributionManager() { + return (DistributionManager) getSource(); + } + + /** + * Returns the {@link Type} of this {@link MembershipEvent}, such as {@link Type#MEMBER_JOINED}. + * + * @return the {@link MembershipEvent.Type}. + */ + public Type getType() { + return Type.UNQUALIFIED; + } + + /** + * Null-safe builder method used to configure the {@link DistributedMember member} that is the subject + * of this event. + * + * @param distributedMember {@link DistributedMember} that is the subject of this event. + * @return this {@link MembershipEvent}. + * @see org.apache.geode.distributed.DistributedMember + * @see #getDistributedMember() + */ + @SuppressWarnings("unchecked") + public T withMember(DistributedMember distributedMember) { + + this.distributedMember = distributedMember; + + return (T) this; + } + + /** + * An {@link Enum enumeration} of different type of {@link MembershipEvent MembershipEvents}. + */ + public enum Type { + + MEMBER_DEPARTED, + MEMBER_JOINED, + MEMBER_SUSPECT, + QUORUM_LOST, + UNQUALIFIED; + + } +} diff --git a/apache-geode-extensions/src/test/java/org/springframework/geode/distributed/event/MembershipEventUnitTests.java b/apache-geode-extensions/src/test/java/org/springframework/geode/distributed/event/MembershipEventUnitTests.java new file mode 100644 index 00000000..22543b72 --- /dev/null +++ b/apache-geode-extensions/src/test/java/org/springframework/geode/distributed/event/MembershipEventUnitTests.java @@ -0,0 +1,130 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.Test; + +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.internal.DistributionManager; +import org.apache.geode.distributed.internal.InternalDistributedSystem; +import org.apache.geode.internal.cache.InternalCache; + +/** + * Unit Tests for {@link MembershipEvent}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.distributed.DistributedMember + * @see org.apache.geode.distributed.DistributedSystem + * @see org.apache.geode.distributed.internal.DistributionManager + * @see org.springframework.geode.distributed.event.MembershipEvent + * @since 1.3.0 + */ +public class MembershipEventUnitTests { + + @Test + public void assertNotNullWithNonNullValue() { + assertThat(MembershipEvent.assertNotNull("test", "message")).isEqualTo("test"); + } + + @Test(expected = IllegalArgumentException.class) + public void assertNotNullWithNullValue() { + + try { + MembershipEvent.assertNotNull(null, "Message with argument [%s]", "test"); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Message with argument [test]"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void constructsNewMembershipEventWithDistributionManager() { + + DistributionManager mockDistributionManager = mock(DistributionManager.class); + + InternalCache mockCache = mock(InternalCache.class); + + InternalDistributedSystem mockDistributedSystem = mock(InternalDistributedSystem.class); + + doReturn(mockCache).when(mockDistributionManager).getCache(); + doReturn(mockDistributedSystem).when(mockDistributionManager).getSystem(); + + MembershipEvent event = new TestMembershipEvent(mockDistributionManager); + + assertThat(event).isNotNull(); + assertThat(event.getCache().orElse(null)).isEqualTo(mockCache); + assertThat(event.getDistributedMember().orElse(null)).isNull(); + assertThat(event.getDistributedSystem().orElse(null)).isEqualTo(mockDistributedSystem); + assertThat(event.getDistributionManager()).isEqualTo(mockDistributionManager); + assertThat(event.getType()).isEqualTo(MembershipEvent.Type.UNQUALIFIED); + + verify(mockDistributionManager, times(1)).getCache(); + verify(mockDistributionManager, times(1)).getSystem(); + verifyNoMoreInteractions(mockDistributionManager); + } + + @Test + public void withMemberReturnsMember() { + + DistributedMember mockDistributedMember = mock(DistributedMember.class); + + DistributionManager mockDistributionManager = mock(DistributionManager.class); + + MembershipEvent event = new TestMembershipEvent(mockDistributionManager); + + assertThat(event).isNotNull(); + assertThat(event.getDistributedMember().orElse(null)).isNull(); + assertThat(event.withMember(mockDistributedMember)).isSameAs(event); + assertThat(event.getDistributedMember().orElse(null)).isEqualTo(mockDistributedMember); + assertThat(event.withMember(null)).isSameAs(event); + assertThat(event.getDistributedMember().orElse(null)).isNull(); + } + + @Test(expected = IllegalArgumentException.class) + public void constructWithNullDistributionManagerThrowsIllegalArgumentException() { + + try { + new TestMembershipEvent(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("DistributionManager must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + static class TestMembershipEvent extends MembershipEvent { + + TestMembershipEvent(DistributionManager distributionManager) { + super(distributionManager); + } + } +}