/* * 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.geode.management.membership; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.internal.Assert; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.management.ManagementService; /** * <p> * The <code>UniversalMembershipListenerAdapter</code> is a wrapper for * {@link org.apache.geode.management.membership.MembershipListener} and * {@link ClientMembershipListener}, providing a facade that makes both appear as a single * <code>MembershipListener</code> . This includes adapting <code>ClientMembershipListener</code> * events to appear as events for the <code>MembershipListener</code>. * </p> * * <p> * <code>UniversalMembershipListenerAdapter</code> implements <code>MembershipListener</code>, * exposing the callback in that interface as methods to be overridden by implementing classes. * </p> * * <p> * An internal implementation of <code>ClientMembershipListener</code> is registered when this class * is instantiated. This implementation creates a * {@link org.apache.geode.management.membership.MembershipEvent} and calls the corresponding * <code>MembershipListener</code> public methods on * <code>UniversalMembershipListenerAdapter</code>.The <code>ClientMembershipEvent</code>s are * wrapped to appear as <code>MembershipEvent</code>s. In this way, both types of membership events * appear as <code>MembershipEvent</code>s. * </p> * * <p> * Any CacheServer using the <code>UniversalMembershipListenerAdapter</code> will receive * notifications of peer membership changes and client membership changes through a single listener. * </p> * * <p> * Any cache client using the <code>UniversalMembershipListenerAdapter</code> would receive * notifications of cache server connection changes. If that cache client also creates a connection * to the GemFire {@link org.apache.geode.distributed.DistributedSystem}, then it will also register * the adapter for membership events. But it wont be an automatic process. User needs to register * the UniversalMembershipListenerAdapter with ManagementService to receive membership events. How * to register UniversalMembershipListenerAdapter with ManagementService is explained below. * </p> * * <p> * Subclasses of <code>UniversalMembershipListenerAdapter</code> may be registered as a * <code>MembershipListener</code> using * {@link org.apache.geode.management.ManagementService#addMembershipListener} .It is best, however, * to register the listener using {@link #registerMembershipListener} since this allows the adapter * to prevent duplicate events for members that are both a peer member and a client. * </p> * * <p> * Simply constructing the <code>UniversalMembershipListenerAdapter</code> results in the underlying * <code>ClientMembershipListener</code> also being registered. * </p> * * <p> * The following code illustrates how a CacheServer application would use * <code>UniversalMembershipListenerAdapter</code>. The code in this example assumes that the class * MyMembershipListenerImpl extends <code>UniversalMembershipListenerAdapter</code>: * * <pre> * <code> * public class MyMembershipListenerImpl extends UniversalMembershipListenerAdapter { * public void memberCrashed(MembershipEvent event) { * // customer code * } * public void memberLeft(MembershipEvent event) { * // customer code * } * public void memberJoined(MembershipEvent event) { * // customer code * } * } * * Cache cache = //Get hold of GemFire Cache instance * ManagementService service = ManagementService.getExistingManagementService(cache); * * MyMembershipListenerImpl myListener = new MyMembershipListenerImpl(); * myListener.registerMembershipListener(service); * </code> * </pre> * * The callback on MyMembershipListenerImpl would then be invoked for all * <code>MembershipEvent</code>s and <code>ClientMembershipEvent</code>s. The latter will appear to * be <code>MembershipEvent</code>s. * </p> * * <p> * Similarly, the following code illustrates how a client application would use * <code>UniversalMembershipListenerAdapter</code>, where MyMembershipListenerImpl is a * subclass.Simply by constructing this subclass of <code>UniversalMembershipListenerAdapter</code> * it is registering itself as a <code>ClientMembershipListener</code>: * * <pre> * <code> * new MyMembershipListenerImpl(); * </code> * </pre> * * A client that also connects to the <code>DistributedSystem</code> could register with * the<code>ManagementService</code> as shown above. * </p> * * <p> * It is recommended that subclasses register with the <code>ManagementService</code> using * {@link #registerMembershipListener}, as this will prevent duplicate events for members that are * both clients and peer members.If duplicate events are acceptable, you may register subclasses * using {@link org.apache.geode.management.ManagementService#addMembershipListener * ManagementService#addMembershipListener}. * </p> * * * @since GemFire 8.0 */ public abstract class UniversalMembershipListenerAdapter implements MembershipListener { /** * Default number of historical events to track in order to avoid duplicate events for members * that are both clients and peer members; value is 100. */ public static final int DEFAULT_HISTORY_SIZE = 100; private final int historySize; private final LinkedList<String> eventHistory; // list of String memberIds private final Map<String, Boolean> eventJoined; // key: memberId, value: Boolean /** Constructs an instance of UniversalMembershipListenerAdapter. */ public UniversalMembershipListenerAdapter() { this(DEFAULT_HISTORY_SIZE); } /** * Constructs an instance of UniversalMembershipListenerAdapter. * * @param historySize number of historical events to track in order to avoid duplicate events for * members that are both client and peer members; must a number between 10 and * <code>Integer.MAX_INT</code> * @throws IllegalArgumentException if historySize is less than 10 */ public UniversalMembershipListenerAdapter(int historySize) { if (historySize < 10) { throw new IllegalArgumentException( LocalizedStrings.UniversalMembershipListenerAdapter_ARGUMENT_HISTORYSIZE_MUST_BE_BETWEEN_10_AND_INTEGERMAX_INT_0 .toLocalizedString(Integer.valueOf(historySize))); } this.historySize = historySize; this.eventHistory = new LinkedList<String>(); this.eventJoined = new HashMap<String, Boolean>(); ClientMembership.registerClientMembershipListener(this.clientMembershipListener); } /** * Registers this adapter with the <code>ManagementService</code>. Registering in this way allows * the adapter to ensure that callback will not be invoked twice for members that have a client * connection and a peer connection. If you register with * {@link org.apache.geode.management.ManagementService#addMembershipListener} then duplicate * events may occur for members that are both client and peer. */ public void registerMembershipListener(ManagementService service) { synchronized (this.eventHistory) { service.addMembershipListener(this.membershipListener); } } /** * Unregisters this adapter with the <code>ManagementService</code>. If registration is performed * with {@link #registerMembershipListener} then this method must be used to successfully * unregister the adapter. */ public void unregisterMembershipListener(ManagementService service) { synchronized (this.eventHistory) { service.removeMembershipListener(this.membershipListener); } unregisterClientMembershipListener(); } /** * Registers this adapter as a <code>ClientMembershipListener</code>. Registration is automatic * when constructing this adapter, so this call is not necessary unless it was previously * unregistered by calling {@link #unregisterClientMembershipListener}. */ public void registerClientMembershipListener() { ClientMembership.registerClientMembershipListener(this.clientMembershipListener); } /** * Unregisters this adapter as a <code>ClientMembershipListener</code>. * * @see #registerClientMembershipListener */ public void unregisterClientMembershipListener() { ClientMembership.unregisterClientMembershipListener(this.clientMembershipListener); } /** * Invoked when a member has joined the distributed system. Also invoked when a client has * connected to this process or when this process has connected to a <code>CacheServer</code>. */ public void memberJoined(MembershipEvent event) {} /** * Invoked when a member has gracefully left the distributed system. Also invoked when a client * has gracefully disconnected from this process. or when this process has gracefully disconnected * from a <code>CacheServer</code>. */ public void memberLeft(MembershipEvent event) {} /** * Invoked when a member has unexpectedly left the distributed system. Also invoked when a client * has unexpectedly disconnected from this process or when this process has unexpectedly * disconnected from a <code>CacheServer</code>. */ public void memberCrashed(MembershipEvent event) {} /** Adapts ClientMembershipEvent to look like a MembershipEvent */ public static class AdaptedMembershipEvent implements MembershipEvent { private final ClientMembershipEvent event; protected AdaptedMembershipEvent(ClientMembershipEvent event) { this.event = event; } /** * Returns true if the member is a client to a CacheServer hosted by this process. Returns false * if the member is a CacheServer that this process is connected to. */ public boolean isClient() { return event.isClient(); } public String getMemberId() { return event.getMemberId(); } public DistributedMember getDistributedMember() { return event.getMember(); } @Override public boolean equals(Object other) { if (other == this) return true; if (other == null) return false; if (!(other instanceof AdaptedMembershipEvent)) return false; final AdaptedMembershipEvent that = (AdaptedMembershipEvent) other; if (this.event != that.event && !(this.event != null && this.event.equals(that.event))) return false; return true; } @Override public int hashCode() { return this.event.hashCode(); } @Override public String toString() { final StringBuffer sb = new StringBuffer("[AdaptedMembershipEvent: "); sb.append(this.event); sb.append("]"); return sb.toString(); } } private final ClientMembershipListener clientMembershipListener = new ClientMembershipListener() { public void memberJoined(ClientMembershipEvent event) { membershipListener.memberJoined(new AdaptedMembershipEvent(event)); } public void memberLeft(ClientMembershipEvent event) { membershipListener.memberLeft(new AdaptedMembershipEvent(event)); } public void memberCrashed(ClientMembershipEvent event) { membershipListener.memberCrashed(new AdaptedMembershipEvent(event)); } }; protected final MembershipListener membershipListener = new MembershipListener() { public void memberJoined(MembershipEvent event) { if (!isDuplicate(event, true)) { UniversalMembershipListenerAdapter.this.memberJoined(event); } } public void memberLeft(MembershipEvent event) { if (!isDuplicate(event, false)) { UniversalMembershipListenerAdapter.this.memberLeft(event); } } public void memberCrashed(MembershipEvent event) { if (!isDuplicate(event, false)) { UniversalMembershipListenerAdapter.this.memberCrashed(event); } } protected boolean isDuplicate(MembershipEvent event, boolean joined) { synchronized (eventHistory) { boolean duplicate = false; String memberId = event.getMemberId(); // find memberId in eventHistory... int indexOf = eventHistory.indexOf(memberId); if (indexOf > -1) { // found an event for this member if ((eventJoined.get(memberId)).booleanValue() == joined) { // we already recorded a matching event for this member duplicate = true; } else { // remove the event from history and map... will be re-inserted Assert.assertTrue(eventHistory.remove(memberId), "Failed to replace entry in eventHistory for " + memberId); Assert.assertTrue(eventJoined.remove(memberId) != null, "Failed to replace entry in eventJoined for " + memberId); } } if (!duplicate) { // add the event to the history and map if (eventHistory.size() == historySize) { // filled the eventHistory, so need to remove first entry eventHistory.removeFirst(); } eventHistory.addLast(memberId); // linked list eventJoined.put(memberId, Boolean.valueOf(joined)); // boolean map Assert.assertTrue(eventHistory.size() <= historySize, "Attempted to grow eventHistory beyond maximum of " + historySize); } return duplicate; } // sync } }; }