All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.client.cache.impl.ClientCacheProxySupport Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 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 com.hazelcast.client.cache.impl;

import com.hazelcast.cache.HazelcastCacheManager;
import com.hazelcast.cache.impl.CacheEventListenerAdaptor;
import com.hazelcast.cache.impl.CacheSyncListenerCompleter;
import com.hazelcast.cache.impl.ICacheInternal;
import com.hazelcast.cache.impl.ICacheService;
import com.hazelcast.client.cache.impl.ClientCacheProxySupportUtil.EmptyCompletionListener;
import com.hazelcast.internal.nearcache.impl.RemoteCallHook;
import com.hazelcast.client.impl.ClientDelegatingFuture;
import com.hazelcast.client.impl.clientside.ClientMessageDecoder;
import com.hazelcast.client.impl.protocol.ClientMessage;
import com.hazelcast.client.impl.protocol.codec.CacheClearCodec;
import com.hazelcast.client.impl.protocol.codec.CacheContainsKeyCodec;
import com.hazelcast.client.impl.protocol.codec.CacheEntryProcessorCodec;
import com.hazelcast.client.impl.protocol.codec.CacheGetAllCodec;
import com.hazelcast.client.impl.protocol.codec.CacheGetAndRemoveCodec;
import com.hazelcast.client.impl.protocol.codec.CacheGetAndReplaceCodec;
import com.hazelcast.client.impl.protocol.codec.CacheGetCodec;
import com.hazelcast.client.impl.protocol.codec.CacheListenerRegistrationCodec;
import com.hazelcast.client.impl.protocol.codec.CacheLoadAllCodec;
import com.hazelcast.client.impl.protocol.codec.CachePutAllCodec;
import com.hazelcast.client.impl.protocol.codec.CachePutCodec;
import com.hazelcast.client.impl.protocol.codec.CachePutIfAbsentCodec;
import com.hazelcast.client.impl.protocol.codec.CacheRemoveAllCodec;
import com.hazelcast.client.impl.protocol.codec.CacheRemoveAllKeysCodec;
import com.hazelcast.client.impl.protocol.codec.CacheRemoveCodec;
import com.hazelcast.client.impl.protocol.codec.CacheReplaceCodec;
import com.hazelcast.client.impl.protocol.codec.CacheSetExpiryPolicyCodec;
import com.hazelcast.client.impl.spi.ClientContext;
import com.hazelcast.client.impl.spi.ClientListenerService;
import com.hazelcast.client.impl.spi.ClientPartitionService;
import com.hazelcast.client.impl.spi.ClientProxy;
import com.hazelcast.client.impl.spi.impl.ClientInvocation;
import com.hazelcast.client.impl.spi.impl.ClientInvocationFuture;
import com.hazelcast.cluster.Member;
import com.hazelcast.config.CacheConfig;
import com.hazelcast.core.ManagedContext;
import com.hazelcast.internal.nio.IOUtil;
import com.hazelcast.internal.serialization.Data;
import com.hazelcast.internal.util.ExceptionUtil;
import com.hazelcast.internal.util.FutureUtil;
import com.hazelcast.internal.util.Timer;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.impl.InternalCompletableFuture;

import javax.annotation.Nonnull;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.event.CacheEntryListener;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.integration.CompletionListener;
import javax.cache.processor.EntryProcessorException;
import java.io.Closeable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;

import static com.hazelcast.cache.impl.CacheProxyUtil.NULL_KEY_IS_NOT_ALLOWED;
import static com.hazelcast.cache.impl.CacheProxyUtil.validateConfiguredTypes;
import static com.hazelcast.cache.impl.CacheProxyUtil.validateNotNull;
import static com.hazelcast.cache.impl.operation.MutableOperation.IGNORE_COMPLETION;
import static com.hazelcast.client.cache.impl.ClientCacheProxySupportUtil.addCallback;
import static com.hazelcast.client.cache.impl.ClientCacheProxySupportUtil.getSafely;
import static com.hazelcast.client.cache.impl.ClientCacheProxySupportUtil.handleFailureOnCompletionListener;
import static com.hazelcast.internal.util.CollectionUtil.objectToDataCollection;
import static com.hazelcast.internal.util.ConcurrencyUtil.getDefaultAsyncExecutor;
import static com.hazelcast.internal.util.ExceptionUtil.rethrow;
import static com.hazelcast.internal.util.ExceptionUtil.rethrowAllowedTypeFirst;
import static com.hazelcast.internal.util.ExceptionUtil.sneakyThrow;

/**
 * Abstract {@link com.hazelcast.cache.ICache} implementation which provides shared internal implementations
 * of cache operations like put, replace, remove, invoke and open/close operations. These internal implementations are delegated
 * by actual cache methods.
 * 

* Note: this partial implementation is used by client. * * @param the type of key * @param the type of value */ @SuppressWarnings({"checkstyle:methodcount", "checkstyle:classfanoutcomplexity"}) abstract class ClientCacheProxySupport extends ClientProxy implements ICacheInternal, CacheSyncListenerCompleter { private static final int TIMEOUT = 10; private static final CompletionListener NULL_COMPLETION_LISTENER = new EmptyCompletionListener(); // this will represent the name from the user perspective protected final String name; protected final String nameWithPrefix; protected final CacheConfig cacheConfig; protected int partitionCount; boolean statisticsEnabled; CacheStatsHandler statsHandler; private ILogger logger; private final AtomicReference cacheManagerRef = new AtomicReference<>(); private final ConcurrentMap loadAllCalls = new ConcurrentHashMap<>(); private final AtomicBoolean isClosed = new AtomicBoolean(false); private final AtomicBoolean isDestroyed = new AtomicBoolean(false); private final AtomicInteger completionIdCounter = new AtomicInteger(); private final ClientCacheProxySyncListenerCompleter listenerCompleter; private final ConcurrentMap closeableListeners; ClientCacheProxySupport(CacheConfig cacheConfig, ClientContext context) { super(ICacheService.SERVICE_NAME, cacheConfig.getName(), context); this.name = cacheConfig.getName(); this.nameWithPrefix = cacheConfig.getNameWithPrefix(); this.cacheConfig = cacheConfig; this.statisticsEnabled = cacheConfig.isStatisticsEnabled(); this.closeableListeners = new ConcurrentHashMap<>(); this.listenerCompleter = new ClientCacheProxySyncListenerCompleter(this); } @Override protected void onInitialize() { logger = getContext().getLoggingService().getLogger(getClass()); statsHandler = new CacheStatsHandler(getSerializationService()); ClientPartitionService partitionService = getContext().getPartitionService(); partitionCount = partitionService.getPartitionCount(); } @Override protected String getDistributedObjectName() { return cacheConfig.getNameWithPrefix(); } @Override public void close() { if (!isClosed.compareAndSet(false, true)) { return; } if (statisticsEnabled) { statsHandler.clear(); } close0(false); } @Override protected boolean preDestroy() { if (!isDestroyed.compareAndSet(false, true)) { return false; } close0(true); isClosed.set(true); return true; } @Override public boolean isClosed() { return isClosed.get(); } @Override public boolean isDestroyed() { return isDestroyed.get(); } @Override public void open() { if (isDestroyed.get()) { throw new IllegalStateException("Cache is already destroyed! Cannot be reopened"); } isClosed.set(false); } @Override public String getPrefixedName() { return nameWithPrefix; } @Override protected T invoke(ClientMessage clientMessage) { try { Future future = new ClientInvocation(getClient(), clientMessage, getName()).invoke(); return (T) future.get(); } catch (Exception e) { throw rethrow(e); } } @Override public void countDownCompletionLatch(int countDownLatchId) { listenerCompleter.countDownCompletionLatch(countDownLatchId); } @Override public CacheManager getCacheManager() { return cacheManagerRef.get(); } @Override public void setCacheManager(HazelcastCacheManager cacheManager) { assert cacheManager instanceof HazelcastClientCacheManager; // optimistically assume the CacheManager is already set if (cacheManagerRef.get() == cacheManager) { return; } if (!cacheManagerRef.compareAndSet(null, (HazelcastClientCacheManager) cacheManager)) { if (cacheManagerRef.get() == cacheManager) { // some other thread managed to set the same CacheManager, we are good return; } throw new IllegalStateException("Cannot overwrite a Cache's CacheManager."); } } @Override public void resetCacheManager() { cacheManagerRef.set(null); } @Override protected void postDestroy() { CacheManager cacheManager = cacheManagerRef.get(); if (cacheManager != null) { cacheManager.destroyCache(getName()); } resetCacheManager(); } @Override protected void onDestroy() { if (statisticsEnabled) { statsHandler.clear(); } super.onDestroy(); } protected void ensureOpen() { if (isClosed()) { throw new IllegalStateException("Cache operations can not be performed. The cache closed"); } } @SuppressWarnings("unchecked") protected T injectDependencies(T obj) { ManagedContext managedContext = getSerializationService().getManagedContext(); return (T) managedContext.initialize(obj); } protected long nowInNanosOrDefault() { return statisticsEnabled ? Timer.nanos() : -1; } protected ClientInvocationFuture invoke(ClientMessage req, int partitionId, int completionId) { boolean completionOperation = completionId != -1; if (completionOperation) { listenerCompleter.registerCompletionLatch(completionId, 1); } try { ClientInvocation clientInvocation = new ClientInvocation(getClient(), req, name, partitionId); ClientInvocationFuture future = clientInvocation.invoke(); if (completionOperation) { listenerCompleter.waitCompletionLatch(completionId, future); } return future; } catch (Throwable e) { if (e instanceof IllegalStateException) { close(); } if (completionOperation) { listenerCompleter.deregisterCompletionLatch(completionId); } throw rethrowAllowedTypeFirst(e, CacheException.class); } } protected ClientInvocationFuture invoke(ClientMessage req, Data keyData, int completionId) { int partitionId = getContext().getPartitionService().getPartitionId(keyData); return invoke(req, partitionId, completionId); } protected V getAndRemoveSyncInternal(K key) { long startNanos = nowInNanosOrDefault(); ensureOpen(); validateNotNull(key); validateConfiguredTypes(cacheConfig, key); Data keyData = toData(key); V response = callGetAndRemoveSync(key, keyData); if (statisticsEnabled) { statsHandler.onRemove(true, startNanos, response); } return response; } @Nonnull protected V callGetAndRemoveSync(K key, Data keyData) { try { return (V) getAndRemoveInternal(keyData, true).get(); } catch (Throwable t) { throw ExceptionUtil.rethrow(t); } } protected CompletableFuture getAndRemoveAsyncInternal(K key) { long startNanos = nowInNanosOrDefault(); ensureOpen(); validateNotNull(key); validateConfiguredTypes(cacheConfig, key); Data keyData = toData(key); BiConsumer statsCallback = statisticsEnabled ? statsHandler.newOnRemoveCallback(true, startNanos) : null; return callGetAndRemoveAsync(key, keyData, statsCallback); } @Nonnull protected CompletableFuture callGetAndRemoveAsync(K key, Data keyData, BiConsumer statsCallback) { ClientDelegatingFuture delegatingFuture = getAndRemoveInternal(keyData, false); addCallback(delegatingFuture, statsCallback); return delegatingFuture; } private ClientDelegatingFuture getAndRemoveInternal(Data keyData, boolean withCompletionEvent) { int completionId = withCompletionEvent ? nextCompletionId() : -1; ClientMessage request = CacheGetAndRemoveCodec.encodeRequest(nameWithPrefix, keyData, completionId); ClientInvocationFuture future = invoke(request, keyData, completionId); return newDelegatingFuture(future, CacheGetAndRemoveCodec::decodeResponse); } protected boolean removeSync(K key, V oldValue, boolean hasOldValue, boolean withCompletionEvent) { long startNanos = nowInNanosOrDefault(); ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue); validateConfiguredTypes(cacheConfig, key, oldValue); } else { validateNotNull(key); validateConfiguredTypes(cacheConfig, key); } Data keyData = toData(key); Data oldValueData = toData(oldValue); boolean removed = callRemoveSync(key, keyData, oldValueData, withCompletionEvent); if (statisticsEnabled) { statsHandler.onRemove(false, startNanos, removed); } return removed; } protected boolean callRemoveSync(K key, Data keyData, Data oldValueData, boolean withCompletionEvent) { InternalCompletableFuture delegatingFuture = doRemoveOnServer(keyData, oldValueData, withCompletionEvent); try { return delegatingFuture.get(); } catch (Throwable t) { throw rethrow(t); } } protected CompletableFuture removeAsync(K key, V oldValue, boolean hasOldValue, boolean withCompletionEvent) { long startNanos = nowInNanosOrDefault(); ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue); validateConfiguredTypes(cacheConfig, key, oldValue); } else { validateNotNull(key); validateConfiguredTypes(cacheConfig, key); } Data keyData = toData(key); Data oldValueData = toData(oldValue); BiConsumer callback = statisticsEnabled ? statsHandler.newOnRemoveCallback(false, startNanos) : null; return callRemoveAsync(key, keyData, oldValueData, withCompletionEvent, callback); } protected CompletableFuture callRemoveAsync(K key, Data keyData, Data oldValueData, boolean withCompletionEvent, BiConsumer callback) { return addCallback(doRemoveOnServer(keyData, oldValueData, withCompletionEvent), callback); } @Nonnull private InternalCompletableFuture doRemoveOnServer(Data keyData, Data oldValueData, boolean withCompletionEvent) { int completionId = withCompletionEvent ? nextCompletionId() : -1; ClientMessage request = CacheRemoveCodec.encodeRequest(nameWithPrefix, keyData, oldValueData, completionId); ClientInvocationFuture future = invoke(request, keyData, completionId); return newDelegatingFuture(future, CacheRemoveCodec::decodeResponse); } protected boolean replaceSync(K key, V oldValue, V newValue, ExpiryPolicy expiryPolicy, boolean hasOldValue) { long startNanos = nowInNanosOrDefault(); ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue, newValue); validateConfiguredTypes(cacheConfig, key, oldValue, newValue); } else { validateNotNull(key, newValue); validateConfiguredTypes(cacheConfig, key, newValue); } Data keyData = toData(key); Data oldValueData = toData(oldValue); Data newValueData = toData(newValue); Data expiryPolicyData = toData(expiryPolicy); boolean replaced = callReplaceSync(key, keyData, newValue, newValueData, oldValueData, expiryPolicyData); if (statisticsEnabled) { statsHandler.onReplace(false, startNanos, replaced); } return replaced; } protected boolean callReplaceSync(K key, Data keyData, V newValue, Data newValueData, Data oldValueData, Data expiryPolicyData) { CompletableFuture future = doReplaceOnServer(keyData, newValueData, oldValueData, expiryPolicyData, true, null); try { return future.get(); } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } protected CompletableFuture replaceAsync(K key, V oldValue, V newValue, ExpiryPolicy expiryPolicy, boolean hasOldValue, boolean withCompletionEvent) { long startNanos = nowInNanosOrDefault(); ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue, newValue); validateConfiguredTypes(cacheConfig, key, oldValue, newValue); } else { validateNotNull(key, newValue); validateConfiguredTypes(cacheConfig, key, newValue); } Data keyData = toData(key); Data oldValueData = toData(oldValue); Data newValueData = toData(newValue); Data expiryPolicyData = toData(expiryPolicy); BiConsumer statsCallback = statisticsEnabled ? statsHandler.newOnReplaceCallback(startNanos) : null; return callReplaceAsync(key, keyData, newValue, newValueData, oldValueData, expiryPolicyData, withCompletionEvent, statsCallback); } protected CompletableFuture callReplaceAsync(K key, Data keyData, V newValue, Data newValueData, Data oldValueData, Data expiryPolicyData, boolean withCompletionEvent, BiConsumer statsCallback) { return doReplaceOnServer(keyData, newValueData, oldValueData, expiryPolicyData, withCompletionEvent, statsCallback); } private CompletableFuture doReplaceOnServer(Data keyData, Data newValueData, Data oldValueData, Data expiryPolicyData, boolean withCompletionEvent, BiConsumer statsCallback) { int completionId = withCompletionEvent ? nextCompletionId() : -1; ClientMessage request = CacheReplaceCodec.encodeRequest(nameWithPrefix, keyData, oldValueData, newValueData, expiryPolicyData, completionId); ClientDelegatingFuture delegatingFuture = newDelegatingFuture(invoke(request, keyData, completionId), CacheReplaceCodec::decodeResponse); return addCallback(delegatingFuture, statsCallback); } protected V getAndReplaceSync(K key, V oldValue, V newValue, ExpiryPolicy expiryPolicy, boolean hasOldValue, boolean withCompletionEvent) { long startNanos = nowInNanosOrDefault(); ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue, newValue); validateConfiguredTypes(cacheConfig, key, oldValue, newValue); } else { validateNotNull(key, newValue); validateConfiguredTypes(cacheConfig, key, newValue); } Data keyData = toData(key); Data newValueData = toData(newValue); Data expiryPolicyData = toData(expiryPolicy); V response = callGetAndReplaceSync(key, keyData, newValue, newValueData, expiryPolicyData, withCompletionEvent); if (statisticsEnabled) { statsHandler.onReplace(true, startNanos, response); } return response; } protected V callGetAndReplaceSync(K key, Data keyData, V newValue, Data newValueData, Data expiryPolicyData, boolean withCompletionEvent) { try { return (V) doGetAndReplaceOnServer(keyData, newValueData, expiryPolicyData, withCompletionEvent, null).get(); } catch (Throwable t) { throw ExceptionUtil.rethrow(t); } } private CompletableFuture doGetAndReplaceOnServer(Data keyData, Data newValueData, Data expiryPolicyData, boolean withCompletionEvent, BiConsumer statsCallback) { int completionId = withCompletionEvent ? nextCompletionId() : -1; ClientMessage request = CacheGetAndReplaceCodec.encodeRequest(nameWithPrefix, keyData, newValueData, expiryPolicyData, completionId); ClientInvocationFuture future = invoke(request, keyData, completionId); ClientDelegatingFuture delegatingFuture = newDelegatingFuture(future, CacheGetAndReplaceCodec::decodeResponse); return addCallback(delegatingFuture, statsCallback); } protected CompletableFuture getAndReplaceAsync(K key, V oldValue, V newValue, ExpiryPolicy expiryPolicy, boolean hasOldValue, boolean withCompletionEvent) { long startNanos = nowInNanosOrDefault(); ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue, newValue); validateConfiguredTypes(cacheConfig, key, oldValue, newValue); } else { validateNotNull(key, newValue); validateConfiguredTypes(cacheConfig, key, newValue); } Data keyData = toData(key); Data newValueData = toData(newValue); Data expiryPolicyData = toData(expiryPolicy); BiConsumer statsCallback = statisticsEnabled ? statsHandler.newOnReplaceCallback(startNanos) : null; return callGetAndReplaceAsync(key, keyData, newValue, newValueData, expiryPolicyData, withCompletionEvent, statsCallback); } protected CompletableFuture callGetAndReplaceAsync(K key, Data keyData, V newValue, Data newValueData, Data expiryPolicyData, boolean withCompletionEvent, BiConsumer statsCallback) { return doGetAndReplaceOnServer(keyData, newValueData, expiryPolicyData, withCompletionEvent, statsCallback); } protected V putSyncInternal(K key, V value, ExpiryPolicy expiryPolicy, boolean isGet) { long startNanos = nowInNanosOrDefault(); ensureOpen(); validateNotNull(key, value); validateConfiguredTypes(cacheConfig, key, value); Data keyData = toData(key); Data valueData = toData(value); Data expiryPolicyData = toData(expiryPolicy); try { V response = callPutSync(key, keyData, value, valueData, expiryPolicyData, isGet); if (statisticsEnabled) { statsHandler.onPut(isGet, startNanos, response != null); } return response; } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } protected V callPutSync(K key, Data keyData, V value, Data valueData, Data expiryPolicyData, boolean isGet) throws InterruptedException, ExecutionException { ClientInvocationFuture invocationFuture = putInternal(keyData, valueData, expiryPolicyData, isGet, true); ClientDelegatingFuture delegatingFuture = newDelegatingFuture(invocationFuture, CachePutCodec::decodeResponse); return delegatingFuture.get(); } protected CompletableFuture putAsyncInternal(K key, V value, ExpiryPolicy expiryPolicy, boolean isGet, boolean withCompletionEvent) { ensureOpen(); validateNotNull(key, value); validateConfiguredTypes(cacheConfig, key, value); Data keyData = toData(key); Data valueData = toData(value); Data expiryPolicyData = toData(expiryPolicy); BiConsumer statsCallback = newStatsCallbackOrNull(isGet); return callPutAsync(key, keyData, value, valueData, expiryPolicyData, isGet, withCompletionEvent, statsCallback); } protected CompletableFuture callPutAsync(K key, Data keyData, V value, Data valueData, Data expiryPolicyData, boolean isGet, boolean withCompletionEvent, BiConsumer statsCallback) { ClientInvocationFuture invocationFuture = putInternal(keyData, valueData, expiryPolicyData, isGet, withCompletionEvent); InternalCompletableFuture future = newDelegatingFuture(invocationFuture, CachePutCodec::decodeResponse); return addCallback(future, statsCallback); } protected Boolean putIfAbsentSync(K key, V value, ExpiryPolicy expiryPolicy, boolean withCompletionEvent) { long startNanos = nowInNanosOrDefault(); ensureOpen(); validateNotNull(key, value); validateConfiguredTypes(cacheConfig, key, value); Data keyData = toData(key); Data valueData = toData(value); Data expiryPolicyData = toData(expiryPolicy); Boolean result = callPutIfAbsentSync(key, keyData, value, valueData, expiryPolicyData, withCompletionEvent); if (statisticsEnabled) { statsHandler.onPutIfAbsent(startNanos, result); } return result; } protected Boolean callPutIfAbsentSync(K key, Data keyData, V value, Data valueData, Data expiryPolicyData, boolean withCompletionEvent) { InternalCompletableFuture future = doPutIfAbsentOnServer(keyData, valueData, expiryPolicyData, withCompletionEvent); try { return future.get(); } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } protected CompletableFuture putIfAbsentAsync(K key, V value, ExpiryPolicy expiryPolicy, boolean withCompletionEvent) { long startNanos = nowInNanosOrDefault(); ensureOpen(); validateNotNull(key, value); validateConfiguredTypes(cacheConfig, key, value); Data keyData = toData(key); Data valueData = toData(value); Data expiryPolicyData = toData(expiryPolicy); BiConsumer statsCallback = statisticsEnabled ? statsHandler.newOnPutIfAbsentCallback(startNanos) : null; return callPutIfAbsentAsync(key, keyData, value, valueData, expiryPolicyData, withCompletionEvent, statsCallback); } @SuppressWarnings("checkstyle:parameternumber") protected CompletableFuture callPutIfAbsentAsync(K key, Data keyData, V value, Data valueData, Data expiryPolicyData, boolean withCompletionEvent, BiConsumer statsCallback) { InternalCompletableFuture future = doPutIfAbsentOnServer(keyData, valueData, expiryPolicyData, withCompletionEvent); return addCallback(future, statsCallback); } private InternalCompletableFuture doPutIfAbsentOnServer(Data keyData, Data valueData, Data expiryPolicyData, boolean withCompletionEvent) { int completionId = withCompletionEvent ? nextCompletionId() : -1; ClientMessage request = CachePutIfAbsentCodec.encodeRequest(nameWithPrefix, keyData, valueData, expiryPolicyData, completionId); ClientInvocationFuture future = invoke(request, keyData, completionId); return newDelegatingFuture(future, CachePutIfAbsentCodec::decodeResponse); } protected BiConsumer newStatsCallbackOrNull(boolean isGet) { if (!statisticsEnabled) { return null; } return statsHandler.newOnPutCallback(isGet, Timer.nanos()); } protected boolean setExpiryPolicyInternal(K key, ExpiryPolicy expiryPolicy) { ensureOpen(); validateNotNull(key); validateNotNull(expiryPolicy); Data keyData = toData(key); Data expiryPolicyData = toData(expiryPolicy); List list = Collections.singletonList(keyData); ClientMessage request = CacheSetExpiryPolicyCodec.encodeRequest(nameWithPrefix, list, expiryPolicyData); ClientInvocationFuture future = invoke(request, keyData, IGNORE_COMPLETION); ClientDelegatingFuture delegatingFuture = newDelegatingFuture(future, CacheSetExpiryPolicyCodec::decodeResponse); try { return delegatingFuture.get(); } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } protected void removeAllKeysInternal(Set keys, Collection dataKeys, long startNanos) { int partitionCount = getContext().getPartitionService().getPartitionCount(); int completionId = nextCompletionId(); listenerCompleter.registerCompletionLatch(completionId, partitionCount); ClientMessage request = CacheRemoveAllKeysCodec.encodeRequest(nameWithPrefix, dataKeys, completionId); try { invoke(request); listenerCompleter.waitCompletionLatch(completionId, null); if (statisticsEnabled) { statsHandler.onBatchRemove(startNanos, dataKeys.size()); } } catch (Throwable t) { listenerCompleter.deregisterCompletionLatch(completionId); throw rethrowAllowedTypeFirst(t, CacheException.class); } } protected void removeAllInternal() { int partitionCount = getContext().getPartitionService().getPartitionCount(); int completionId = nextCompletionId(); listenerCompleter.registerCompletionLatch(completionId, partitionCount); ClientMessage request = CacheRemoveAllCodec.encodeRequest(nameWithPrefix, completionId); try { invoke(request); listenerCompleter.waitCompletionLatch(completionId, null); if (statisticsEnabled) { statsHandler.getStatistics().setLastUpdateTime(System.currentTimeMillis()); // we don't support count stats of removing all entries } } catch (Throwable t) { listenerCompleter.deregisterCompletionLatch(completionId); throw rethrowAllowedTypeFirst(t, CacheException.class); } } protected void clearInternal() { ClientMessage request = CacheClearCodec.encodeRequest(nameWithPrefix); try { invoke(request); if (statisticsEnabled) { statsHandler.getStatistics().setLastUpdateTime(System.currentTimeMillis()); // we don't support count stats of removing all entries } } catch (Throwable t) { throw rethrowAllowedTypeFirst(t, CacheException.class); } } protected void addListenerLocally(UUID regId, CacheEntryListenerConfiguration cacheEntryListenerConfiguration, CacheEventListenerAdaptor adaptor) { listenerCompleter.putListenerIfAbsent(cacheEntryListenerConfiguration, regId); CacheEntryListener entryListener = adaptor.getCacheEntryListener(); if (entryListener instanceof Closeable closeable) { closeableListeners.putIfAbsent(regId, closeable); } } protected void removeListenerLocally(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { UUID registrationId = listenerCompleter.removeListener(cacheEntryListenerConfiguration); if (registrationId != null) { Closeable closeable = closeableListeners.remove(registrationId); IOUtil.closeResource(closeable); } } protected UUID getListenerIdLocal(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { return listenerCompleter.getListenerId(cacheEntryListenerConfiguration); } private void deregisterAllCacheEntryListener(Collection listenerRegistrations) { ClientListenerService listenerService = getContext().getListenerService(); for (UUID regId : listenerRegistrations) { listenerService.deregisterListener(regId); } } protected V callGetSync(Object key, ExpiryPolicy expiryPolicy) { long startNanos = nowInNanosOrDefault(); try { Data dataKey = toData(key); V value = getInternal(dataKey, expiryPolicy, false).get(); if (statisticsEnabled) { statsHandler.onGet(startNanos, value != null); } return value; } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } protected CompletableFuture getAsyncInternal(Object key, ExpiryPolicy expiryPolicy) { long startNanos = nowInNanosOrDefault(); BiConsumer statsCallback = statisticsEnabled ? statsHandler.newOnGetCallback(startNanos) : null; return callGetAsync(key, expiryPolicy, statsCallback); } @Nonnull protected CompletableFuture callGetAsync(Object key, ExpiryPolicy expiryPolicy, BiConsumer statsCallback) { Data dataKey = toData(key); ClientDelegatingFuture future = getInternal(dataKey, expiryPolicy, true); return addCallback(future, statsCallback); } protected void getAllInternal(Set keys, Collection dataKeys, ExpiryPolicy expiryPolicy, List resultingKeyValuePairs, long startNanos) { if (dataKeys.isEmpty()) { objectToDataCollection(keys, dataKeys, getSerializationService(), NULL_KEY_IS_NOT_ALLOWED); } Data expiryPolicyData = toData(expiryPolicy); ClientMessage request = CacheGetAllCodec.encodeRequest(nameWithPrefix, dataKeys, expiryPolicyData); ClientMessage responseMessage = invoke(request); List> response = CacheGetAllCodec.decodeResponse(responseMessage); for (Map.Entry entry : response) { resultingKeyValuePairs.add(entry.getKey()); resultingKeyValuePairs.add(entry.getValue()); } if (statisticsEnabled) { statsHandler.onBatchGet(startNanos, response.size()); } } protected void setExpiryPolicyInternal(Set keys, ExpiryPolicy policy) { setExpiryPolicyInternal(keys, policy, null); } protected void setExpiryPolicyInternal(Set keys, ExpiryPolicy policy, Set serializedKeys) { try { List[] keysByPartition = groupKeysToPartitions(keys, serializedKeys); setExpiryPolicyAndWaitForCompletion(keysByPartition, policy); } catch (Exception e) { throw rethrow(e); } } protected void putAllInternal(Map userInputMap, ExpiryPolicy expiryPolicy, List>[] entriesPerPartition, long startNanos) { try { RemoteCallHook nearCachingHook = createPutAllNearCachingHook(userInputMap.size()); // first we fill entry set per partition groupDataToPartitions(userInputMap, entriesPerPartition, nearCachingHook); // then we invoke the operations and sync on completion of these operations Data expiryPolicyData = toData(expiryPolicy); callPutAllSync(entriesPerPartition, expiryPolicyData, nearCachingHook, startNanos); } catch (Exception t) { throw rethrow(t); } } // Overridden to inject hook for near cache population. protected RemoteCallHook createPutAllNearCachingHook(int keySetSize) { return RemoteCallHook.EMPTY_HOOK; } private void groupDataToPartitions(Map userInputMap, List>[] entriesPerPartition, RemoteCallHook nearCachingHook) { ClientPartitionService partitionService = getContext().getPartitionService(); for (Map.Entry entry : userInputMap.entrySet()) { K key = entry.getKey(); V value = entry.getValue(); validateNotNull(key, value); Data keyData = toData(key); Data valueData = toData(value); nearCachingHook.beforeRemoteCall(key, keyData, value, valueData); int partitionId = partitionService.getPartitionId(keyData); List> entries = entriesPerPartition[partitionId]; if (entries == null) { entries = new ArrayList<>(); entriesPerPartition[partitionId] = entries; } // TODO Can we do this without creating SimpleImmutableEntry? entries.add(new AbstractMap.SimpleImmutableEntry<>(keyData, valueData)); } } protected void callPutAllSync(List>[] entriesPerPartition, Data expiryPolicyData, RemoteCallHook nearCachingHook, long startNanos) { List futureEntriesTuples = new ArrayList<>(entriesPerPartition.length); for (int partitionId = 0; partitionId < entriesPerPartition.length; partitionId++) { List> entries = entriesPerPartition[partitionId]; if (entries != null) { int completionId = nextCompletionId(); // TODO: if there is a single entry, we could make use of a put operation since that is a bit cheaper ClientMessage request = CachePutAllCodec.encodeRequest(nameWithPrefix, entries, expiryPolicyData, completionId); Future future = invoke(request, partitionId, completionId); futureEntriesTuples.add(new ClientCacheProxySupportUtil.FutureEntriesTuple(future, entries)); } } waitResponseFromAllPartitionsForPutAll(futureEntriesTuples, startNanos); } protected boolean containsKeyInternal(Object key) { Data keyData = toData(key); ClientMessage request = CacheContainsKeyCodec.encodeRequest(nameWithPrefix, keyData); ClientMessage result = invoke(request, keyData); return CacheContainsKeyCodec.decodeResponse(result); } protected void loadAllInternal(Set keys, List dataKeys, boolean replaceExistingValues, CompletionListener completionListener) { ClientMessage request = CacheLoadAllCodec.encodeRequest(nameWithPrefix, dataKeys, replaceExistingValues); try { submitLoadAllTask(request, completionListener, dataKeys); } catch (Exception e) { if (completionListener != null) { completionListener.onException(e); } throw new CacheException(e); } } private void submitLoadAllTask(ClientMessage request, CompletionListener completionListener, final List binaryKeys) { final CompletionListener listener = completionListener != null ? injectDependencies(completionListener) : NULL_COMPLETION_LISTENER; ClientDelegatingFuture delegatingFuture = null; try { final long startNanos = nowInNanosOrDefault(); ClientInvocationFuture future = new ClientInvocation(getClient(), request, getName()).invoke(); delegatingFuture = newDelegatingFuture(future, clientMessage -> Boolean.TRUE); final Future delFuture = delegatingFuture; loadAllCalls.put(delegatingFuture, listener); delegatingFuture.whenCompleteAsync((response, t) -> { if (t == null) { loadAllCalls.remove(delFuture); onLoadAll(binaryKeys, startNanos); listener.onCompletion(); } else { loadAllCalls.remove(delFuture); handleFailureOnCompletionListener(listener, t); } }, getDefaultAsyncExecutor()); } catch (Throwable t) { if (delegatingFuture != null) { loadAllCalls.remove(delegatingFuture); } handleFailureOnCompletionListener(listener, t); } } private void onLoadAll(List keys, long startNanos) { if (statisticsEnabled) { statsHandler.onBatchPut(startNanos, keys.size()); } } protected Object invokeInternal(Object key, Data epData, Object... arguments) { Data keyData = toData(key); List argumentsData; if (arguments != null) { argumentsData = new ArrayList<>(arguments.length); for (Object argument : arguments) { argumentsData.add(toData(argument)); } } else { argumentsData = Collections.emptyList(); } int completionId = nextCompletionId(); ClientMessage request = CacheEntryProcessorCodec.encodeRequest(nameWithPrefix, keyData, epData, argumentsData, completionId); try { InternalCompletableFuture future = invoke(request, keyData, completionId); ClientMessage response = getSafely(future); Data data = CacheEntryProcessorCodec.decodeResponse(response); // at client side, we don't know what entry processor does so we ignore it from statistics perspective return toObject(data); } catch (CacheException ce) { throw ce; } catch (Exception e) { throw new EntryProcessorException(e); } } protected void updateCacheListenerConfigOnOtherNodes(CacheEntryListenerConfiguration cacheEntryListenerConfiguration, boolean isRegister) { Collection members = getContext().getClusterService().getMemberList(); for (Member member : members) { try { UUID uuid = member.getUuid(); Data configData = toData(cacheEntryListenerConfiguration); ClientMessage request = CacheListenerRegistrationCodec.encodeRequest(nameWithPrefix, configData, isRegister, uuid); ClientInvocation invocation = new ClientInvocation(getClient(), request, getName(), uuid); invocation.invoke(); } catch (Exception e) { sneakyThrow(e); } } } protected ILogger getLogger() { return logger; } private ClientDelegatingFuture getInternal(Data keyData, ExpiryPolicy expiryPolicy, boolean deserializeResponse) { Data expiryPolicyData = toData(expiryPolicy); ClientMessage request = CacheGetCodec.encodeRequest(nameWithPrefix, keyData, expiryPolicyData); int partitionId = getContext().getPartitionService().getPartitionId(keyData); ClientInvocation clientInvocation = new ClientInvocation(getClient(), request, name, partitionId); ClientInvocationFuture future = clientInvocation.invoke(); return newDelegatingFuture(future, CacheGetCodec::decodeResponse, deserializeResponse); } private List[] groupKeysToPartitions(Set keys, Set serializedKeys) { List[] keysByPartition = new List[partitionCount]; ClientPartitionService partitionService = getContext().getPartitionService(); for (K key : keys) { Data keyData = getSerializationService().toData(key); if (serializedKeys != null) { serializedKeys.add(keyData); } int partitionId = partitionService.getPartitionId(keyData); List partition = keysByPartition[partitionId]; if (partition == null) { partition = new ArrayList<>(); keysByPartition[partitionId] = partition; } partition.add(keyData); } return keysByPartition; } private void setExpiryPolicyAndWaitForCompletion(List[] keysByPartition, ExpiryPolicy expiryPolicy) { List futures = new ArrayList<>(keysByPartition.length); Data policyData = toData(expiryPolicy); for (int partitionId = 0; partitionId < keysByPartition.length; partitionId++) { List keys = keysByPartition[partitionId]; if (keys != null) { ClientMessage request = CacheSetExpiryPolicyCodec.encodeRequest(nameWithPrefix, keys, policyData); futures.add(invoke(request, partitionId, IGNORE_COMPLETION)); } } List throwables = FutureUtil.waitUntilAllResponded(futures); if (!throwables.isEmpty()) { throw rethrow(throwables.get(0)); } } private void waitResponseFromAllPartitionsForPutAll(List futureEntriesTuples, long startNanos) { Throwable error = null; for (ClientCacheProxySupportUtil.FutureEntriesTuple tuple : futureEntriesTuples) { Future future = tuple.getFuture(); List> entries = tuple.getEntries(); try { future.get(); // Note that we count the batch put only if there is no exception while putting to target partition. // In case of error, some of the entries might have been put and others might fail. // But we simply ignore the actual put count here if there is an error. if (statisticsEnabled) { statsHandler.getStatistics().increaseCachePuts(entries.size()); } } catch (Throwable t) { logger.finest("Error occurred while putting entries as batch!", t); if (error == null) { error = t; } } } if (statisticsEnabled) { statsHandler.getStatistics().addPutTimeNanos(nowInNanosOrDefault() - startNanos); } if (error != null) { /* * There maybe multiple exceptions but we throw only the first one. * There are some ideas to throw all exceptions to caller but all of them have drawbacks: * - `Thread::addSuppressed` can be used to add other exceptions to the first one * but it is available since JDK 7. * - `Thread::initCause` can be used but this is wrong as semantic * since the other exceptions are not cause of the first one. * - We may wrap all exceptions in our custom exception (such as `MultipleCacheException`) * but in this case caller may wait different exception type and this idea causes problem. * For example see this TCK test: * `org.jsr107.tck.integration.CacheWriterTest::shouldWriteThoughUsingPutAll_partialSuccess` * In this test exception is thrown at `CacheWriter` and caller side expects this exception. * So as a result, we only throw the first exception and others are suppressed by only logging. */ throw rethrow(error); } } private int nextCompletionId() { return completionIdCounter.incrementAndGet(); } private void close0(boolean destroy) { waitOnGoingLoadAllCallsToFinish(); closeListeners(); if (!destroy) { // when cache is being destroyed, the CacheManager is still required for cleanup and reset in postDestroy // when cache is being closed, the CacheManager is reset now resetCacheManager(); } } private void waitOnGoingLoadAllCallsToFinish() { Iterator> iterator = loadAllCalls.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); Future future = entry.getKey(); CompletionListener completionListener = entry.getValue(); try { future.get(TIMEOUT, TimeUnit.SECONDS); } catch (Throwable t) { logger.finest("Error occurred at loadAll operation execution while waiting it to finish on cache close!", t); handleFailureOnCompletionListener(completionListener, t); } iterator.remove(); } } private void closeListeners() { deregisterAllCacheEntryListener(listenerCompleter.getListenersIds(true)); deregisterAllCacheEntryListener(listenerCompleter.getListenersIds(false)); listenerCompleter.clearListeners(); for (Closeable closeable : closeableListeners.values()) { IOUtil.closeResource(closeable); } } private ClientMessage invoke(ClientMessage clientMessage, Data keyData) { try { int partitionId = getContext().getPartitionService().getPartitionId(keyData); Future future = new ClientInvocation(getClient(), clientMessage, getName(), partitionId).invoke(); return (ClientMessage) future.get(); } catch (Exception e) { throw rethrow(e); } } private ClientDelegatingFuture newDelegatingFuture(ClientInvocationFuture future, ClientMessageDecoder decoder) { return new ClientDelegatingFuture<>(future, getSerializationService(), decoder); } private ClientDelegatingFuture newDelegatingFuture(ClientInvocationFuture future, ClientMessageDecoder decoder, boolean deserializeResponse) { return new ClientDelegatingFuture<>(future, getSerializationService(), decoder, deserializeResponse); } public ClientInvocationFuture putInternal(Data keyData, Data valueData, Data expiryPolicyData, boolean isGet, boolean withCompletionEvent) { int completionId = withCompletionEvent ? nextCompletionId() : -1; ClientMessage request = CachePutCodec.encodeRequest(nameWithPrefix, keyData, valueData, expiryPolicyData, isGet, completionId); return invoke(request, keyData, completionId); } }