/*
* 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.internal.cache.tier.sockets;
import static org.apache.geode.distributed.ConfigurationProperties.*;
import java.io.BufferedOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketAddress;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.subject.Subject;
import org.apache.geode.CancelException;
import org.apache.geode.DataSerializer;
import org.apache.geode.Instantiator;
import org.apache.geode.InternalGemFireError;
import org.apache.geode.StatisticsFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheEvent;
import org.apache.geode.cache.CacheException;
import org.apache.geode.cache.InterestRegistrationEvent;
import org.apache.geode.cache.InterestRegistrationListener;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionDestroyedException;
import org.apache.geode.cache.RegionExistsException;
import org.apache.geode.cache.UnsupportedVersionException;
import org.apache.geode.cache.client.internal.PoolImpl;
import org.apache.geode.cache.client.internal.PoolImpl.PoolTask;
import org.apache.geode.cache.query.CqException;
import org.apache.geode.cache.query.Query;
import org.apache.geode.cache.query.internal.DefaultQuery;
import org.apache.geode.cache.query.internal.cq.CqService;
import org.apache.geode.cache.query.internal.cq.ServerCQ;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.DM;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.DistributionManager;
import org.apache.geode.distributed.internal.HighPriorityDistributionMessage;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.distributed.internal.MessageWithReply;
import org.apache.geode.distributed.internal.ReplyMessage;
import org.apache.geode.distributed.internal.ReplyProcessor21;
import org.apache.geode.internal.ClassLoadUtil;
import org.apache.geode.internal.statistics.DummyStatisticsFactory;
import org.apache.geode.internal.InternalDataSerializer;
import org.apache.geode.internal.InternalInstantiator;
import org.apache.geode.internal.net.SocketCloser;
import org.apache.geode.internal.SystemTimer;
import org.apache.geode.internal.Version;
import org.apache.geode.internal.VersionedDataInputStream;
import org.apache.geode.internal.VersionedDataOutputStream;
import org.apache.geode.internal.cache.CacheClientStatus;
import org.apache.geode.internal.cache.CacheDistributionAdvisor;
import org.apache.geode.internal.cache.CacheServerImpl;
import org.apache.geode.internal.cache.ClientRegionEventImpl;
import org.apache.geode.internal.cache.ClientServerObserver;
import org.apache.geode.internal.cache.ClientServerObserverHolder;
import org.apache.geode.internal.cache.Conflatable;
import org.apache.geode.internal.cache.DistributedRegion;
import org.apache.geode.internal.cache.EntryEventImpl;
import org.apache.geode.internal.cache.EnumListenerEvent;
import org.apache.geode.internal.cache.EventID;
import org.apache.geode.internal.cache.FilterProfile;
import org.apache.geode.internal.cache.FilterRoutingInfo.FilterInfo;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalCacheEvent;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.internal.cache.RegionEventImpl;
import org.apache.geode.internal.cache.ha.HAContainerMap;
import org.apache.geode.internal.cache.ha.HAContainerRegion;
import org.apache.geode.internal.cache.ha.HAContainerWrapper;
import org.apache.geode.internal.cache.ha.HARegionQueue;
import org.apache.geode.internal.cache.ha.ThreadIdentifier;
import org.apache.geode.internal.cache.tier.Acceptor;
import org.apache.geode.internal.cache.tier.MessageType;
import org.apache.geode.internal.cache.versions.VersionTag;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.logging.InternalLogWriter;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.logging.log4j.LocalizedMessage;
import org.apache.geode.internal.net.SocketCloser;
import org.apache.geode.security.AccessControl;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.AuthenticationRequiredException;
/**
* Class <code>CacheClientNotifier</code> works on the server and manages client socket connections
* to clients requesting notification of updates and notifies them when updates occur.
*
*
* @since GemFire 3.2
*/
@SuppressWarnings({"synthetic-access", "deprecation"})
public class CacheClientNotifier {
private static final Logger logger = LogService.getLogger();
private static volatile CacheClientNotifier ccnSingleton;
/**
* Factory method to construct a CacheClientNotifier <code>CacheClientNotifier</code> instance.
*
* @param cache The GemFire <code>Cache</code>
* @param acceptorStats
* @param maximumMessageCount
* @param messageTimeToLive
* @param listener
* @param overflowAttributesList
* @return A <code>CacheClientNotifier</code> instance
*/
public static synchronized CacheClientNotifier getInstance(Cache cache,
CacheServerStats acceptorStats, int maximumMessageCount, int messageTimeToLive,
ConnectionListener listener, List overflowAttributesList, boolean isGatewayReceiver) {
if (ccnSingleton == null) {
ccnSingleton = new CacheClientNotifier(cache, acceptorStats, maximumMessageCount,
messageTimeToLive, listener, overflowAttributesList, isGatewayReceiver);
}
if (!isGatewayReceiver && ccnSingleton.getHaContainer() == null) {
// Gateway receiver might have create CCN instance without HaContainer
// In this case, the HaContainer should be lazily created here
ccnSingleton.initHaContainer(overflowAttributesList);
}
// else {
// ccnSingleton.acceptorStats = acceptorStats;
// ccnSingleton.maximumMessageCount = maximumMessageCount;
// ccnSingleton.messageTimeToLive = messageTimeToLive;
// ccnSingleton._connectionListener = listener;
// ccnSingleton.setCache((GemFireCache)cache);
// }
return ccnSingleton;
}
public static CacheClientNotifier getInstance() {
return ccnSingleton;
}
/**
* Writes a given message to the output stream
*
* @param dos the <code>DataOutputStream</code> to use for writing the message
* @param type a byte representing the message type
* @param p_msg the message to be written; can be null
* @param clientVersion
*
*/
private void writeMessage(DataOutputStream dos, byte type, String p_msg, Version clientVersion)
throws IOException {
writeMessage(dos, type, p_msg, clientVersion, (byte) 0x00, 0);
}
private void writeMessage(DataOutputStream dos, byte type, String p_msg, Version clientVersion,
byte epType, int qSize) throws IOException {
String msg = p_msg;
// write the message type
dos.writeByte(type);
// dummy epType
dos.writeByte(epType);
// dummy qSize
dos.writeInt(qSize);
if (msg == null) {
msg = "";
}
dos.writeUTF(msg);
if (clientVersion != null && clientVersion.compareTo(Version.GFE_61) >= 0) {
// get all the instantiators.
Instantiator[] instantiators = InternalInstantiator.getInstantiators();
HashMap instantiatorMap = new HashMap();
if (instantiators != null && instantiators.length > 0) {
for (Instantiator instantiator : instantiators) {
ArrayList instantiatorAttributes = new ArrayList();
instantiatorAttributes.add(instantiator.getClass().toString().substring(6));
instantiatorAttributes.add(instantiator.getInstantiatedClass().toString().substring(6));
instantiatorMap.put(instantiator.getId(), instantiatorAttributes);
}
}
DataSerializer.writeHashMap(instantiatorMap, dos);
// get all the dataserializers.
DataSerializer[] dataSerializers = InternalDataSerializer.getSerializers();
HashMap<Integer, ArrayList<String>> dsToSupportedClasses =
new HashMap<Integer, ArrayList<String>>();
HashMap<Integer, String> dataSerializersMap = new HashMap<Integer, String>();
if (dataSerializers != null && dataSerializers.length > 0) {
for (DataSerializer dataSerializer : dataSerializers) {
dataSerializersMap.put(dataSerializer.getId(),
dataSerializer.getClass().toString().substring(6));
if (clientVersion.compareTo(Version.GFE_6516) >= 0) {
ArrayList<String> supportedClassNames = new ArrayList<String>();
for (Class clazz : dataSerializer.getSupportedClasses()) {
supportedClassNames.add(clazz.getName());
}
dsToSupportedClasses.put(dataSerializer.getId(), supportedClassNames);
}
}
}
DataSerializer.writeHashMap(dataSerializersMap, dos);
if (clientVersion.compareTo(Version.GFE_6516) >= 0) {
DataSerializer.writeHashMap(dsToSupportedClasses, dos);
}
}
dos.flush();
}
/**
* Writes an exception message to the socket
*
* @param dos the <code>DataOutputStream</code> to use for writing the message
* @param type a byte representing the exception type
* @param ex the exception to be written; should not be null
* @param clientVersion
*
*/
private void writeException(DataOutputStream dos, byte type, Exception ex, Version clientVersion)
throws IOException {
writeMessage(dos, type, ex.toString(), clientVersion);
}
// /**
// * Factory method to return the singleton <code>CacheClientNotifier</code>
// * instance.
// * @return the singleton <code>CacheClientNotifier</code> instance
// */
// public static CacheClientNotifier getInstance()
// {
// return _instance;
// }
// /**
// * Shuts down the singleton <code>CacheClientNotifier</code> instance.
// */
// public static void shutdownInstance()
// {
// if (_instance == null) return;
// _instance.shutdown();
// _instance = null;
// }
/**
* Registers a new client updater that wants to receive updates with this server.
*
* @param socket The socket over which the server communicates with the client.
*/
public void registerClient(Socket socket, boolean isPrimary, long acceptorId,
boolean notifyBySubscription) throws IOException {
// Since no remote ports were specified in the message, wait for them.
long startTime = this._statistics.startTime();
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
// Read the client version
short clientVersionOrdinal = Version.readOrdinal(dis);
Version clientVersion = null;
try {
clientVersion = Version.fromOrdinal(clientVersionOrdinal, true);
if (logger.isDebugEnabled()) {
logger.debug("{}: Registering client with version: {}", this, clientVersion);
}
} catch (UnsupportedVersionException e) {
SocketAddress sa = socket.getRemoteSocketAddress();
UnsupportedVersionException uve = e;
if (sa != null) {
String sInfo = " Client: " + sa.toString() + ".";
uve = new UnsupportedVersionException(e.getMessage() + sInfo);
}
logger.warn(
LocalizedMessage.create(
LocalizedStrings.CacheClientNotifier_CACHECLIENTNOTIFIER_CAUGHT_EXCEPTION_ATTEMPTING_TO_CLIENT),
uve);
writeException(dos, Acceptor.UNSUCCESSFUL_SERVER_TO_CLIENT, uve, clientVersion);
return;
}
// Read and ignore the reply code. This is used on the client to server
// handshake.
dis.readByte(); // replyCode
if (Version.GFE_57.compareTo(clientVersion) <= 0) {
if (Version.CURRENT.compareTo(clientVersion) > 0) {
dis = new VersionedDataInputStream(dis, clientVersion);
dos = new VersionedDataOutputStream(dos, clientVersion);
}
registerGFEClient(dis, dos, socket, isPrimary, startTime, clientVersion, acceptorId,
notifyBySubscription);
} else {
Exception e = new UnsupportedVersionException(clientVersionOrdinal);
throw new IOException(e.toString());
}
}
protected void registerGFEClient(DataInputStream dis, DataOutputStream dos, Socket socket,
boolean isPrimary, long startTime, Version clientVersion, long acceptorId,
boolean notifyBySubscription) throws IOException {
// Read the ports and throw them away. We no longer need them
int numberOfPorts = dis.readInt();
for (int i = 0; i < numberOfPorts; i++) {
dis.readInt();
}
// Read the handshake identifier and convert it to a string member id
ClientProxyMembershipID proxyID = null;
CacheClientProxy proxy;
AccessControl authzCallback = null;
byte clientConflation = HandShake.CONFLATION_DEFAULT;
try {
proxyID = ClientProxyMembershipID.readCanonicalized(dis);
if (getBlacklistedClient().contains(proxyID)) {
writeException(dos, HandShake.REPLY_INVALID,
new Exception("This client is blacklisted by server"), clientVersion);
return;
}
proxy = getClientProxy(proxyID);
DistributedMember member = proxyID.getDistributedMember();
DistributedSystem system = this.getCache().getDistributedSystem();
Properties sysProps = system.getProperties();
String authenticator = sysProps.getProperty(SECURITY_CLIENT_AUTHENTICATOR);
// TODO;hitesh for conflation
if (clientVersion.compareTo(Version.GFE_603) >= 0) {
byte[] overrides = HandShake.extractOverrides(new byte[] {(byte) dis.read()});
clientConflation = overrides[0];
} else {
clientConflation = (byte) dis.read();
}
switch (clientConflation) {
case HandShake.CONFLATION_DEFAULT:
case HandShake.CONFLATION_OFF:
case HandShake.CONFLATION_ON:
break;
default:
writeException(dos, HandShake.REPLY_INVALID,
new IllegalArgumentException("Invalid conflation byte"), clientVersion);
return;
}
proxy = registerClient(socket, proxyID, proxy, isPrimary, clientConflation, clientVersion,
acceptorId, notifyBySubscription);
// TODO:hitesh
Properties credentials = HandShake.readCredentials(dis, dos, system);
if (credentials != null && proxy != null) {
if (securityLogWriter.fineEnabled()) {
securityLogWriter
.fine("CacheClientNotifier: verifying credentials for proxyID: " + proxyID);
}
Object subject = HandShake.verifyCredentials(authenticator, credentials,
system.getSecurityProperties(), this.logWriter, this.securityLogWriter, member);
if (subject instanceof Principal) {
Principal principal = (Principal) subject;
if (securityLogWriter.fineEnabled()) {
securityLogWriter
.fine("CacheClientNotifier: successfully verified credentials for proxyID: "
+ proxyID + " having principal: " + principal.getName());
}
String postAuthzFactoryName = sysProps.getProperty(SECURITY_CLIENT_ACCESSOR_PP);
if (postAuthzFactoryName != null && postAuthzFactoryName.length() > 0) {
if (principal == null) {
securityLogWriter.warning(
LocalizedStrings.CacheClientNotifier_CACHECLIENTNOTIFIER_POST_PROCESS_AUTHORIZATION_CALLBACK_ENABLED_BUT_AUTHENTICATION_CALLBACK_0_RETURNED_WITH_NULL_CREDENTIALS_FOR_PROXYID_1,
new Object[] {SECURITY_CLIENT_AUTHENTICATOR, proxyID});
}
Method authzMethod = ClassLoadUtil.methodFromName(postAuthzFactoryName);
authzCallback = (AccessControl) authzMethod.invoke(null, (Object[]) null);
authzCallback.init(principal, member, this.getCache());
}
proxy.setPostAuthzCallback(authzCallback);
} else if (subject instanceof Subject) {
proxy.setSubject((Subject) subject);
}
}
} catch (ClassNotFoundException e) {
throw new IOException(
LocalizedStrings.CacheClientNotifier_CLIENTPROXYMEMBERSHIPID_OBJECT_COULD_NOT_BE_CREATED_EXCEPTION_OCCURRED_WAS_0
.toLocalizedString(e));
} catch (AuthenticationRequiredException ex) {
securityLogWriter.warning(
LocalizedStrings.CacheClientNotifier_AN_EXCEPTION_WAS_THROWN_FOR_CLIENT_0_1,
new Object[] {proxyID, ex});
writeException(dos, HandShake.REPLY_EXCEPTION_AUTHENTICATION_REQUIRED, ex, clientVersion);
return;
} catch (AuthenticationFailedException ex) {
securityLogWriter.warning(
LocalizedStrings.CacheClientNotifier_AN_EXCEPTION_WAS_THROWN_FOR_CLIENT_0_1,
new Object[] {proxyID, ex});
writeException(dos, HandShake.REPLY_EXCEPTION_AUTHENTICATION_FAILED, ex, clientVersion);
return;
} catch (CacheException e) {
logger.warn(LocalizedMessage.create(
LocalizedStrings.CacheClientNotifier_0_REGISTERCLIENT_EXCEPTION_ENCOUNTERED_IN_REGISTRATION_1,
new Object[] {this, e}), e);
IOException io = new IOException(
LocalizedStrings.CacheClientNotifier_EXCEPTION_OCCURRED_WHILE_TRYING_TO_REGISTER_INTEREST_DUE_TO_0
.toLocalizedString(e.getMessage()));
io.initCause(e);
throw io;
} catch (Exception ex) {
logger.warn(LocalizedMessage.create(
LocalizedStrings.CacheClientNotifier_AN_EXCEPTION_WAS_THROWN_FOR_CLIENT_0_1,
new Object[] {proxyID, ""}), ex);
writeException(dos, Acceptor.UNSUCCESSFUL_SERVER_TO_CLIENT, ex, clientVersion);
return;
}
this._statistics.endClientRegistration(startTime);
}
/**
* Registers a new client that wants to receive updates with this server.
*
* @param socket The socket over which the server communicates with the client.
* @param proxyId The distributed member id of the client being registered
* @param proxy The <code>CacheClientProxy</code> of the given <code>proxyId</code>
*
* @return CacheClientProxy for the registered client
*/
private CacheClientProxy registerClient(Socket socket, ClientProxyMembershipID proxyId,
CacheClientProxy proxy, boolean isPrimary, byte clientConflation, Version clientVersion,
long acceptorId, boolean notifyBySubscription) throws IOException, CacheException {
CacheClientProxy l_proxy = proxy;
// Initialize the socket
socket.setTcpNoDelay(true);
socket.setSendBufferSize(CacheClientNotifier.socketBufferSize);
socket.setReceiveBufferSize(CacheClientNotifier.socketBufferSize);
if (logger.isDebugEnabled()) {
logger.debug(
"CacheClientNotifier: Initialized server-to-client socket with send buffer size: {} bytes and receive buffer size: {} bytes",
socket.getSendBufferSize(), socket.getReceiveBufferSize());
}
// Determine whether the client is durable or not.
byte responseByte = Acceptor.SUCCESSFUL_SERVER_TO_CLIENT;
String unsuccessfulMsg = null;
boolean successful = true;
boolean clientIsDurable = proxyId.isDurable();
if (logger.isDebugEnabled()) {
if (clientIsDurable) {
logger.debug("CacheClientNotifier: Attempting to register durable client: {}",
proxyId.getDurableId());
} else {
logger.debug("CacheClientNotifier: Attempting to register non-durable client");
}
}
byte epType = 0x00;
int qSize = 0;
if (clientIsDurable) {
if (l_proxy == null) {
if (isTimedOut(proxyId)) {
qSize = PoolImpl.PRIMARY_QUEUE_TIMED_OUT;
} else {
qSize = PoolImpl.PRIMARY_QUEUE_NOT_AVAILABLE;
}
// No proxy exists for this durable client. It must be created.
if (logger.isDebugEnabled()) {
logger.debug(
"CacheClientNotifier: No proxy exists for durable client with id {}. It must be created.",
proxyId.getDurableId());
}
l_proxy = new CacheClientProxy(this, socket, proxyId, isPrimary, clientConflation,
clientVersion, acceptorId, notifyBySubscription);
successful = this.initializeProxy(l_proxy);
} else {
if (proxy.isPrimary()) {
epType = (byte) 2;
} else {
epType = (byte) 1;
}
qSize = proxy.getQueueSize();
// A proxy exists for this durable client. It must be reinitialized.
if (l_proxy.isPaused()) {
if (CacheClientProxy.testHook != null) {
CacheClientProxy.testHook.doTestHook("CLIENT_PRE_RECONNECT");
}
if (l_proxy.lockDrain()) {
try {
if (logger.isDebugEnabled()) {
logger.debug(
"CacheClientNotifier: A proxy exists for durable client with id {}. This proxy will be reinitialized: {}",
proxyId.getDurableId(), l_proxy);
}
this._statistics.incDurableReconnectionCount();
l_proxy.getProxyID().updateDurableTimeout(proxyId.getDurableTimeout());
l_proxy.reinitialize(socket, proxyId, this.getCache(), isPrimary, clientConflation,
clientVersion);
l_proxy.setMarkerEnqueued(true);
if (CacheClientProxy.testHook != null) {
CacheClientProxy.testHook.doTestHook("CLIENT_RECONNECTED");
}
} finally {
l_proxy.unlockDrain();
}
} else {
unsuccessfulMsg =
LocalizedStrings.CacheClientNotifier_COULD_NOT_CONNECT_DUE_TO_CQ_BEING_DRAINED
.toLocalizedString();
logger.warn(unsuccessfulMsg);
responseByte = HandShake.REPLY_REFUSED;
if (CacheClientProxy.testHook != null) {
CacheClientProxy.testHook.doTestHook("CLIENT_REJECTED_DUE_TO_CQ_BEING_DRAINED");
}
}
} else {
// The existing proxy is already running (which means that another
// client is already using this durable id.
unsuccessfulMsg =
LocalizedStrings.CacheClientNotifier_CACHECLIENTNOTIFIER_THE_REQUESTED_DURABLE_CLIENT_HAS_THE_SAME_IDENTIFIER__0__AS_AN_EXISTING_DURABLE_CLIENT__1__DUPLICATE_DURABLE_CLIENTS_ARE_NOT_ALLOWED
.toLocalizedString(new Object[] {proxyId.getDurableId(), proxy});
logger.warn(unsuccessfulMsg);
// Set the unsuccessful response byte.
responseByte = HandShake.REPLY_EXCEPTION_DUPLICATE_DURABLE_CLIENT;
}
}
} else {
CacheClientProxy staleClientProxy = this.getClientProxy(proxyId);
boolean toCreateNewProxy = true;
if (staleClientProxy != null) {
if (staleClientProxy.isConnected() && staleClientProxy.getSocket().isConnected()) {
successful = false;
toCreateNewProxy = false;
} else {
// A proxy exists for this non-durable client. It must be closed.
if (logger.isDebugEnabled()) {
logger.debug(
"CacheClientNotifier: A proxy exists for this non-durable client. It must be closed.");
}
if (staleClientProxy.startRemoval()) {
staleClientProxy.waitRemoval();
} else {
staleClientProxy.close(false, false); // do not check for queue, just close it
removeClientProxy(staleClientProxy); // remove old proxy from proxy set
}
}
} // non-null stale proxy
if (toCreateNewProxy) {
// Create the new proxy for this non-durable client
l_proxy = new CacheClientProxy(this, socket, proxyId, isPrimary, clientConflation,
clientVersion, acceptorId, notifyBySubscription);
successful = this.initializeProxy(l_proxy);
}
}
if (!successful) {
l_proxy = null;
responseByte = HandShake.REPLY_REFUSED;
unsuccessfulMsg =
LocalizedStrings.CacheClientNotifier_CACHECLIENTNOTIFIER_A_PREVIOUS_CONNECTION_ATTEMPT_FROM_THIS_CLIENT_IS_STILL_BEING_PROCESSED__0
.toLocalizedString(new Object[] {proxyId});
}
// Tell the client that the proxy has been registered using the response
// byte. This byte will be read on the client by the CacheClientUpdater to
// determine whether the registration was successful. The times when
// registration is unsuccessful currently are if a duplicate durable client
// is attempted to be registered or authentication fails.
try {
DataOutputStream dos =
new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
// write the message type, message length and the error message (if any)
writeMessage(dos, responseByte, unsuccessfulMsg, clientVersion, epType, qSize);
} catch (IOException ioe) {// remove the added proxy if we get IOException.
if (l_proxy != null) {
boolean keepProxy = l_proxy.close(false, false); // do not check for queue, just close it
if (!keepProxy) {
removeClientProxy(l_proxy);
}
}
throw ioe;
}
if (unsuccessfulMsg != null && logger.isDebugEnabled()) {
logger.debug(unsuccessfulMsg);
}
// If the client is not durable, start its message processor
// Starting it here (instead of in the CacheClientProxy constructor)
// will ensure that the response byte is sent to the client before
// the marker message. If the client is durable, the message processor
// is not started until the clientReady message is received.
if (!clientIsDurable && l_proxy != null
&& responseByte == Acceptor.SUCCESSFUL_SERVER_TO_CLIENT) {
// The startOrResumeMessageDispatcher tests if the proxy is a primary.
// If this is a secondary proxy, the dispatcher is not started.
// The false parameter signifies that a marker message has not already been
// processed. This will generate and send one.
l_proxy.startOrResumeMessageDispatcher(false);
}
if (responseByte == Acceptor.SUCCESSFUL_SERVER_TO_CLIENT) {
if (logger.isDebugEnabled()) {
logger.debug("CacheClientNotifier: Successfully registered {}", l_proxy);
}
} else {
logger.warn(LocalizedMessage.create(
LocalizedStrings.CacheClientNotifier_CACHECLIENTNOTIFIER_UNSUCCESSFULLY_REGISTERED_CLIENT_WITH_IDENTIFIER__0,
proxyId));
}
return l_proxy;
}
private boolean initializeProxy(CacheClientProxy l_proxy) throws IOException, CacheException {
boolean status = false;
if (!this.isProxyInInitializationMode(l_proxy)) {
if (logger.isDebugEnabled()) {
logger.debug("Initializing proxy: {}", l_proxy);
}
try {
// Add client proxy to initialization list. This has to be done before
// the queue is created so that events can be buffered here for delivery
// to the queue once it's initialized (bug #41681 and others)
addClientInitProxy(l_proxy);
l_proxy.initializeMessageDispatcher();
// Initialization success. Add to client proxy list.
addClientProxy(l_proxy);
return true;
} catch (RegionExistsException ree) {
if (logger.isDebugEnabled()) {
String name = ree.getRegion() != null ? ree.getRegion().getFullPath() : "null region";
logger.debug("Found RegionExistsException while initializing proxy. Region name: {}",
name);
}
// This will return false;
} finally {
removeClientInitProxy(l_proxy);
}
}
return status;
}
/**
* Makes Primary to this CacheClientProxy and start the dispatcher of the CacheClientProxy
*
* @param proxyId
* @param isClientReady Whether the marker has already been processed. This value helps determine
* whether to start the dispatcher.
*/
public void makePrimary(ClientProxyMembershipID proxyId, boolean isClientReady) {
CacheClientProxy proxy = getClientProxy(proxyId);
if (proxy != null) {
proxy.setPrimary(true);
/*
* If the client represented by this proxy has: - already processed the marker message
* (meaning the client is failing over to this server as its primary) <or> - is not durable
* (meaning the marker message is being processed automatically
*
* Then, start or resume the dispatcher. Otherwise, let the clientReady message start the
* dispatcher. See CacheClientProxy.startOrResumeMessageDispatcher if
* (!proxy._messageDispatcher.isAlive()) {
*
* proxy._messageDispatcher._messageQueue.setPrimary(true); proxy._messageDispatcher.start();
* }
*/
if (isClientReady || !proxy.isDurable()) {
if (logger.isDebugEnabled()) {
logger.debug("CacheClientNotifier: Notifying proxy to start dispatcher for: {}", proxy);
}
proxy.startOrResumeMessageDispatcher(false);
}
} else {
throw new InternalGemFireError("No cache client proxy on this node for proxyId " + proxyId);
}
}
/**
* Adds or updates entry in the dispatched message map when client sends an ack.
*
* @param proxyId
* @param eid
* @return success
*/
public boolean processDispatchedMessage(ClientProxyMembershipID proxyId, EventID eid) {
boolean success = false;
CacheClientProxy proxy = getClientProxy(proxyId);
if (proxy != null) {
HARegionQueue harq = proxy.getHARegionQueue();
harq.addDispatchedMessage(new ThreadIdentifier(eid.getMembershipID(), eid.getThreadID()),
eid.getSequenceID());
success = true;
}
return success;
}
/**
* Sets keepalive on the proxy of the given membershipID
*
* @param membershipID Uniquely identifies the client pool
* @since GemFire 5.7
*/
public void setKeepAlive(ClientProxyMembershipID membershipID, boolean keepAlive) {
if (logger.isDebugEnabled()) {
logger.debug("CacheClientNotifier: setKeepAlive client: {}", membershipID);
}
CacheClientProxy proxy = getClientProxy(membershipID);
if (proxy != null) {
// Close the port if the proxy represents the client and contains the
// port)
// // If so, remove the port from the client's remote ports
// proxy.removePort(clientPort);
// Set the keepalive flag
proxy.setKeepAlive(keepAlive);
}
}
/**
* Unregisters an existing client from this server.
*
* @param memberId Uniquely identifies the client
*
*
*/
public void unregisterClient(ClientProxyMembershipID memberId, boolean normalShutdown) {
if (logger.isDebugEnabled()) {
logger.debug("CacheClientNotifier: Unregistering all clients with member id: {}", memberId);
}
CacheClientProxy proxy = getClientProxy(memberId);
if (proxy != null) {
final boolean isTraceEnabled = logger.isTraceEnabled();
if (isTraceEnabled) {
logger.trace("CacheClientNotifier: Potential client: {}", proxy);
}
// If the proxy's member id is the same as the input member id, add
// it to the set of dead proxies.
if (!proxy.startRemoval()) {
if (isTraceEnabled) {
logger.trace("CacheClientNotifier: Potential client: {} matches {}", proxy, memberId);
}
closeDeadProxies(Collections.singletonList(proxy), normalShutdown);
}
}
}
/**
* The client represented by the proxyId is ready to receive updates.
*
* @param proxyId
*/
public void readyForEvents(ClientProxyMembershipID proxyId) {
CacheClientProxy proxy = getClientProxy(proxyId);
if (proxy == null) {
// @todo log a message
} else {
// False signifies that a marker message has not already been processed.
// Generate and send one.
proxy.startOrResumeMessageDispatcher(false);
}
}
private ClientUpdateMessageImpl constructClientMessage(InternalCacheEvent event) {
ClientUpdateMessageImpl clientMessage = null;
EnumListenerEvent operation = event.getEventType();
try {
clientMessage = initializeMessage(operation, event);
} catch (Exception e) {
logger.fatal(LocalizedMessage.create(
LocalizedStrings.CacheClientNotifier_CANNOT_NOTIFY_CLIENTS_TO_PERFORM_OPERATION_0_ON_EVENT_1,
new Object[] {operation, event}), e);
}
return clientMessage;
}
/**
* notify interested clients of the given cache event. The event should have routing information
* in it that determines which clients will receive the event.
*/
public static void notifyClients(InternalCacheEvent event) {
CacheClientNotifier instance = ccnSingleton;
if (instance != null) {
instance.singletonNotifyClients(event, null);
}
}
/**
* notify interested clients of the given cache event using the given update message. The event
* should have routing information in it that determines which clients will receive the event.
*/
public static void notifyClients(InternalCacheEvent event, ClientUpdateMessage cmsg) {
CacheClientNotifier instance = ccnSingleton;
if (instance != null) {
instance.singletonNotifyClients(event, cmsg);
}
}
private void singletonNotifyClients(InternalCacheEvent event, ClientUpdateMessage cmsg) {
final boolean isDebugEnabled = logger.isDebugEnabled();
final boolean isTraceEnabled = logger.isTraceEnabled();
FilterInfo filterInfo = event.getLocalFilterInfo();
// if (_logger.fineEnabled()) {
// _logger.fine("Client dispatcher processing event " + event);
// }
FilterProfile regionProfile = ((LocalRegion) event.getRegion()).getFilterProfile();
if (filterInfo != null) {
// if the routing was made using an old profile we need to recompute it
if (isTraceEnabled) {
logger.trace("Event isOriginRemote={}", event.isOriginRemote());
}
}
if ((filterInfo == null
|| (filterInfo.getCQs() == null && filterInfo.getInterestedClients() == null
&& filterInfo.getInterestedClientsInv() == null))) {
return;
}
long startTime = this._statistics.startTime();
ClientUpdateMessageImpl clientMessage;
if (cmsg == null) {
clientMessage = constructClientMessage(event);
} else {
clientMessage = (ClientUpdateMessageImpl) cmsg;
}
if (clientMessage == null) {
return;
}
// Holds the clientIds to which filter message needs to be sent.
Set<ClientProxyMembershipID> filterClients = new HashSet();
// Add CQ info.
if (filterInfo.getCQs() != null) {
for (Map.Entry<Long, Integer> e : filterInfo.getCQs().entrySet()) {
Long cqID = e.getKey();
String cqName = regionProfile.getRealCqID(cqID);
if (cqName == null) {
continue;
}
ServerCQ cq = regionProfile.getCq(cqName);
if (cq != null) {
ClientProxyMembershipID id = cq.getClientProxyId();
filterClients.add(id);
if (isDebugEnabled) {
logger.debug("Adding cq routing info to message for id: {} and cq: {}", id, cqName);
}
clientMessage.addClientCq(id, cq.getName(), e.getValue());
}
}
}
// Add interestList info.
if (filterInfo.getInterestedClientsInv() != null) {
Set<Object> rawIDs = regionProfile.getRealClientIDs(filterInfo.getInterestedClientsInv());
Set<ClientProxyMembershipID> ids = getProxyIDs(rawIDs, true);
if (ids.remove(event.getContext())) { // don't send to member of origin
CacheClientProxy ccp = getClientProxy(event.getContext());
if (ccp != null) {
ccp.getStatistics().incMessagesNotQueuedOriginator();
}
}
if (!ids.isEmpty()) {
if (isTraceEnabled) {
logger.trace("adding invalidation routing to message for {}" + ids);
}
clientMessage.addClientInterestList(ids, false);
filterClients.addAll(ids);
}
}
if (filterInfo.getInterestedClients() != null) {
Set<Object> rawIDs = regionProfile.getRealClientIDs(filterInfo.getInterestedClients());
Set<ClientProxyMembershipID> ids = getProxyIDs(rawIDs, true);
if (ids.remove(event.getContext())) { // don't send to member of origin
CacheClientProxy ccp = getClientProxy(event.getContext());
if (ccp != null) {
ccp.getStatistics().incMessagesNotQueuedOriginator();
}
}
if (!ids.isEmpty()) {
if (isTraceEnabled) {
logger.trace("adding routing to message for {}", ids);
}
clientMessage.addClientInterestList(ids, true);
filterClients.addAll(ids);
}
}
Conflatable conflatable = null;
if (clientMessage instanceof ClientTombstoneMessage) {
// bug #46832 - HAEventWrapper deserialization can't handle subclasses
// of ClientUpdateMessageImpl, so don't wrap them
conflatable = clientMessage;
// Remove clients older than 70 from the filterClients if the message is
// ClientTombstoneMessage. Fix for #46591.
Object[] objects = filterClients.toArray();
for (Object id : objects) {
CacheClientProxy ccp = getClientProxy((ClientProxyMembershipID) id, true);
if (ccp != null && ccp.getVersion().compareTo(Version.GFE_70) < 0) {
filterClients.remove(id);
}
}
} else {
HAEventWrapper wrapper = new HAEventWrapper(clientMessage);
// Set the putInProgress flag to true before starting the put on proxy's
// HA queues. Nowhere else, this flag is being set to true.
wrapper.setPutInProgress(true);
conflatable = wrapper;
}
singletonRouteClientMessage(conflatable, filterClients);
this._statistics.endEvent(startTime);
// Cleanup destroyed events in CQ result cache.
// While maintaining the CQ results key caching. the destroy event
// keys are marked as destroyed instead of removing them, this is
// to take care, arrival of duplicate events. The key marked as
// destroyed are removed after the event is placed in clients HAQueue.
if (filterInfo.filterProcessedLocally) {
removeDestroyTokensFromCqResultKeys(event, filterInfo);
}
}
private void removeDestroyTokensFromCqResultKeys(InternalCacheEvent event,
FilterInfo filterInfo) {
FilterProfile regionProfile = ((LocalRegion) event.getRegion()).getFilterProfile();
if (event.getOperation().isEntry() && filterInfo.getCQs() != null) {
EntryEventImpl entryEvent = (EntryEventImpl) event;
for (Map.Entry<Long, Integer> e : filterInfo.getCQs().entrySet()) {
Long cqID = e.getKey();
String cqName = regionProfile.getRealCqID(cqID);
if (cqName != null) {
ServerCQ cq = regionProfile.getCq(cqName);
if (cq != null && e.getValue().equals(Integer.valueOf(MessageType.LOCAL_DESTROY))) {
cq.removeFromCqResultKeys(entryEvent.getKey(), true);
}
}
}
}
}
/**
* delivers the given message to all proxies for routing. The message should already have client
* interest established, or override the isClientInterested method to implement its own routing
*
* @param clientMessage
*/
public static void routeClientMessage(Conflatable clientMessage) {
CacheClientNotifier instance = ccnSingleton;
if (instance != null) {
instance.singletonRouteClientMessage(clientMessage, instance._clientProxies.keySet()); // ok
// to
// use
// keySet
// here
// because
// all
// we
// do
// is
// call
// getClientProxy
// with
// these
// keys
}
}
/*
* this is for server side registration of client queue
*/
public static void routeSingleClientMessage(ClientUpdateMessage clientMessage,
ClientProxyMembershipID clientProxyMembershipId) {
CacheClientNotifier instance = ccnSingleton;
if (instance != null) {
instance.singletonRouteClientMessage(clientMessage,
Collections.singleton(clientProxyMembershipId));
}
}
private void singletonRouteClientMessage(Conflatable conflatable,
Collection<ClientProxyMembershipID> filterClients) {
this._cache.getCancelCriterion().checkCancelInProgress(null); // bug #43942 - client notified
// but no p2p distribution
List<CacheClientProxy> deadProxies = null;
for (ClientProxyMembershipID clientId : filterClients) {
CacheClientProxy proxy;
proxy = this.getClientProxy(clientId, true);
if (proxy != null) {
if (proxy.isAlive() || proxy.isPaused() || proxy.isConnected() || proxy.isDurable()) {
proxy.deliverMessage(conflatable);
} else {
proxy.getStatistics().incMessagesFailedQueued();
if (deadProxies == null) {
deadProxies = new ArrayList<CacheClientProxy>();
}
deadProxies.add(proxy);
}
this.blackListSlowReciever(proxy);
}
}
checkAndRemoveFromClientMsgsRegion(conflatable);
// Remove any dead clients from the clients to notify
if (deadProxies != null) {
closeDeadProxies(deadProxies, false);
}
}
/**
* processes the given collection of durable and non-durable client identifiers, returning a
* collection of non-durable identifiers of clients connected to this VM
*/
public Set<ClientProxyMembershipID> getProxyIDs(Set mixedDurableAndNonDurableIDs) {
return getProxyIDs(mixedDurableAndNonDurableIDs, false);
}
/**
* processes the given collection of durable and non-durable client identifiers, returning a
* collection of non-durable identifiers of clients connected to this VM. This version can check
* for proxies in initialization as well as fully initialized proxies.
*/
public Set<ClientProxyMembershipID> getProxyIDs(Set mixedDurableAndNonDurableIDs,
boolean proxyInInitMode) {
Set<ClientProxyMembershipID> result = new HashSet();
for (Object id : mixedDurableAndNonDurableIDs) {
if (id instanceof String) {
CacheClientProxy clientProxy = getClientProxy((String) id, true);
if (clientProxy != null) {
result.add(clientProxy.getProxyID());
}
// else { we don't have a proxy for the given durable ID }
} else {
// try to canonicalize the ID.
CacheClientProxy proxy = getClientProxy((ClientProxyMembershipID) id, true);
if (proxy != null) {
// this._logger.info(LocalizedStrings.DEBUG, "BRUCE: found match for " + id + ": " +
// proxy.getProxyID());
result.add(proxy.getProxyID());
} else {
// this._logger.info(LocalizedStrings.DEBUG, "BRUCE: did not find match for " + id);
// this was causing OOMEs in HARegion initial image processing because
// messages had routing for clients unknown to this server
// result.add((ClientProxyMembershipID)id);
}
}
}
return result;
}
private void blackListSlowReciever(CacheClientProxy clientProxy) {
final CacheClientProxy proxy = clientProxy;
if ((proxy.getHARegionQueue() != null && proxy.getHARegionQueue().isClientSlowReciever())
&& !blackListedClients.contains(proxy.getProxyID())) {
// log alert with client info.
logger.warn(
LocalizedMessage.create(LocalizedStrings.CacheClientNotifier_CLIENT_0_IS_A_SLOW_RECEIVER,
new Object[] {proxy.getProxyID()}));
addToBlacklistedClient(proxy.getProxyID());
InternalDistributedSystem ids =
(InternalDistributedSystem) this.getCache().getDistributedSystem();
final DM dm = ids.getDistributionManager();
dm.getWaitingThreadPool().execute(new Runnable() {
public void run() {
CacheDistributionAdvisor advisor =
((DistributedRegion) proxy.getHARegionQueue().getRegion())
.getCacheDistributionAdvisor();
Set members = advisor.adviseCacheOp();
// Send client blacklist message
ClientBlacklistProcessor.sendBlacklistedClient(proxy.getProxyID(), dm, members);
// close the proxy for slow receiver.
proxy.close(false, false);
removeClientProxy(proxy);
if (PoolImpl.AFTER_QUEUE_DESTROY_MESSAGE_FLAG) {
ClientServerObserver bo = ClientServerObserverHolder.getInstance();
bo.afterQueueDestroyMessage();
}
// send remove from blacklist.
RemoveClientFromBlacklistMessage rcm = new RemoveClientFromBlacklistMessage();
rcm.setProxyID(proxy.getProxyID());
dm.putOutgoing(rcm);
blackListedClients.remove(proxy.getProxyID());
}
});
}
}
/**
* Initializes a <code>ClientUpdateMessage</code> from an operation and event
*
* @param operation The operation that occurred (e.g. AFTER_CREATE)
* @param event The event containing the data to be updated
* @return a <code>ClientUpdateMessage</code>
* @throws Exception
*/
private ClientUpdateMessageImpl initializeMessage(EnumListenerEvent operation, CacheEvent event)
throws Exception {
if (!supportsOperation(operation)) {
throw new Exception(
LocalizedStrings.CacheClientNotifier_THE_CACHE_CLIENT_NOTIFIER_DOES_NOT_SUPPORT_OPERATIONS_OF_TYPE_0
.toLocalizedString(operation));
}
// String regionName = event.getRegion().getFullPath();
Object keyOfInterest = null;
final EventID eventIdentifier;
ClientProxyMembershipID membershipID = null;
boolean isNetLoad = false;
Object callbackArgument = null;
byte[] delta = null;
VersionTag versionTag = null;
if (event.getOperation().isEntry()) {
EntryEventImpl entryEvent = (EntryEventImpl) event;
versionTag = entryEvent.getVersionTag();
delta = entryEvent.getDeltaBytes();
callbackArgument = entryEvent.getRawCallbackArgument();
if (entryEvent.isBridgeEvent()) {
membershipID = entryEvent.getContext();
}
keyOfInterest = entryEvent.getKey();
eventIdentifier = entryEvent.getEventId();
isNetLoad = entryEvent.isNetLoad();
} else {
RegionEventImpl regionEvent = (RegionEventImpl) event;
callbackArgument = regionEvent.getRawCallbackArgument();
eventIdentifier = regionEvent.getEventId();
if (event instanceof ClientRegionEventImpl) {
ClientRegionEventImpl bridgeEvent = (ClientRegionEventImpl) event;
membershipID = bridgeEvent.getContext();
}
}
// NOTE: If delta is non-null, value MUST be in Object form of type Delta.
ClientUpdateMessageImpl clientUpdateMsg =
new ClientUpdateMessageImpl(operation, (LocalRegion) event.getRegion(), keyOfInterest, null,
delta, (byte) 0x01, callbackArgument, membershipID, eventIdentifier, versionTag);
if (event.getOperation().isEntry()) {
EntryEventImpl entryEvent = (EntryEventImpl) event;
// only need a value if notifyBySubscription is true
entryEvent.exportNewValue(clientUpdateMsg);
}
if (isNetLoad) {
clientUpdateMsg.setIsNetLoad(isNetLoad);
}
return clientUpdateMsg;
}
/**
* Returns whether the <code>CacheClientNotifier</code> supports the input operation.
*
* @param operation The operation that occurred (e.g. AFTER_CREATE)
* @return whether the <code>CacheClientNotifier</code> supports the input operation
*/
protected boolean supportsOperation(EnumListenerEvent operation) {
return operation == EnumListenerEvent.AFTER_CREATE
|| operation == EnumListenerEvent.AFTER_UPDATE
|| operation == EnumListenerEvent.AFTER_DESTROY
|| operation == EnumListenerEvent.AFTER_INVALIDATE
|| operation == EnumListenerEvent.AFTER_REGION_DESTROY
|| operation == EnumListenerEvent.AFTER_REGION_CLEAR
|| operation == EnumListenerEvent.AFTER_REGION_INVALIDATE;
}
// /**
// * Queues the <code>ClientUpdateMessage</code> to be distributed
// * to interested clients. This method is not being used currently.
// * @param clientMessage The <code>ClientUpdateMessage</code> to be queued
// */
// protected void notifyClients(final ClientUpdateMessage clientMessage)
// {
// if (USE_SYNCHRONOUS_NOTIFICATION)
// {
// // Execute the method in the same thread as the caller
// deliver(clientMessage);
// }
// else {
// // Obtain an Executor and use it to execute the method in its own thread
// try
// {
// getExecutor().execute(new Runnable()
// {
// public void run()
// {
// deliver(clientMessage);
// }
// }
// );
// } catch (InterruptedException e)
// {
// _logger.warning("CacheClientNotifier: notifyClients interrupted", e);
// Thread.currentThread().interrupt();
// }
// }
// }
// /**
// * Updates the information this <code>CacheClientNotifier</code> maintains
// * for a given edge client. It is invoked when a edge client re-connects to
// * the server.
// *
// * @param clientHost
// * The host on which the client runs (i.e. the host the
// * CacheClientNotifier uses to communicate with the
// * CacheClientUpdater) This is used with the clientPort to uniquely
// * identify the client
// * @param clientPort
// * The port through which the server communicates with the client
// * (i.e. the port the CacheClientNotifier uses to communicate with
// * the CacheClientUpdater) This is used with the clientHost to
// * uniquely identify the client
// * @param remotePort
// * The port through which the client communicates with the server
// * (i.e. the new port the ConnectionImpl uses to communicate with the
// * ServerConnection)
// * @param membershipID
// * Uniquely idenifies the client
// */
// public void registerClientPort(String clientHost, int clientPort,
// int remotePort, ClientProxyMembershipID membershipID)
// {
// if (_logger.fineEnabled())
// _logger.fine("CacheClientNotifier: Registering client port: "
// + clientHost + ":" + clientPort + " with remote port " + remotePort
// + " and ID " + membershipID);
// for (Iterator i = getClientProxies().iterator(); i.hasNext();) {
// CacheClientProxy proxy = (CacheClientProxy)i.next();
// if (_logger.finerEnabled())
// _logger.finer("CacheClientNotifier: Potential client: " + proxy);
// //if (proxy.representsCacheClientUpdater(clientHost, clientPort))
// if (proxy.isMember(membershipID)) {
// if (_logger.finerEnabled())
// _logger
// .finer("CacheClientNotifier: Updating remotePorts since host and port are a match");
// proxy.addPort(remotePort);
// }
// else {
// if (_logger.finerEnabled())
// _logger.finer("CacheClientNotifier: Host and port "
// + proxy.getRemoteHostAddress() + ":" + proxy.getRemotePort()
// + " do not match " + clientHost + ":" + clientPort);
// }
// }
// }
/**
* Registers client interest in the input region and key.
*
* @param regionName The name of the region of interest
* @param keyOfInterest The name of the key of interest
* @param membershipID clients ID
* @param interestType type of registration
* @param isDurable whether the registration persists when client goes away
* @param sendUpdatesAsInvalidates client wants invalidation messages
* @param manageEmptyRegions whether to book keep empty region information
* @param regionDataPolicy (0=empty)
*/
public void registerClientInterest(String regionName, Object keyOfInterest,
ClientProxyMembershipID membershipID, int interestType, boolean isDurable,
boolean sendUpdatesAsInvalidates, boolean manageEmptyRegions, int regionDataPolicy,
boolean flushState) throws IOException, RegionDestroyedException {
CacheClientProxy proxy = getClientProxy(membershipID, true);
if (logger.isDebugEnabled()) {
logger.debug(
"CacheClientNotifier: Client {} registering interest in: {} -> {} (an instance of {})",
proxy, regionName, keyOfInterest, keyOfInterest.getClass().getName());
}
if (proxy == null) {
// client should see this and initiates failover
throw new IOException(
LocalizedStrings.CacheClientNotifier_CACHECLIENTPROXY_FOR_THIS_CLIENT_IS_NO_LONGER_ON_THE_SERVER_SO_REGISTERINTEREST_OPERATION_IS_UNSUCCESSFUL
.toLocalizedString());
}
boolean done = false;
try {
proxy.registerClientInterest(regionName, keyOfInterest, interestType, isDurable,
sendUpdatesAsInvalidates, flushState);
if (manageEmptyRegions) {
updateMapOfEmptyRegions(proxy.getRegionsWithEmptyDataPolicy(), regionName,
regionDataPolicy);
}
done = true;
} finally {
if (!done) {
proxy.unregisterClientInterest(regionName, keyOfInterest, interestType, false);
}
}
}
/*
* protected void addFilterRegisteredClients(String regionName, ClientProxyMembershipID
* membershipID) throws RegionNotFoundException { // Update Regions book keeping. LocalRegion
* region = (LocalRegion)this._cache.getRegion(regionName); if (region == null) { //throw new
* AssertionError("Could not find region named '" + regionName + "'"); // @todo: see bug 36805 //
* fix for bug 37979 if (_logger.fineEnabled()) { _logger .fine("CacheClientNotifier: Client " +
* membershipID + " :Throwing RegionDestroyedException as region: " + regionName +
* " is not present."); } throw new RegionDestroyedException("registerInterest failed",
* regionName); } else { region.getFilterProfile().addFilterRegisteredClients(this, membershipID);
* } }
*/
/**
* Store region and delta relation
*
* @param regionsWithEmptyDataPolicy
* @param regionName
* @param regionDataPolicy (0==empty)
* @since GemFire 6.1
*/
public void updateMapOfEmptyRegions(Map regionsWithEmptyDataPolicy, String regionName,
int regionDataPolicy) {
if (regionDataPolicy == 0) {
if (!regionsWithEmptyDataPolicy.containsKey(regionName)) {
regionsWithEmptyDataPolicy.put(regionName, Integer.valueOf(0));
}
}
}
/**
* Unregisters client interest in the input region and key.
*
* @param regionName The name of the region of interest
* @param keyOfInterest The name of the key of interest
* @param isClosing Whether the caller is closing
* @param membershipID The <code>ClientProxyMembershipID</code> of the client no longer interested
* in this <code>Region</code> and key
*/
public void unregisterClientInterest(String regionName, Object keyOfInterest, int interestType,
boolean isClosing, ClientProxyMembershipID membershipID, boolean keepalive) {
if (logger.isDebugEnabled()) {
logger.debug(
"CacheClientNotifier: Client {} unregistering interest in: {} -> {} (an instance of {})",
membershipID, regionName, keyOfInterest, keyOfInterest.getClass().getName());
}
CacheClientProxy proxy = getClientProxy(membershipID);
if (proxy != null) {
proxy.setKeepAlive(keepalive);
proxy.unregisterClientInterest(regionName, keyOfInterest, interestType, isClosing);
}
}
/**
* Registers client interest in the input region and list of keys.
*
* @param regionName The name of the region of interest
* @param keysOfInterest The list of keys of interest
* @param membershipID The <code>ClientProxyMembershipID</code> of the client no longer interested
* in this <code>Region</code> and key
*/
public void registerClientInterest(String regionName, List keysOfInterest,
ClientProxyMembershipID membershipID, boolean isDurable, boolean sendUpdatesAsInvalidates,
boolean manageEmptyRegions, int regionDataPolicy, boolean flushState)
throws IOException, RegionDestroyedException {
CacheClientProxy proxy = getClientProxy(membershipID, true);
if (logger.isDebugEnabled()) {
logger.debug("CacheClientNotifier: Client {} registering interest in: {} -> {}", proxy,
regionName, keysOfInterest);
}
if (proxy == null) {
throw new IOException(
LocalizedStrings.CacheClientNotifier_CACHECLIENTPROXY_FOR_THIS_CLIENT_IS_NO_LONGER_ON_THE_SERVER_SO_REGISTERINTEREST_OPERATION_IS_UNSUCCESSFUL
.toLocalizedString());
}
proxy.registerClientInterestList(regionName, keysOfInterest, isDurable,
sendUpdatesAsInvalidates, flushState);
if (manageEmptyRegions) {
updateMapOfEmptyRegions(proxy.getRegionsWithEmptyDataPolicy(), regionName, regionDataPolicy);
}
}
/**
* Unregisters client interest in the input region and list of keys.
*
* @param regionName The name of the region of interest
* @param keysOfInterest The list of keys of interest
* @param isClosing Whether the caller is closing
* @param membershipID The <code>ClientProxyMembershipID</code> of the client no longer interested
* in this <code>Region</code> and key
*/
public void unregisterClientInterest(String regionName, List keysOfInterest, boolean isClosing,
ClientProxyMembershipID membershipID, boolean keepalive) {
if (logger.isDebugEnabled()) {
logger.debug("CacheClientNotifier: Client {} unregistering interest in: {} -> {}",
membershipID, regionName, keysOfInterest);
}
CacheClientProxy proxy = getClientProxy(membershipID);
if (proxy != null) {
proxy.setKeepAlive(keepalive);
proxy.unregisterClientInterest(regionName, keysOfInterest, isClosing);
}
}
/**
* If the conflatable is an instance of HAEventWrapper, and if the corresponding entry is present
* in the haContainer, set the reference to the clientUpdateMessage to null and putInProgress flag
* to false. Also, if the ref count is zero, then remove the entry from the haContainer.
*
* @param conflatable
* @since GemFire 5.7
*/
private void checkAndRemoveFromClientMsgsRegion(Conflatable conflatable) {
if (haContainer == null) {
return;
}
if (conflatable instanceof HAEventWrapper) {
HAEventWrapper wrapper = (HAEventWrapper) conflatable;
if (!wrapper.getIsRefFromHAContainer()) {
wrapper = (HAEventWrapper) haContainer.getKey(wrapper);
if (wrapper != null && !wrapper.getPutInProgress()) {
synchronized (wrapper) {
if (wrapper.getReferenceCount() == 0L) {
if (logger.isDebugEnabled()) {
logger.debug("Removing event from haContainer: {}", wrapper);
}
haContainer.remove(wrapper);
}
}
}
// else {
// This is a replay-of-event case.
// }
} else {
// This wrapper resides in haContainer.
wrapper.setClientUpdateMessage(null);
wrapper.setPutInProgress(false);
synchronized (wrapper) {
if (wrapper.getReferenceCount() == 0L) {
if (logger.isDebugEnabled()) {
logger.debug("Removing event from haContainer: {}", wrapper);
}
haContainer.remove(wrapper);
}
}
}
}
}
/**
* Returns the <code>CacheClientProxy</code> associated to the membershipID *
*
* @return the <code>CacheClientProxy</code> associated to the membershipID
*/
public CacheClientProxy getClientProxy(ClientProxyMembershipID membershipID) {
return (CacheClientProxy) this._clientProxies.get(membershipID);
}
/**
* Returns the CacheClientProxy associated to the membershipID. This looks at both proxies that
* are initialized and those that are still in initialization mode.
*/
public CacheClientProxy getClientProxy(ClientProxyMembershipID membershipID,
boolean proxyInInitMode) {
CacheClientProxy proxy = getClientProxy(membershipID);
if (proxyInInitMode && proxy == null) {
proxy = (CacheClientProxy) this._initClientProxies.get(membershipID);
}
return proxy;
}
/**
* Returns the <code>CacheClientProxy</code> associated to the durableClientId
*
* @return the <code>CacheClientProxy</code> associated to the durableClientId
*/
public CacheClientProxy getClientProxy(String durableClientId) {
return getClientProxy(durableClientId, false);
}
/**
* Returns the <code>CacheClientProxy</code> associated to the durableClientId. This version of
* the method can check for initializing proxies as well as fully initialized proxies.
*
* @return the <code>CacheClientProxy</code> associated to the durableClientId
*/
public CacheClientProxy getClientProxy(String durableClientId, boolean proxyInInitMode) {
final boolean isDebugEnabled = logger.isDebugEnabled();
final boolean isTraceEnabled = logger.isTraceEnabled();
if (isDebugEnabled) {
logger.debug("CacheClientNotifier: Determining client for {}", durableClientId);
}
CacheClientProxy proxy = null;
for (Iterator i = getClientProxies().iterator(); i.hasNext();) {
CacheClientProxy clientProxy = (CacheClientProxy) i.next();
if (isTraceEnabled) {
logger.trace("CacheClientNotifier: Checking client {}", clientProxy);
}
if (clientProxy.getDurableId().equals(durableClientId)) {
proxy = clientProxy;
if (isDebugEnabled) {
logger.debug("CacheClientNotifier: {} represents the durable client {}", proxy,
durableClientId);
}
break;
}
}
if (proxy == null && proxyInInitMode) {
for (Iterator i = this._initClientProxies.values().iterator(); i.hasNext();) {
CacheClientProxy clientProxy = (CacheClientProxy) i.next();
if (isTraceEnabled) {
logger.trace("CacheClientNotifier: Checking initializing client {}", clientProxy);
}
if (clientProxy.getDurableId().equals(durableClientId)) {
proxy = clientProxy;
if (isDebugEnabled) {
logger.debug(
"CacheClientNotifier: initializing client {} represents the durable client {}",
proxy, durableClientId);
}
break;
}
}
}
return proxy;
}
/**
* Returns the <code>CacheClientProxySameDS</code> associated to the membershipID *
*
* @return the <code>CacheClientProxy</code> associated to the same distributed system
*/
public CacheClientProxy getClientProxySameDS(ClientProxyMembershipID membershipID) {
final boolean isDebugEnabled = logger.isDebugEnabled();
if (isDebugEnabled) {
logger.debug("{}::getClientProxySameDS(), Determining client for host {}", this,
membershipID);
logger.debug("{}::getClientProxySameDS(), Number of proxies in the Cache Clinet Notifier: {}",
this, getClientProxies().size());
/*
* _logger.fine(this + "::getClientProxySameDS(), Proxies in the Cache Clinet Notifier: " +
* getClientProxies());
*/
}
CacheClientProxy proxy = null;
for (Iterator i = getClientProxies().iterator(); i.hasNext();) {
CacheClientProxy clientProxy = (CacheClientProxy) i.next();
if (isDebugEnabled) {
logger.debug("CacheClientNotifier: Checking client {}", clientProxy);
}
if (clientProxy.isSameDSMember(membershipID)) {
proxy = clientProxy;
if (isDebugEnabled) {
logger.debug("CacheClientNotifier: {} represents the client running on host {}", proxy,
membershipID);
}
break;
}
}
return proxy;
}
/**
* It will remove the clients connected to the passed acceptorId. If its the only server, shuts
* down this instance.
*/
protected synchronized void shutdown(long acceptorId) {
final boolean isDebugEnabled = logger.isDebugEnabled();
if (isDebugEnabled) {
logger.debug("At cache server shutdown time, the number of cache servers in the cache is {}",
this.getCache().getCacheServers().size());
}
Iterator it = this._clientProxies.values().iterator();
// Close all the client proxies
while (it.hasNext()) {
CacheClientProxy proxy = (CacheClientProxy) it.next();
if (proxy.getAcceptorId() != acceptorId) {
continue;
}
it.remove();
try {
if (isDebugEnabled) {
logger.debug("CacheClientNotifier: Closing {}", proxy);
}
proxy.terminateDispatching(true);
} catch (Exception ignore) {
if (isDebugEnabled) {
logger.debug("{}: Exception in closing down the CacheClientProxy", this, ignore);
}
}
}
if (noActiveServer() && ccnSingleton != null) {
ccnSingleton = null;
if (haContainer != null) {
haContainer.cleanUp();
if (isDebugEnabled) {
logger.debug("haContainer ({}) is now cleaned up.", haContainer.getName());
}
}
this.clearCompiledQueries();
blackListedClients.clear();
// cancel the ping task
this.clientPingTask.cancel();
// Close the statistics
this._statistics.close();
this.socketCloser.close();
}
}
private boolean noActiveServer() {
for (CacheServer server : this.getCache().getCacheServers()) {
if (server.isRunning()) {
return false;
}
}
return true;
}
/**
* Adds a new <code>CacheClientProxy</code> to the list of known client proxies
*
* @param proxy The <code>CacheClientProxy</code> to add
*/
protected void addClientProxy(CacheClientProxy proxy) throws IOException {
// this._logger.info(LocalizedStrings.DEBUG, "adding client proxy " + proxy);
getCache(); // ensure cache reference is up to date so firstclient state is correct
this._clientProxies.put(proxy.getProxyID(), proxy);
// Remove this proxy from the init proxy list.
removeClientInitProxy(proxy);
this._connectionListener.queueAdded(proxy.getProxyID());
if (!(proxy.clientConflation == HandShake.CONFLATION_ON)) {
// Delta not supported with conflation ON
ClientHealthMonitor chm = ClientHealthMonitor.getInstance();
/*
* #41788 - If the client connection init starts while cache/member is shutting down,
* ClientHealthMonitor.getInstance() might return null.
*/
if (chm != null) {
chm.numOfClientsPerVersion.incrementAndGet(proxy.getVersion().ordinal());
}
}
this.timedOutDurableClientProxies.remove(proxy.getProxyID());
}
protected void addClientInitProxy(CacheClientProxy proxy) throws IOException {
this._initClientProxies.put(proxy.getProxyID(), proxy);
}
protected void removeClientInitProxy(CacheClientProxy proxy) throws IOException {
this._initClientProxies.remove(proxy.getProxyID());
}
protected boolean isProxyInInitializationMode(CacheClientProxy proxy) throws IOException {
return this._initClientProxies.containsKey(proxy.getProxyID());
}
/**
* Returns (possibly stale) set of memberIds for all clients being actively notified by this
* server.
*
* @return set of memberIds
*/
public Set getActiveClients() {
Set clients = new HashSet();
for (Iterator iter = getClientProxies().iterator(); iter.hasNext();) {
CacheClientProxy proxy = (CacheClientProxy) iter.next();
if (proxy.hasRegisteredInterested()) {
ClientProxyMembershipID proxyID = proxy.getProxyID();
clients.add(proxyID);
}
}
return clients;
}
/**
* Return (possibly stale) list of all clients and their status
*
* @return Map, with CacheClientProxy as a key and CacheClientStatus as a value
*/
public Map getAllClients() {
Map clients = new HashMap();
for (Iterator iter = this._clientProxies.values().iterator(); iter.hasNext();) {
CacheClientProxy proxy = (CacheClientProxy) iter.next();
ClientProxyMembershipID proxyID = proxy.getProxyID();
clients.put(proxyID, new CacheClientStatus(proxyID));
}
return clients;
}
/**
* Checks if there is any proxy present for the given durable client
*
* @param durableId - id for the durable-client
* @return - true if a proxy is present for the given durable client
*
* @since GemFire 5.6
*/
public boolean hasDurableClient(String durableId) {
for (Iterator iter = this._clientProxies.values().iterator(); iter.hasNext();) {
CacheClientProxy proxy = (CacheClientProxy) iter.next();
ClientProxyMembershipID proxyID = proxy.getProxyID();
if (durableId.equals(proxyID.getDurableId())) {
return true;
}
}
return false;
}
/**
* Checks if there is any proxy which is primary for the given durable client
*
* @param durableId - id for the durable-client
* @return - true if a primary proxy is present for the given durable client
*
* @since GemFire 5.6
*/
public boolean hasPrimaryForDurableClient(String durableId) {
for (Iterator iter = this._clientProxies.values().iterator(); iter.hasNext();) {
CacheClientProxy proxy = (CacheClientProxy) iter.next();
ClientProxyMembershipID proxyID = proxy.getProxyID();
if (durableId.equals(proxyID.getDurableId())) {
if (proxy.isPrimary()) {
return true;
} else {
return false;
}
}
}
return false;
}
/**
* Returns (possibly stale) map of queue sizes for all clients notified by this server.
*
* @return map with CacheClientProxy as key, and Integer as a value
*/
public Map getClientQueueSizes() {
Map/* <ClientProxyMembershipID,Integer> */ queueSizes = new HashMap();
for (Iterator iter = this._clientProxies.values().iterator(); iter.hasNext();) {
CacheClientProxy proxy = (CacheClientProxy) iter.next();
queueSizes.put(proxy.getProxyID(), Integer.valueOf(proxy.getQueueSize()));
}
return queueSizes;
}
public int getDurableClientHAQueueSize(String durableClientId) {
CacheClientProxy ccp = getClientProxy(durableClientId);
if (ccp == null) {
return -1;
}
return ccp.getQueueSizeStat();
}
// closes the cq and drains the queue
public boolean closeClientCq(String durableClientId, String clientCQName) throws CqException {
CacheClientProxy proxy = getClientProxy(durableClientId);
// close and drain
if (proxy != null) {
return proxy.closeClientCq(clientCQName);
}
return false;
}
/**
* Removes an existing <code>CacheClientProxy</code> from the list of known client proxies
*
* @param proxy The <code>CacheClientProxy</code> to remove
*/
protected void removeClientProxy(CacheClientProxy proxy) {
// this._logger.info(LocalizedStrings.DEBUG, "removing client proxy " + proxy, new
// Exception("stack trace"));
ClientProxyMembershipID client = proxy.getProxyID();
this._clientProxies.remove(client);
this._connectionListener.queueRemoved();
((GemFireCacheImpl) this.getCache()).cleanupForClient(this, client);
if (!(proxy.clientConflation == HandShake.CONFLATION_ON)) {
ClientHealthMonitor chm = ClientHealthMonitor.getInstance();
if (chm != null) {
chm.numOfClientsPerVersion.decrementAndGet(proxy.getVersion().ordinal());
}
}
}
void durableClientTimedOut(ClientProxyMembershipID client) {
this.timedOutDurableClientProxies.add(client);
}
public boolean isTimedOut(ClientProxyMembershipID client) {
return this.timedOutDurableClientProxies.contains(client);
}
/**
* Returns an unmodifiable Collection of known <code>CacheClientProxy</code> instances. The
* collection is not static so its contents may change.
*
* @return the collection of known <code>CacheClientProxy</code> instances
*/
public Collection<CacheClientProxy> getClientProxies() {
return Collections.unmodifiableCollection(this._clientProxies.values());
}
// /**
// * Returns the <code>Executor</code> that delivers messages to the
// * <code>CacheClientProxy</code> instances.
// * @return the <code>Executor</code> that delivers messages to the
// * <code>CacheClientProxy</code> instances
// */
// protected Executor getExecutor()
// {
// return _executor;
// }
private void closeAllClientCqs(CacheClientProxy proxy) {
CqService cqService = proxy.getCache().getCqService();
if (cqService != null) {
final boolean isDebugEnabled = logger.isDebugEnabled(); // LocalizedMessage.create(
try {
if (isDebugEnabled) {
logger.debug("CacheClientNotifier: Closing client CQs: {}", proxy);
}
cqService.closeClientCqs(proxy.getProxyID());
} catch (CqException e1) {
logger.warn(LocalizedMessage.create(
LocalizedStrings.CacheClientNotifier_UNABLE_TO_CLOSE_CQS_FOR_THE_CLIENT__0,
proxy.getProxyID()));
if (isDebugEnabled) {
e1.printStackTrace();
}
}
}
}
/**
* Shuts down durable client proxy
*
*/
public boolean closeDurableClientProxy(String durableClientId) throws CacheException {
CacheClientProxy ccp = getClientProxy(durableClientId);
if (ccp == null) {
return false;
}
// we can probably remove the isPaused check
if (ccp.isPaused() && !ccp.isConnected()) {
ccp.setKeepAlive(false);
closeDeadProxies(Collections.singletonList(ccp), true);
return true;
} else {
if (logger.isDebugEnabled()) {
logger.debug("Cannot close running durable client: {}", durableClientId);
}
throw new CacheException("Cannot close a running durable client : " + durableClientId) {};
}
}
/**
* Close dead <code>CacheClientProxy</code> instances
*
* @param deadProxies The list of <code>CacheClientProxy</code> instances to close
*/
private void closeDeadProxies(List deadProxies, boolean stoppedNormally) {
final boolean isDebugEnabled = logger.isDebugEnabled();
for (Iterator i = deadProxies.iterator(); i.hasNext();) {
CacheClientProxy proxy = (CacheClientProxy) i.next();
if (isDebugEnabled)
logger.debug("CacheClientNotifier: Closing dead client: {}", proxy);
// Close the proxy
boolean keepProxy = false;
try {
keepProxy = proxy.close(false, stoppedNormally);
} catch (CancelException e) {
throw e;
} catch (Exception e) {
}
// Remove the proxy if necessary. It might not be necessary to remove the
// proxy if it is durable.
if (keepProxy) {
logger.info(LocalizedMessage.create(
LocalizedStrings.CacheClientNotifier_CACHECLIENTNOTIFIER_KEEPING_PROXY_FOR_DURABLE_CLIENT_NAMED_0_FOR_1_SECONDS_2,
new Object[] {proxy.getDurableId(), Integer.valueOf(proxy.getDurableTimeout()),
proxy}));
} else {
closeAllClientCqs(proxy);
if (isDebugEnabled) {
logger.debug("CacheClientNotifier: Not keeping proxy for non-durable client: {}", proxy);
}
removeClientProxy(proxy);
}
proxy.notifyRemoval();
} // for
}
/**
* Registers a new <code>InterestRegistrationListener</code> with the set of
* <code>InterestRegistrationListener</code>s.
*
* @param listener The <code>InterestRegistrationListener</code> to register
*
* @since GemFire 5.8Beta
*/
public void registerInterestRegistrationListener(InterestRegistrationListener listener) {
this.writableInterestRegistrationListeners.add(listener);
}
/**
* Unregisters an existing <code>InterestRegistrationListener</code> from the set of
* <code>InterestRegistrationListener</code>s.
*
* @param listener The <code>InterestRegistrationListener</code> to unregister
*
* @since GemFire 5.8Beta
*/
public void unregisterInterestRegistrationListener(InterestRegistrationListener listener) {
this.writableInterestRegistrationListeners.remove(listener);
}
/**
* Returns a read-only collection of <code>InterestRegistrationListener</code>s registered with
* this notifier.
*
* @return a read-only collection of <code>InterestRegistrationListener</code>s registered with
* this notifier
*
* @since GemFire 5.8Beta
*/
public Set getInterestRegistrationListeners() {
return this.readableInterestRegistrationListeners;
}
/**
*
* @since GemFire 5.8Beta
*/
protected boolean containsInterestRegistrationListeners() {
return !this.writableInterestRegistrationListeners.isEmpty();
}
/**
*
* @since GemFire 5.8Beta
*/
protected void notifyInterestRegistrationListeners(InterestRegistrationEvent event) {
for (Iterator i = this.writableInterestRegistrationListeners.iterator(); i.hasNext();) {
InterestRegistrationListener listener = (InterestRegistrationListener) i.next();
if (event.isRegister()) {
listener.afterRegisterInterest(event);
} else {
listener.afterUnregisterInterest(event);
}
}
}
/**
* Test method used to determine the state of the CacheClientNotifier
*
* @return the statistics for the notifier
*/
public CacheClientNotifierStats getStats() {
return this._statistics;
}
/**
* Returns this <code>CacheClientNotifier</code>'s <code>Cache</code>.
*
* @return this <code>CacheClientNotifier</code>'s <code>Cache</code>
*/
protected Cache getCache() { // TODO:SYNC: looks wrong
if (this._cache != null && this._cache.isClosed()) {
GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
if (cache != null) {
this._cache = cache;
this.logWriter = cache.getInternalLogWriter();
this.securityLogWriter = cache.getSecurityInternalLogWriter();
}
}
return this._cache;
}
/**
* Returns this <code>CacheClientNotifier</code>'s maximum message count.
*
* @return this <code>CacheClientNotifier</code>'s maximum message count
*/
protected int getMaximumMessageCount() {
return this.maximumMessageCount;
}
/**
* Returns this <code>CacheClientNotifier</code>'s message time-to-live.
*
* @return this <code>CacheClientNotifier</code>'s message time-to-live
*/
protected int getMessageTimeToLive() {
return this.messageTimeToLive;
}
protected void handleInterestEvent(InterestRegistrationEvent event) {
LocalRegion region = (LocalRegion) event.getRegion();
region.handleInterestEvent(event);
}
/**
* Constructor.
*
* @param cache The GemFire <code>Cache</code>
* @param acceptorStats
* @param maximumMessageCount
* @param messageTimeToLive
* @param listener a listener which should receive notifications abouts queues being added or
* removed.
* @param overflowAttributesList
*/
private CacheClientNotifier(Cache cache, CacheServerStats acceptorStats, int maximumMessageCount,
int messageTimeToLive, ConnectionListener listener, List overflowAttributesList,
boolean isGatewayReceiver) {
// Set the Cache
this.setCache((GemFireCacheImpl) cache);
this.acceptorStats = acceptorStats;
this.socketCloser = new SocketCloser(1, 50); // we only need one thread per client and wait 50ms
// for close
// Set the LogWriter
this.logWriter = (InternalLogWriter) cache.getLogger();
this._connectionListener = listener;
// Set the security LogWriter
this.securityLogWriter = (InternalLogWriter) cache.getSecurityLogger();
this.maximumMessageCount = maximumMessageCount;
this.messageTimeToLive = messageTimeToLive;
// Initialize the statistics
StatisticsFactory factory;
if (isGatewayReceiver) {
factory = new DummyStatisticsFactory();
} else {
factory = this.getCache().getDistributedSystem();
}
this._statistics = new CacheClientNotifierStats(factory);
// Initialize the executors
// initializeExecutors(this._logger);
try {
this.logFrequency = Long.valueOf(System.getProperty(MAX_QUEUE_LOG_FREQUENCY));
if (this.logFrequency <= 0) {
this.logFrequency = DEFAULT_LOG_FREQUENCY;
}
} catch (Exception e) {
this.logFrequency = DEFAULT_LOG_FREQUENCY;
}
eventEnqueueWaitTime =
Integer.getInteger(EVENT_ENQUEUE_WAIT_TIME_NAME, DEFAULT_EVENT_ENQUEUE_WAIT_TIME);
if (eventEnqueueWaitTime < 0) {
eventEnqueueWaitTime = DEFAULT_EVENT_ENQUEUE_WAIT_TIME;
}
// Schedule task to periodically ping clients.
scheduleClientPingTask();
}
/**
* this message is used to send interest registration to another server. Since interest
* registration performs a state-flush operation this message must not transmitted on an ordered
* socket
*/
public static class ServerInterestRegistrationMessage extends HighPriorityDistributionMessage
implements MessageWithReply {
ClientProxyMembershipID clientId;
ClientInterestMessageImpl clientMessage;
int processorId;
ServerInterestRegistrationMessage(ClientProxyMembershipID clientID,
ClientInterestMessageImpl msg) {
this.clientId = clientID;
this.clientMessage = msg;
}
public ServerInterestRegistrationMessage() {}
static void sendInterestChange(DM dm, ClientProxyMembershipID clientID,
ClientInterestMessageImpl msg) {
ServerInterestRegistrationMessage smsg = new ServerInterestRegistrationMessage(clientID, msg);
Set recipients = dm.getOtherDistributionManagerIds();
smsg.setRecipients(recipients);
ReplyProcessor21 rp = new ReplyProcessor21(dm, recipients);
smsg.processorId = rp.getProcessorId();
dm.putOutgoing(smsg);
try {
rp.waitForReplies();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
/*
* (non-Javadoc)
*
* @see org.apache.geode.distributed.internal.DistributionMessage#process(org.apache.geode.
* distributed.internal.DistributionManager)
*/
@Override
protected void process(DistributionManager dm) {
// Get the proxy for the proxy id
try {
CacheClientNotifier ccn = CacheClientNotifier.getInstance();
if (ccn != null) {
CacheClientProxy proxy = ccn.getClientProxy(clientId);
// If this VM contains a proxy for the requested proxy id, forward the
// message on to the proxy for processing
if (proxy != null) {
proxy.processInterestMessage(this.clientMessage);
}
}
} finally {
ReplyMessage reply = new ReplyMessage();
reply.setProcessorId(this.processorId);
reply.setRecipient(getSender());
try {
dm.putOutgoing(reply);
} catch (CancelException e) {
// can't send a reply, so ignore the exception
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.geode.internal.DataSerializableFixedID#getDSFID()
*/
public int getDSFID() {
return SERVER_INTEREST_REGISTRATION_MESSAGE;
}
@Override
public void toData(DataOutput out) throws IOException {
super.toData(out);
out.writeInt(this.processorId);
InternalDataSerializer.invokeToData(this.clientId, out);
InternalDataSerializer.invokeToData(this.clientMessage, out);
}
@Override
public void fromData(DataInput in) throws IOException, ClassNotFoundException {
super.fromData(in);
this.processorId = in.readInt();
this.clientId = new ClientProxyMembershipID();
InternalDataSerializer.invokeFromData(this.clientId, in);
this.clientMessage = new ClientInterestMessageImpl();
InternalDataSerializer.invokeFromData(this.clientMessage, in);
}
}
// * Initializes the <code>QueuedExecutor</code> and
// <code>PooledExecutor</code>
// * used to deliver messages to <code>CacheClientProxy</code> instances.
// * @param logger The GemFire <code>LogWriterI18n</code>
// */
// private void initializeExecutors(LogWriterI18n logger)
// {
// // Create the thread groups
// final ThreadGroup loggerGroup = LoggingThreadGroup.createThreadGroup("Cache
// Client Notifier Logger Group", logger);
// final ThreadGroup notifierGroup =
// new ThreadGroup("Cache Client Notifier Group")
// {
// public void uncaughtException(Thread t, Throwable e)
// {
// Thread.dumpStack();
// loggerGroup.uncaughtException(t, e);
// //CacheClientNotifier.exceptionInThreads = true;
// }
// };
//
// // Originally set ThreadGroup to be a daemon, but it was causing the
// following
// // exception after five minutes of non-activity (the keep alive time of the
// // threads in the PooledExecutor.
//
// // java.lang.IllegalThreadStateException
// // at java.lang.ThreadGroup.add(Unknown Source)
// // at java.lang.Thread.init(Unknown Source)
// // at java.lang.Thread.<init>(Unknown Source)
// // at
// org.apache.geode.internal.cache.tier.sockets.CacheClientNotifier$4.newThread(CacheClientNotifier.java:321)
// // at
// org.apache.edu.oswego.cs.dl.util.concurrent.PooledExecutor.addThread(PooledExecutor.java:512)
// // at
// org.apache.edu.oswego.cs.dl.util.concurrent.PooledExecutor.execute(PooledExecutor.java:888)
// // at
// org.apache.geode.internal.cache.tier.sockets.CacheClientNotifier.notifyClients(CacheClientNotifier.java:95)
// // at
// org.apache.geode.internal.cache.tier.sockets.ServerConnection.run(ServerConnection.java:271)
//
// //notifierGroup.setDaemon(true);
//
// if (USE_QUEUED_EXECUTOR)
// createQueuedExecutor(notifierGroup);
// else
// createPooledExecutor(notifierGroup);
// }
// /**
// * Creates the <code>QueuedExecutor</code> used to deliver messages
// * to <code>CacheClientProxy</code> instances
// * @param notifierGroup The <code>ThreadGroup</code> to which the
// * <code>QueuedExecutor</code>'s <code>Threads</code> belong
// */
// protected void createQueuedExecutor(final ThreadGroup notifierGroup)
// {
// QueuedExecutor queuedExecutor = new QueuedExecutor(new LinkedQueue());
// queuedExecutor.setThreadFactory(new ThreadFactory()
// {
// public Thread newThread(Runnable command)
// {
// Thread thread = new Thread(notifierGroup, command, "Queued Cache Client
// Notifier");
// thread.setDaemon(true);
// return thread;
// }
// });
// _executor = queuedExecutor;
// }
// /**
// * Creates the <code>PooledExecutor</code> used to deliver messages
// * to <code>CacheClientProxy</code> instances
// * @param notifierGroup The <code>ThreadGroup</code> to which the
// * <code>PooledExecutor</code>'s <code>Threads</code> belong
// */
// protected void createPooledExecutor(final ThreadGroup notifierGroup)
// {
// PooledExecutor pooledExecutor = new PooledExecutor(new
// BoundedLinkedQueue(4096), 50);
// pooledExecutor.setMinimumPoolSize(10);
// pooledExecutor.setKeepAliveTime(1000 * 60 * 5);
// pooledExecutor.setThreadFactory(new ThreadFactory()
// {
// public Thread newThread(Runnable command)
// {
// Thread thread = new Thread(notifierGroup, command, "Pooled Cache Client
// Notifier");
// thread.setDaemon(true);
// return thread;
// }
// });
// pooledExecutor.createThreads(5);
// _executor = pooledExecutor;
// }
protected void deliverInterestChange(ClientProxyMembershipID proxyID,
ClientInterestMessageImpl message) {
DM dm = ((InternalDistributedSystem) this.getCache().getDistributedSystem())
.getDistributionManager();
ServerInterestRegistrationMessage.sendInterestChange(dm, proxyID, message);
}
public CacheServerStats getAcceptorStats() {
return this.acceptorStats;
}
public SocketCloser getSocketCloser() {
return this.socketCloser;
}
public void addCompiledQuery(DefaultQuery query) {
if (this.compiledQueries.putIfAbsent(query.getQueryString(), query) == null) {
// Added successfully.
this._statistics.incCompiledQueryCount(1);
if (logger.isDebugEnabled()) {
logger.debug(
"Added compiled query into ccn.compliedQueries list. Query: {}. Total compiled queries: {}",
query.getQueryString(), this._statistics.getCompiledQueryCount());
}
// Start the clearIdleCompiledQueries thread.
startCompiledQueryCleanupThread();
}
}
public Query getCompiledQuery(String queryString) {
return this.compiledQueries.get(queryString);
}
private void clearCompiledQueries() {
if (this.compiledQueries.size() > 0) {
this._statistics.incCompiledQueryCount(-(this.compiledQueries.size()));
this.compiledQueries.clear();
if (logger.isDebugEnabled()) {
logger.debug(
"Removed all compiled queries from ccn.compliedQueries list. Total compiled queries: {}",
this._statistics.getCompiledQueryCount());
}
}
}
/**
* This starts the cleanup thread that periodically (DefaultQuery.TEST_COMPILED_QUERY_CLEAR_TIME)
* checks for the compiled queries that are not used and removes them.
*/
private void startCompiledQueryCleanupThread() {
if (isCompiledQueryCleanupThreadStarted) {
return;
}
SystemTimer.SystemTimerTask task = new SystemTimer.SystemTimerTask() {
@Override
public void run2() {
final boolean isDebugEnabled = logger.isDebugEnabled();
for (Map.Entry<String, DefaultQuery> e : compiledQueries.entrySet()) {
DefaultQuery q = e.getValue();
// Check if the query last used flag.
// If its true set it to false. If its false it means it is not used
// from the its last checked.
if (q.getLastUsed()) {
q.setLastUsed(false);
} else {
if (compiledQueries.remove(e.getKey()) != null) {
// If successfully removed decrement the counter.
_statistics.incCompiledQueryCount(-1);
if (isDebugEnabled) {
logger.debug("Removed compiled query from ccn.compliedQueries list. Query: "
+ q.getQueryString() + ". Total compiled queries are : "
+ _statistics.getCompiledQueryCount());
}
}
}
}
}
};
synchronized (lockIsCompiledQueryCleanupThreadStarted) {
if (!isCompiledQueryCleanupThreadStarted) {
long period = DefaultQuery.TEST_COMPILED_QUERY_CLEAR_TIME > 0
? DefaultQuery.TEST_COMPILED_QUERY_CLEAR_TIME : DefaultQuery.COMPILED_QUERY_CLEAR_TIME;
_cache.getCCPTimer().scheduleAtFixedRate(task, period, period);
}
isCompiledQueryCleanupThreadStarted = true;
}
}
protected void scheduleClientPingTask() {
this.clientPingTask = new SystemTimer.SystemTimerTask() {
@Override
public void run2() {
// If there are no proxies, return
if (CacheClientNotifier.this._clientProxies.isEmpty()) {
return;
}
// Create ping message
ClientMessage message = new ClientPingMessageImpl();
// Determine clients to ping
for (CacheClientProxy proxy : getClientProxies()) {
logger.debug("Checking whether to ping {}", proxy);
// Ping clients whose version is GE 6.6.2.2
if (proxy.getVersion().compareTo(Version.GFE_6622) >= 0) {
// Send the ping message directly to the client. Do not qo through
// the queue. If the queue were used, the secondary connection would
// not be pinged. Instead, pings would just build up in secondary
// queue and never be sent. The counter is used to help scalability.
// If normal messages are sent by the proxy, then the counter will
// be reset and no pings will be sent.
if (proxy.incrementAndGetPingCounter() >= CLIENT_PING_TASK_COUNTER) {
logger.debug("Pinging {}", proxy);
proxy.sendMessageDirectly(message);
logger.debug("Done pinging {}", proxy);
} else {
logger.debug("Not pinging because not idle: {}", proxy);
}
} else {
logger.debug("Ignoring because of version: {}", proxy);
}
}
}
};
if (logger.isDebugEnabled()) {
logger.debug("Scheduling client ping task with period={} ms", CLIENT_PING_TASK_PERIOD);
}
CacheClientNotifier.this._cache.getCCPTimer().scheduleAtFixedRate(this.clientPingTask,
CLIENT_PING_TASK_PERIOD, CLIENT_PING_TASK_PERIOD);
}
/**
* A string representing all hosts used for delivery purposes.
*/
protected static final String ALL_HOSTS = "ALL_HOSTS";
/**
* An int representing all ports used for delivery purposes.
*/
protected static final int ALL_PORTS = -1;
// /**
// * Whether to synchonously deliver messages to proxies.
// * This is currently hard-coded to true to ensure ordering.
// */
// protected static final boolean USE_SYNCHRONOUS_NOTIFICATION =
// true;
// Boolean.getBoolean("CacheClientNotifier.USE_SYNCHRONOUS_NOTIFICATION");
// /**
// * Whether to use the <code>QueuedExecutor</code> (or the
// * <code>PooledExecutor</code>) to deliver messages to proxies.
// * Currently, delivery is synchronous. No <code>Executor</code> is
// * used.
// */
// protected static final boolean USE_QUEUED_EXECUTOR =
// Boolean.getBoolean("CacheClientNotifier.USE_QUEUED_EXECUTOR");
/**
* The map of known <code>CacheClientProxy</code> instances. Maps ClientProxyMembershipID to
* CacheClientProxy. Note that the keys in this map are not updated when a durable client
* reconnects. To make sure you get the updated ClientProxyMembershipID use this map to lookup the
* CacheClientProxy and then call getProxyID on it.
*/
private final ConcurrentMap/* <ClientProxyMembershipID, CacheClientProxy> */ _clientProxies =
new ConcurrentHashMap();
/**
* The map of <code>CacheClientProxy</code> instances which are getting initialized. Maps
* ClientProxyMembershipID to CacheClientProxy.
*/
private final ConcurrentMap/* <ClientProxyMembershipID, CacheClientProxy> */ _initClientProxies =
new ConcurrentHashMap();
private final HashSet<ClientProxyMembershipID> timedOutDurableClientProxies =
new HashSet<ClientProxyMembershipID>();
/**
* The GemFire <code>Cache</code>. Note that since this is a singleton class you should not use a
* direct reference to _cache in CacheClientNotifier code. Instead, you should always use
* <code>getCache()</code>
*/
private GemFireCacheImpl _cache;
private InternalLogWriter logWriter;
/**
* The GemFire security <code>LogWriter</code>
*/
private InternalLogWriter securityLogWriter;
/** the maximum number of messages that can be enqueued in a client-queue. */
private int maximumMessageCount;
/**
* the time (in seconds) after which a message in the client queue will expire.
*/
private int messageTimeToLive;
/**
* A listener which receives notifications about queues that are added or removed
*/
private ConnectionListener _connectionListener;
private CacheServerStats acceptorStats;
/**
* haContainer can hold either the name of the client-messages-region (in case of eviction
* policies "mem" or "entry") or an instance of HashMap (in case of eviction policy "none"). In
* both the cases, it'll store HAEventWrapper as its key and ClientUpdateMessage as its value.
*/
private volatile HAContainerWrapper haContainer;
// /**
// * The singleton <code>CacheClientNotifier</code> instance
// */
// protected static CacheClientNotifier _instance;
/**
* The size of the server-to-client communication socket buffers. This can be modified using the
* BridgeServer.SOCKET_BUFFER_SIZE system property.
*/
static final private int socketBufferSize =
Integer.getInteger("BridgeServer.SOCKET_BUFFER_SIZE", 32768).intValue();
/**
* The statistics for this notifier
*/
protected final CacheClientNotifierStats _statistics;
/**
* The <code>InterestRegistrationListener</code> instances registered in this VM. This is used
* when modifying the set of listeners.
*/
private final Set writableInterestRegistrationListeners = new CopyOnWriteArraySet();
/**
* The <code>InterestRegistrationListener</code> instances registered in this VM. This is used to
* provide a read-only <code>Set</code> of listeners.
*/
private final Set readableInterestRegistrationListeners =
Collections.unmodifiableSet(writableInterestRegistrationListeners);
/**
* System property name for indicating how much frequently the "Queue full" message should be
* logged.
*/
public static final String MAX_QUEUE_LOG_FREQUENCY =
DistributionConfig.GEMFIRE_PREFIX + "logFrequency.clientQueueReachedMaxLimit";
public static final long DEFAULT_LOG_FREQUENCY = 1000;
public static final String EVENT_ENQUEUE_WAIT_TIME_NAME =
DistributionConfig.GEMFIRE_PREFIX + "subscription.EVENT_ENQUEUE_WAIT_TIME";
public static final int DEFAULT_EVENT_ENQUEUE_WAIT_TIME = 100;
/**
* System property value denoting the time in milliseconds. Any thread putting an event into a
* subscription queue, which is full, will wait this much time for the queue to make space. It'll
* then enque the event possibly causing the queue to grow beyond its capacity/max-size. See
* #51400.
*/
public static int eventEnqueueWaitTime;
/**
* The frequency of logging the "Queue full" message.
*/
private long logFrequency = DEFAULT_LOG_FREQUENCY;
private final ConcurrentHashMap<String, DefaultQuery> compiledQueries =
new ConcurrentHashMap<String, DefaultQuery>();
private volatile boolean isCompiledQueryCleanupThreadStarted = false;
private final Object lockIsCompiledQueryCleanupThreadStarted = new Object();
private SystemTimer.SystemTimerTask clientPingTask;
private final SocketCloser socketCloser;
private static final long CLIENT_PING_TASK_PERIOD =
Long.getLong(DistributionConfig.GEMFIRE_PREFIX + "serverToClientPingPeriod", 60000);
private static final long CLIENT_PING_TASK_COUNTER =
Long.getLong(DistributionConfig.GEMFIRE_PREFIX + "serverToClientPingCounter", 3);
public long getLogFrequency() {
return this.logFrequency;
}
/**
* @return the haContainer
*/
public Map getHaContainer() {
return haContainer;
}
public void initHaContainer(List overflowAttributesList) {
// lazily initialize haContainer in case this CCN instance was created by a gateway receiver
if (overflowAttributesList != null
&& !HARegionQueue.HA_EVICTION_POLICY_NONE.equals(overflowAttributesList.get(0))) {
haContainer = new HAContainerRegion(_cache.getRegion(
Region.SEPARATOR + CacheServerImpl.clientMessagesRegion((GemFireCacheImpl) _cache,
(String) overflowAttributesList.get(0),
((Integer) overflowAttributesList.get(1)).intValue(),
((Integer) overflowAttributesList.get(2)).intValue(),
(String) overflowAttributesList.get(3), (Boolean) overflowAttributesList.get(4))));
} else {
haContainer = new HAContainerMap(new ConcurrentHashMap());
}
assert haContainer != null;
if (logger.isDebugEnabled()) {
logger.debug("ha container ({}) has been created.", haContainer.getName());
}
}
private final Set blackListedClients = new CopyOnWriteArraySet();
public void addToBlacklistedClient(ClientProxyMembershipID proxyID) {
blackListedClients.add(proxyID);
// ensure that cache and distributed system state are current and open
this.getCache();
new ScheduledThreadPoolExecutor(1).schedule(new ExpireBlackListTask(proxyID), 120,
TimeUnit.SECONDS);
}
public Set getBlacklistedClient() {
return blackListedClients;
}
/**
* @param _cache the _cache to set
*/
private void setCache(GemFireCacheImpl _cache) {
this._cache = _cache;
}
private class ExpireBlackListTask extends PoolTask {
private ClientProxyMembershipID proxyID;
public ExpireBlackListTask(ClientProxyMembershipID proxyID) {
this.proxyID = proxyID;
}
@Override
public void run2() {
if (blackListedClients.remove(proxyID)) {
if (logger.isDebugEnabled()) {
logger.debug("{} client is no longer blacklisted", proxyID);
}
}
}
}
}