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

com.oracle.coherence.client.AsyncNamedCacheClient Maven / Gradle / Ivy

/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */
package com.oracle.coherence.client;

import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
import com.google.protobuf.Empty;
import com.google.protobuf.Int32Value;
import com.google.protobuf.UnsafeByteOperations;

import com.oracle.coherence.common.base.Classes;

import com.oracle.coherence.grpc.BinaryHelper;
import com.oracle.coherence.grpc.ContainsEntryRequest;
import com.oracle.coherence.grpc.SimpleDaemonPoolExecutor;
import com.oracle.coherence.grpc.Entry;
import com.oracle.coherence.grpc.EntryResult;
import com.oracle.coherence.grpc.InvokeAllRequest;
import com.oracle.coherence.grpc.MapEventResponse;
import com.oracle.coherence.grpc.MapListenerErrorResponse;
import com.oracle.coherence.grpc.MapListenerRequest;
import com.oracle.coherence.grpc.MapListenerResponse;
import com.oracle.coherence.grpc.MapListenerSubscribedResponse;
import com.oracle.coherence.grpc.MapListenerUnsubscribedResponse;
import com.oracle.coherence.grpc.OptionalValue;
import com.oracle.coherence.grpc.Requests;

import com.tangosol.coherence.config.Config;
import com.tangosol.internal.net.NamedCacheDeactivationListener;

import com.tangosol.io.NamedSerializerFactory;
import com.tangosol.io.Serializer;

import com.tangosol.net.AsyncNamedCache;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.CacheService;
import com.tangosol.net.NamedCache;
import com.tangosol.net.NamedMap;
import com.tangosol.net.RequestIncompleteException;

import com.tangosol.net.cache.CacheEvent;
import com.tangosol.net.cache.CacheEvent.TransformationState;
import com.tangosol.net.cache.CacheMap;

import com.tangosol.net.grpc.GrpcDependencies;
import com.tangosol.util.Base;
import com.tangosol.util.ExternalizableHelper;
import com.tangosol.util.Filter;
import com.tangosol.util.InvocableMap;
import com.tangosol.util.Listeners;
import com.tangosol.util.LongArray;
import com.tangosol.util.LongArray.Iterator;
import com.tangosol.util.MapEvent;
import com.tangosol.util.MapListener;
import com.tangosol.util.MapListenerSupport;
import com.tangosol.util.MapTriggerListener;
import com.tangosol.util.SimpleMapEntry;
import com.tangosol.util.SparseArray;
import com.tangosol.util.SynchronousListener;
import com.tangosol.util.ValueExtractor;

import com.tangosol.util.filter.AlwaysFilter;

import io.grpc.Channel;

import io.grpc.stub.StreamObserver;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import java.util.concurrent.atomic.AtomicInteger;

import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;

import java.util.logging.Level;
import java.util.logging.Logger;

import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Implementation of a {@link NamedCache}.
 *
 * @param   the type of the cache keys
 * @param   the type of the cache values
 *
 * @author Jonathan Knight  2019.11.22
 * @since 20.06
 */
@SuppressWarnings("DuplicatedCode")
public class AsyncNamedCacheClient
        implements AsyncNamedCache
    {
    // ----- constructors ---------------------------------------------------

    /**
     * Creates an {@link AsyncNamedCacheClient} from the specified
     * {@link Dependencies}.
     *
     * @param dependencies  the {@link Dependencies} to configure this
     *                      {@link AsyncNamedCacheClient}.
     */
    public AsyncNamedCacheClient(Dependencies dependencies)
        {
        f_sCacheName                = dependencies.getCacheName();
        f_dispatcher                = dependencies.getEventDispatcher();
        f_sScopeName                = dependencies.getScopeName().orElse(GrpcDependencies.DEFAULT_SCOPE);
        f_synchronousCache          = new NamedCacheClient<>(this);
        f_listDeactivationListeners = new ArrayList<>();
        f_executor                  = dependencies.getExecutor().orElseGet(AsyncNamedCacheClient::createDefaultExecutor);
        f_sFormat                   = dependencies.getSerializerFormat()
                                                  .orElseGet(() -> dependencies.getSerializer()
                                                       .map(Serializer::getName)
                                                       .orElseGet(AsyncNamedCacheClient::getDefaultSerializerFormat));
        f_serializer                = dependencies.getSerializer().orElseGet(() -> createSerializer(f_sFormat));
        f_service                   = dependencies.getClient()
                                                  .orElseGet(() -> new NamedCacheGrpcClient(dependencies.getChannel()));
        initEvents();
        }

    // ----- Object methods -------------------------------------------------

    @Override
    public String toString()
        {
        return "AsyncNamedCacheClient{"
               + "scope: \"" + f_sScopeName + '"'
               + "name: \"" + f_sCacheName + '"'
               + " format: \"" + f_sFormat + '"'
               + '}';
        }

    // ----- AsyncNamedCache interface --------------------------------------

    @Override
   public NamedCache getNamedCache()
       {
       return getNamedCacheClient();
       }

    // ----- AsyncNamedMap interface ----------------------------------------

    @Override
   public NamedMap getNamedMap()
       {
       return getNamedCacheClient();
       }

    @Override
    @SuppressWarnings("unchecked")
    public  CompletableFuture aggregate(Collection colKeys,
                                              InvocableMap.EntryAggregator entryAggregator)
        {
        return executeIfActive(() ->
            {
            try
                {
                List keys = colKeys.stream()
                        .map(this::toByteString)
                        .collect(Collectors.toList());
                return f_service.aggregate(Requests.aggregate(f_sScopeName, f_sCacheName, f_sFormat, keys,
                                                              toByteString(entryAggregator)))
                        .thenApplyAsync(this::fromBytesValue)
                        .thenApply(r -> (R) r)
                        .toCompletableFuture();
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  CompletableFuture aggregate(Filter filter,
                                              InvocableMap.EntryAggregator entryAggregator)
        {
        return executeIfActive(() ->
            {
            try
                {
                return f_service
                        .aggregate(Requests.aggregate(f_sScopeName, f_sCacheName, f_sFormat, toByteString(filter),
                                                      toByteString(entryAggregator)))
                        .thenApplyAsync(this::fromBytesValue)
                        .thenApply(r -> (R) r)
                        .toCompletableFuture();
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    @SuppressWarnings("unchecked")
    public  CompletableFuture invoke(K k, InvocableMap.EntryProcessor entryProcessor)
        {
        return executeIfActive(() ->
            {
            try
                {
                return f_service.invoke(Requests.invoke(f_sScopeName, f_sCacheName, f_sFormat, toByteString(k),
                                                        toByteString(entryProcessor)))
                        .thenApplyAsync(this::valueFromBytesValue)
                        .thenApply(r -> (R) r)
                        .toCompletableFuture();
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    public  CompletableFuture> invokeAll(Collection colKeys,
                                                      InvocableMap.EntryProcessor processor)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture>            future   = new CompletableFuture<>();
                BiFunction, Map> function = (e, m) ->
                    {
                    try
                        {
                        m.put(fromByteString(e.getKey()), fromByteString(e.getValue()));
                        return m;
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return null;
                    };
                FutureStreamObserver> observer       = new FutureStreamObserver<>(future,
                                                                                                   new HashMap<>(),
                                                                                                   function);
                Collection                 serializedKeys = colKeys.stream()
                        .map(this::toByteString)
                        .collect(Collectors.toList());
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, serializedKeys, toByteString(processor)),
                                  observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    @SuppressWarnings({"rawtypes"})
    public  CompletableFuture> invokeAll(Filter filter, InvocableMap.EntryProcessor processor)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture>            future   = new CompletableFuture<>();
                BiFunction, Map> function = (e, m) ->
                    {
                    try
                        {
                        m.put(fromByteString(e.getKey()), fromByteString(e.getValue()));
                        return m;
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return null;
                    };
                FutureStreamObserver> observer = new FutureStreamObserver<>(future, new HashMap<>(),
                                                                                             function);
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, toByteString(filter),
                                                     toByteString(processor)), observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    public  CompletableFuture invokeAll(Collection colKeys,
                                                 InvocableMap.EntryProcessor processor,
                                                 Consumer> callback)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture       future   = new CompletableFuture<>();
                BiFunction function = (e, v) ->
                    {
                    try
                        {
                        Map.Entry entry = new SimpleMapEntry<>(fromByteString(e.getKey()),
                                                                     fromByteString(e.getValue()));
                        callback.accept(entry);
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return null;
                    };
                FutureStreamObserver observer       = new FutureStreamObserver<>(future, VOID, function);
                Collection            serializedKeys = colKeys.stream()
                        .map(this::toByteString)
                        .collect(Collectors.toList());
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, serializedKeys, toByteString(processor)),
                                  observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    @SuppressWarnings({"rawtypes"})
    public  CompletableFuture invokeAll(Filter filter,
                                                 InvocableMap.EntryProcessor processor,
                                                 Consumer> callback)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture       future   = new CompletableFuture<>();
                BiFunction function = (e, v) ->
                    {
                    try
                        {
                        Map.Entry entry = new SimpleMapEntry<>(fromByteString(e.getKey()),
                                                                     fromByteString(e.getValue()));
                        callback.accept(entry);
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return null;
                    };
                FutureStreamObserver observer = new FutureStreamObserver<>(future, VOID, function);
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, toByteString(filter),
                                                     toByteString(processor)), observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    // ----- AsyncNamedCache methods ----------------------------------------

    @Override
    public CompletableFuture clear()
        {
        return executeIfActive(() ->
            {
            try
                {
                return f_service.clear(Requests.clear(f_sScopeName, f_sCacheName)).thenApply(e -> VOID).toCompletableFuture();
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    public CompletableFuture containsKey(K key)
        {
        return executeIfActive(() -> containsKeyInternal(key));
        }

    @Override
    public CompletableFuture>> entrySet()
        {
        return executeIfActive(() -> CompletableFuture.completedFuture(new RemoteEntrySet<>(this)));
        }

    @Override
    public CompletableFuture get(K key)
        {
        return executeIfActive(() -> getInternal(key, null));
        }

    @Override
    public CompletableFuture> getAll(Collection colKeys)
        {
        return executeIfActive(() ->
            {
            if (colKeys.isEmpty())
                {
                return CompletableFuture.completedFuture(new HashMap<>());
                }
            else
                {
                return CompletableFuture.supplyAsync(() -> getAllInternalAsMap(colKeys));
                }
            });
        }

    @Override
    public CompletableFuture getOrDefault(K key, V defaultValue)
        {
        return executeIfActive(() -> getInternal(key, defaultValue));
        }

    @Override
    public  CompletableFuture> invokeAll(InvocableMap.EntryProcessor processor)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture>           future   = new CompletableFuture<>();
                InvokeAllBiFunction              function = new InvokeAllBiFunction<>(future);
                FutureStreamObserver> observer = new FutureStreamObserver<>(future, new HashMap<>(),
                                                                                             function);
                ByteString                             filter   = toByteString(AlwaysFilter.INSTANCE());
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, filter, toByteString(processor)),
                                  observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    public  CompletableFuture invokeAll(InvocableMap.EntryProcessor processor,
                                                 Consumer> callback)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture       future   = new CompletableFuture<>();
                BiFunction function = (e, v) ->
                    {
                    try
                        {
                        Map.Entry entry = new SimpleMapEntry<>(fromByteString(e.getKey()),
                                                                     fromByteString(e.getValue()));
                        callback.accept(entry);
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return null;
                    };
                FutureStreamObserver observer = new FutureStreamObserver<>(future, VOID, function);
                ByteString                        filter   = toByteString(AlwaysFilter.INSTANCE());
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, filter, toByteString(processor)),
                                  observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    public  CompletableFuture invokeAll(InvocableMap.EntryProcessor processor,
                                                 BiConsumer callback)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture       future   = new CompletableFuture<>();
                BiFunction function = (e, v) ->
                    {
                    try
                        {
                        callback.accept(fromByteString(e.getKey()), fromByteString(e.getValue()));
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return null;
                    };
                FutureStreamObserver observer = new FutureStreamObserver<>(future, VOID, function);
                ByteString                        filter   = toByteString(AlwaysFilter.INSTANCE());
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, filter, toByteString(processor)),
                                  observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    public  CompletableFuture invokeAll(Collection colKeys,
                                                 InvocableMap.EntryProcessor processor,
                                                 BiConsumer callback)
        {
        return executeIfActive(() ->
            {
            try
                {
                CompletableFuture       future   = new CompletableFuture<>();
                BiFunction function = (e, v) ->
                    {
                    try
                        {
                        callback.accept(fromByteString(e.getKey()), fromByteString(e.getValue()));
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return null;
                    };
                FutureStreamObserver observer = new FutureStreamObserver<>(future, VOID, function);
                Collection            keys     = colKeys.stream()
                        .map(this::toByteString)
                        .collect(Collectors.toList());

                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, keys, toByteString(processor)), observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
            });
        }

    @Override
    @SuppressWarnings({"rawtypes"})
    public  CompletableFuture invokeAll(Filter filter,
                                                 InvocableMap.EntryProcessor processor,
                                                 BiConsumer callback)
        {
        return executeIfActive(() ->
        {
            try
                {
                CompletableFuture       future   = new CompletableFuture<>();
                BiFunction function = (e, v) ->
                    {
                    try
                        {
                        callback.accept(fromByteString(e.getKey()), fromByteString(e.getValue()));
                        }
                    catch (Throwable ex)
                        {
                        future.completeExceptionally(ex);
                        }
                    return VOID;
                    };
                FutureStreamObserver observer = new FutureStreamObserver<>(future, VOID, function);
                invokeAllInternal(Requests.invokeAll(f_sScopeName, f_sCacheName, f_sFormat, toByteString(filter), toByteString(processor)), observer);
                return future;
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
        });
        }

    @Override
    public CompletableFuture isEmpty()
        {
        return executeIfActive(() -> f_service.isEmpty(Requests.isEmpty(f_sScopeName, f_sCacheName))
                .thenApply(BoolValue::getValue)
                .toCompletableFuture());
        }

    @Override
    public CompletableFuture> keySet()
        {
        return executeIfActive(() -> CompletableFuture.completedFuture(new RemoteKeySet<>(this)));
        }

    @Override
    public CompletableFuture put(K key, V value)
        {
        return putInternal(key, value, CacheMap.EXPIRY_DEFAULT).thenApply(v -> VOID);
        }

    @Override
    public CompletableFuture put(K key, V value, long ttl)
        {
        return putInternal(key, value, ttl).thenApply(v -> VOID);
        }

    @Override
    public CompletableFuture putIfAbsent(K key, V value)
        {
        return executeIfActive(() -> f_service.putIfAbsent(Requests.putIfAbsent(f_sScopeName, f_sCacheName, f_sFormat,
                                                                                toByteString(key), toByteString(value)))
                .thenApplyAsync(this::valueFromBytesValue)
                .toCompletableFuture());
        }

    @Override
    public CompletableFuture putAll(Map map)
        {
        return putAllInternal(map).thenApply(v -> VOID);
        }

    @Override
    public CompletableFuture remove(K key)
        {
        return removeInternal(key);
        }

    @Override
    public CompletableFuture remove(K key, V value)
        {
        return removeInternal(key, value);
        }

    @Override
    public CompletableFuture replace(K key, V value)
        {
        return executeIfActive(() -> f_service.replace(Requests.replace(f_sScopeName, f_sCacheName, f_sFormat,
                                                                        toByteString(key), toByteString(value)))
                .thenApplyAsync(this::valueFromBytesValue)
                .toCompletableFuture());
        }

    @Override
    public CompletableFuture replace(K key, V oldValue, V newValue)
        {
        return executeIfActive(() -> f_service.replaceMapping(
                Requests.replace(f_sScopeName, f_sCacheName, f_sFormat, toByteString(key),
                                 toByteString(oldValue), toByteString(newValue)))
                .thenApplyAsync(BoolValue::getValue)
                .toCompletableFuture());
        }

    @Override
    public CompletableFuture size()
        {
        return executeIfActive(() -> f_service.size(Requests.size(f_sScopeName, f_sCacheName))
                .thenApply(Int32Value::getValue)
                .toCompletableFuture());
        }

    @Override
    public CompletableFuture> values()
        {
        return executeIfActive(() -> CompletableFuture.completedFuture(new RemoteValues<>(this)));
        }

    @Override
    @SuppressWarnings({"rawtypes"})
    public CompletableFuture> values(Filter filter, Comparator comparator)
        {
        return executeIfActive(() -> valuesInternal(filter, comparator));
        }

    // ----- helper methods -------------------------------------------------

    /**
     * Helper method to obtain a {@link Collection} of {@link Entry} instances for the specified keys
     * as a {@link Stream}.
     *
     * @param colKeys  the keys to look up
     *
     * @return a {@link Stream} of {@link Entry} instances for the keys found in the cache
     */
    protected Stream getAllInternal(Collection colKeys)
        {
        assertActive();
        if (colKeys.isEmpty())
            {
            return Stream.empty();
            }
        else
            {
            List keys = colKeys.stream()
                    .map(this::toByteString)
                    .collect(Collectors.toList());
            return f_service.getAll(Requests.getAll(f_sScopeName, f_sCacheName, f_sFormat, keys));
            }
        }

    /**
     * Helper method to obtain a {@link Map} of the keys/values found within the cache matching
     * the provided {@link Collection} of keys.
     *
     * @param colKeys  the keys to look up
     *
     * @return a {@link Map} containing keys/values for the keys found within the cache
     */
    @SuppressWarnings("unchecked")
    protected Map getAllInternalAsMap(Collection colKeys)
        {
        assertActive();
        return getAllInternal(colKeys)
                .map(e -> (Map.Entry) new SimpleMapEntry<>(fromByteString(e.getKey()), fromByteString(e.getValue())))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        }

    /**
     * Returns the scope name.
     *
     * @return the scope name
     */
    protected String getScopeName()
        {
        return f_sScopeName;
        }

    /**
     * Returns the cache name.
     *
     * @return the cache name
     */
    protected String getCacheName()
        {
        return f_sCacheName;
        }

    /**
     * Get the {@link CacheService} associated with this cache.
     *
     * @return the {@link CacheService} associated with this cache
     */
    protected CacheService getCacheService()
        {
        return m_cacheService;
        }

    /**
     * Set the {@link CacheService} associated with this cache.
     *
     * @param cacheService  the {@link CacheService} associated with this cache
     */
    protected void setCacheService(GrpcRemoteCacheService cacheService)
        {
        m_cacheService = cacheService;
        }

    /**
     * Helper method for getting a value from the cache.
     *
     * @param key           the key
     * @param defaultValue  the value to return if this cache doesn't contain the provided key
     *
     * @return the value within the Cache, or {@code defaultValue}
     */
    protected CompletableFuture getInternal(Object key, V defaultValue)
        {
        return executeIfActive(() -> f_service.get(Requests.get(f_sScopeName, f_sCacheName, f_sFormat, toByteString(key)))
                .thenApplyAsync(optional -> this.valueFromOptionalValue(optional, defaultValue))
                .toCompletableFuture());
        }

    /**
     * Return the synchronous cache client.
     *
     * @return the synchronous cache client
     */
    public NamedCacheClient getNamedCacheClient()
        {
        return f_synchronousCache;
        }

    /**
     * Helper method to perform invokeAll operations.
     *
     * @param request   the {@link InvokeAllRequest}
     * @param observer  the {@link StreamObserver}
     */
    protected void invokeAllInternal(InvokeAllRequest request, StreamObserver observer)
        {
        assertActive();

        f_service.invokeAll(request, observer);
        }

    /**
     * Return {@code true} if the cache is still active.
     *
     * @return {@code true} if the cache is still active
     */
    protected CompletableFuture isActive()
        {
        return CompletableFuture.completedFuture(isActiveInternal());
        }

    /**
     * Return {@code true} if the cache has been released.
     *
     * @return {@code true} if the cache has been released
     */
    protected boolean isReleased()
        {
        return m_fReleased;
        }

    /**
     * Return {@code true} if the cache has been destroyed.
     *
     * @return {@code true} if the cache has been destroyed
     */
    protected boolean isDestroyed()
        {
        return m_fDestroyed;
        }

    /**
     * Helper method for cache active checks.
     *
     * @return {@code true} if the cache is still active
     */
    public boolean isActiveInternal()
        {
        return !m_fReleased && !m_fDestroyed;
        }

    /**
     * Helper method for storing a key/value pair within the cache.
     *
     * @param key    the key
     * @param value  the value
     * @param cTtl   the time-to-live (may not be supported)
     *
     * @return a {@link CompletableFuture} returning the value previously associated
     *         with the specified key, if any
     */
    protected CompletableFuture putInternal(K key, V value, long cTtl)
        {
        return executeIfActive(() -> f_service.put(Requests.put(f_sScopeName, f_sCacheName, f_sFormat,
                                                                toByteString(key), toByteString(value), cTtl))
                .thenApplyAsync(this::valueFromBytesValue)
                .toCompletableFuture());
        }

    /**
     * Helper method for storing the contents of the provided map within the cache.
     *
     * @param map  the {@link Map} of key/value pairs to store in the cache.
     *
     * @return a {@link CompletableFuture}
     */
    protected CompletableFuture putAllInternal(Map map)
        {
        return executeIfActive(() ->
        {
            try
                {
                List entries = new ArrayList<>();
                for (Map.Entry entry : map.entrySet())
                    {
                    entries.add(Entry.newBuilder()
                                        .setKey(toByteString(entry.getKey()))
                                        .setValue(toByteString(entry.getValue())).build());
                    }
                return f_service.putAll(Requests.putAll(f_sScopeName, f_sCacheName, f_sFormat, entries)).toCompletableFuture();
                }
            catch (Throwable t)
                {
                return failedFuture(t);
                }
        });
        }

    /**
     * Releases the cache.
     *
     * @return a {@link CompletableFuture} returning {@link Void}
     */
    public CompletableFuture release()
        {
        return executeIfActive(() -> releaseInternal(false));
        }

     protected  CompletableFuture removeIndex(ValueExtractor valueExtractor)
        {
        return f_service.removeIndex(Requests.removeIndex(f_sScopeName, f_sCacheName, f_sFormat, toByteString(valueExtractor)))
                .thenApply(e -> VOID).toCompletableFuture();
        }

    /**
     * Remove the mapping for the given key, returning the associated value.
     *
     * @param key  key whose mapping is to be removed from the map
     *
     * @return the value associated with the given key, or {@code null} if no mapping existed
     */
    protected CompletableFuture removeInternal(Object key)
        {
        return executeIfActive(() -> f_service.remove(Requests.remove(f_sScopeName, f_sCacheName, f_sFormat, toByteString(key)))
                .thenApplyAsync(this::valueFromBytesValue)
                .toCompletableFuture());
        }

    /**
     * Removes the entry for the specified key only if it is currently
     * mapped to the specified value.
     *
     * @param key    key with which the specified value is associated
     * @param value  value expected to be associated with the specified key
     *
     * @return a {@link CompletableFuture} returning a {@code boolean} indicating
     *         whether the mapping was removed
     */
    protected CompletableFuture removeInternal(Object key, Object value)
        {
        return executeIfActive(() -> f_service.removeMapping(Requests.remove(f_sScopeName, f_sCacheName, f_sFormat,
                                                                             toByteString(key), toByteString(value)))
                                               .thenApplyAsync(BoolValue::getValue)
                                               .toCompletableFuture());
        }

    /**
     * Removes the specified {@link MapListener}.
     *
     * @param mapListener  the {@link MapListener} to remove
     *
     * @return a {@link CompletableFuture} returning {@link Void}
     */
    protected CompletableFuture removeMapListener(MapListener mapListener)
        {
        return removeMapListener(mapListener, (Filter) null);
        }

    /**
     * Removes the {@link MapListener} associated with the specified key.
     *
     * @param listener  the {@link MapListener} to remove
     * @param key       the key the listener is associated with
     *
     * @return a {@link CompletableFuture} returning {@link Void}
     */
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    protected CompletableFuture removeMapListener(MapListener listener, K key)
        {
        return executeIfActive(() ->
            {
            CompletableFuture future  = new CompletableFuture<>();
            MapListenerSupport      support = getMapListenerSupport();
            synchronized (support)
                {
                support.removeListener(listener, key);
                boolean fEmpty   = support.isEmpty(key);
                boolean fPriming = MapListenerSupport.isPrimingListener(listener);

                // "priming" request should be sent regardless
                if (fEmpty || fPriming)
                    {
                    String uid = "";
                    try
                        {
                        MapListenerRequest request = Requests.removeKeyMapListener(f_sScopeName, f_sCacheName,
                                f_sFormat, toByteString(key), fPriming, ByteString.EMPTY);

                        uid = request.getUid();
                        f_mapFuture.put(uid, future);
                        m_evtRequestObserver.onNext(request);
                        }
                    catch (RuntimeException e)
                        {
                        f_mapFuture.remove(uid);
                        future.completeExceptionally(e);
                        }
                    }
                else
                    {
                    // we're not actually adding the listener to the server so complete the future now.
                    future.complete(VOID);
                    }
                }

            return future;
            });
        }

    /**
     * Removes the {@link MapListener} associated with the specified {@link Filter}.
     *
     * @param listener  the {@link MapListener} to remove
     * @param filter    the filter the listener is associated with
     *
     * @return a {@link CompletableFuture} returning {@link Void}
     */
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    protected CompletableFuture removeMapListener(MapListener listener, Filter filter)
        {
        return executeIfActive(() ->
            {
            if (listener instanceof NamedCacheDeactivationListener && filter == null)
                {
                synchronized (this)
                    {
                    if (f_listCacheDeactivationListeners.remove(listener))
                        {
                        f_cListener.decrementAndGet();
                        }
                    }
                return CompletableFuture.completedFuture(VOID);
                }

            if (listener instanceof MapTriggerListener)
                {
                MapTriggerListener triggerListener = (MapTriggerListener) listener;
                return removeRemoteFilterListener(ByteString.EMPTY, 0L, toByteString(triggerListener.getTrigger()));
                }

            MapListenerSupport support = getMapListenerSupport();
            synchronized (support)
                {
                long nId = getFilterId(filter);
                support.removeListener(listener, filter);
                if (support.isEmpty(filter))
                    {
                    return removeRemoteFilterListener(toByteString(filter), nId, ByteString.EMPTY);
                    }
                }
            return CompletableFuture.completedFuture(VOID);
            });
        }

    /**
     * Sends the serialized {@link Filter} for un-registration with the cache server.
     *
     * @param filterBytes   the serialized bytes of the {@link Filter}
     * @param nFilterId     the ID of the {@link Filter}
     * @param triggerBytes  the serialized bytes of the {@link MapTriggerListener}; pass {@link ByteString#EMPTY}
     *                      if there is no trigger listener.
     *
     * @return {@link CompletableFuture} returning type {@link Void}
     */
    protected CompletableFuture removeRemoteFilterListener(ByteString filterBytes,
            long nFilterId, ByteString triggerBytes)
        {
        String uid = "";
        CompletableFuture future = new CompletableFuture<>();
        try
            {
            MapListenerRequest request = Requests
                    .removeFilterMapListener(f_sScopeName, f_sCacheName, f_sFormat, filterBytes, nFilterId,
                                             false, false, triggerBytes);
            uid = request.getUid();
            f_mapFuture.put(uid, future);
            m_evtRequestObserver.onNext(request);
            }
        catch (RuntimeException e)
            {
            f_mapFuture.remove(uid);
            future.completeExceptionally(e);
            }

        return future;
        }

    /**
     * {@link Stream} all entries within the cache.
     *
     * @return a {@link Stream} for processing entries within the cache.
     */
    protected Stream> stream()
        {
        assertActive();
        return stream(AlwaysFilter.INSTANCE());
        }

    /**
     * {@link Stream} which filters the cache entries.
     *
     * @param filter  the filter to apply to the {@link Stream}
     *
     * @return a {@link Stream} for processing entries within the cache.
     *
     * @throws UnsupportedOperationException this method is unsupported
     */
    @SuppressWarnings("unused")
    protected Stream> stream(Filter filter)
        {
        assertActive();
        throw new UnsupportedOperationException("method not implemented");
        }

    /**
     * Removes all mappings from this map.
     * 

* Note: the removal of entries caused by this truncate operation will * not be observable. This includes any registered {@link com.tangosol.util.MapListener * listeners}, {@link com.tangosol.util.MapTrigger triggers}, or {@link * com.tangosol.net.events.EventInterceptor interceptors}. However, a * {@link com.tangosol.net.events.partition.cache.CacheLifecycleEvent CacheLifecycleEvent} * is raised to notify subscribers of the execution of this operation. * * @return a {@link CompletableFuture} returning {@link Void} */ protected CompletableFuture truncate() { return executeIfActive(() -> f_service.truncate(Requests.truncate(f_sScopeName, f_sCacheName)) .thenApply(e -> VOID).toCompletableFuture()); } /** * Helper method to return the values of the cache. * * @param filter the {@link Filter} to apply * @param comparator the {@link Comparator} for ordering * * @return a {@link Collection} of values based on the provided {@link Filter} and {@link Comparator} */ @SuppressWarnings({"rawtypes", "unchecked"}) protected CompletableFuture> valuesInternal(Filter filter, Comparator comparator) { return this.values(filter).thenApply((colValues) -> { List values = new ArrayList<>(colValues); values.sort(comparator); return values; }); } /** * Helper method for {@link #containsKey(Object)} invocations. * * @param oKey the key to check * * @return a {@link CompletableFuture} returning {@code true} if the cache contains the given key */ protected CompletableFuture containsKeyInternal(Object oKey) { return executeIfActive(() -> f_service.containsKey(Requests.containsKey(f_sScopeName, f_sCacheName, f_sFormat, toByteString(oKey))) .thenApplyAsync(BoolValue::getValue) .toCompletableFuture()); } /** * Helper method for confirm presence of the value within the cache. * * @param oValue the value to check * * @return a {@link CompletableFuture} returning {@code true} if the cache contains the given value */ protected CompletableFuture containsValue(Object oValue) { return executeIfActive(() -> f_service.containsValue(Requests.containsValue(f_sScopeName, f_sCacheName, f_sFormat, toByteString(oValue))) .thenApplyAsync(BoolValue::getValue) .toCompletableFuture()); } /** * Destroys the cache. * * @return a {@link CompletableFuture} returning {@link Void} */ protected CompletableFuture destroy() { return executeIfActive(() -> releaseInternal(true)); } /** * Return the {@link MapListenerSupport}. * * @return the {@link MapListenerSupport} */ protected MapListenerSupport getMapListenerSupport() { return m_listenerSupport; } /** * Called by the constructor to initialize event support. */ protected void initEvents() { MapListenerRequest request = MapListenerRequest.newBuilder() .setScope(f_sScopeName) .setCache(f_sCacheName) .setUid(UUID.randomUUID().toString()) .setSubscribe(true) .setFormat(f_sFormat) .setType(MapListenerRequest.RequestType.INIT) .build(); m_evtResponseObserver = new EventStreamObserver(request.getUid()); m_evtRequestObserver = f_service.events(m_evtResponseObserver); m_listenerSupport = new MapListenerSupport(); m_aEvtFilter = new SparseArray<>(); // initialise the bidirectional stream so that this client will receive // destroy and truncate events m_evtRequestObserver.onNext(request); // create a future to allow us to wait for the init request to complete CompletableFuture future = m_evtResponseObserver.whenSubscribed().toCompletableFuture(); // handle subscription completion errors future.handle((v, err) -> { if (err != null) { // close the channel if subscription failed m_evtRequestObserver.onCompleted(); } return null; }); // wait for the init request to complete future.join(); } /** * Return the serialization format. * * @return the serialization format */ protected String getFormat() { return f_sFormat; } /** * Obtain a page of cache keys. * * @param cookie an opaque cooke used to determine the current page * * @return a page of cache keys */ protected Stream getKeysPage(BytesValue cookie) { assertActive(); ByteString s = cookie == null ? null : cookie.getValue(); return f_service.nextKeySetPage(Requests.page(f_sScopeName, f_sCacheName, f_sFormat, s)); } /** * Obtain a page of cache entries. * * @param cookie an opaque cooke used to determine the current page * * @return a page of cache entries */ protected Stream getEntriesPage(ByteString cookie) { assertActive(); return f_service.nextEntrySetPage(Requests.page(f_sScopeName, f_sCacheName, f_sFormat, cookie)); } /** * Determine whether en entry exists in the cache with the specified key and value. * * @param key the cache key * @param value the cache value * * @return a page of cache keys */ protected boolean containsEntry(K key, V value) { assertActive(); try { ContainsEntryRequest request = Requests.containsEntry(f_sScopeName, f_sCacheName, f_sFormat, toByteString(key), toByteString(value)); CompletionStage stage = f_service.containsEntry(request); BoolValue boolValue = stage.toCompletableFuture().get(); return boolValue != null && boolValue.getValue(); } catch (InterruptedException | ExecutionException e) { throw new RequestIncompleteException(e); } } /** * A utility method to serialize an Object to {@link BytesValue}. * * @param obj the object to convert to {@link BytesValue}. * * @return the {@link BytesValue} for the specified object */ protected BytesValue toBytesValue(Object obj) { return BytesValue.of(toByteString(obj)); } /** * A utility method to serialize an Object to {@link ByteString}. * * @param obj the object to convert to {@link ByteString}. * * @return the {@link ByteString} for the specified object */ protected ByteString toByteString(Object obj) { return UnsafeByteOperations.unsafeWrap(ExternalizableHelper.toBinary(obj, f_serializer).toByteBuffer()); } /** * A utility method to deserialize an {@link OptionalValue} to an Object. *

* If no value is present in the {@link OptionalValue} then the default * value is returned. * * @param optional the {@link OptionalValue} to deserialize an Object. * @param defaultValue the value to return if the {link @OptionalValue} does * not contain a value * * @return an object from the specified {@link BytesValue} */ protected V valueFromOptionalValue(OptionalValue optional, V defaultValue) { if (optional.getPresent()) { return fromByteString(optional.getValue()); } return defaultValue; } /** * A utility method to deserialize a {@link BytesValue} to an Object. * * @param bv the {@link BytesValue} to deserialize an Object. * * @return an object from the specified {@link BytesValue} */ protected V valueFromBytesValue(BytesValue bv) { return fromBytesValue(bv); } /** * A utility method to deserialize a {@link BytesValue} to an Object. * * @param bytes the {@link BytesValue} to deserialize an Object. * * @return an object from the specified {@link BytesValue} */ protected T fromBytesValue(BytesValue bytes) { return BinaryHelper.fromBytesValue(bytes, f_serializer); } /** * A utility method to deserialize a {@link ByteString} to an Object. * * @param bytes the {@link ByteString} to deserialize an Object. * * @return an object from the specified {@link ByteString} */ protected T fromByteString(ByteString bytes) { return BinaryHelper.fromByteString(bytes, f_serializer); } protected CompletableFuture failedFuture(Throwable t) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(t); return future; } /** * Assert that this {@link AsyncNamedCacheClient} is active. * * @throws IllegalStateException if this {@link AsyncNamedCacheClient} * is not active */ protected void assertActive() { if (m_fReleased || m_fDestroyed) { String reason = m_fDestroyed ? "destroyed" : "released"; throw new IllegalStateException("remote cache '" + f_sCacheName + "' has been " + reason); } } /** * If this {@link AsyncNamedCacheClient} is active then return the {@link CompletableFuture} * supplied by the supplier otherwise return a failed {@link CompletableFuture}. */ protected CompletableFuture executeIfActive(Supplier> supplier) { if (!m_fReleased && !m_fDestroyed) { try { return supplier.get(); } catch (Throwable t) { return failedFuture(t); } } else { String reason = m_fDestroyed ? "destroyed" : "released"; return failedFuture(new IllegalStateException("remote cache '" + f_sCacheName + "' has been " + reason)); } } /** * Release or destroy this {@link AsyncNamedCacheClient}, notifying any registered * {@link DeactivationListener DeactivationListeners}. * * @param destroy {@code true} to destroy the cache, {@code false} to release * the cache * * @return a {@link CompletableFuture} that will be complete when the operation * is completed. */ protected synchronized CompletableFuture releaseInternal(boolean destroy) { CompletableFuture future; // close the events bidirectional channel and any event listeners if (m_evtRequestObserver != null) { m_evtRequestObserver.onCompleted(); } f_cListener.set(0); if (!m_fDestroyed && !m_fReleased) { if (destroy) { m_fDestroyed = true; future = f_service.destroy(Requests.destroy(f_sScopeName, f_sCacheName)).thenApply(e -> VOID).toCompletableFuture(); } else { m_fReleased = true; future = CompletableFuture.completedFuture(VOID); } } else { future = CompletableFuture.completedFuture(VOID); } return future.handleAsync((v, err) -> { for (DeactivationListener> listener : f_listDeactivationListeners) { try { if (destroy) { listener.destroyed(this); } else { listener.released(this); } } catch (Throwable t) { t.printStackTrace(); } } f_listDeactivationListeners.clear(); CacheEvent evt = createDeactivationEvent(/*destroyed*/true); for (NamedCacheDeactivationListener listener : f_listCacheDeactivationListeners) { try { listener.entryDeleted(evt); } catch (Throwable t) { t.printStackTrace(); } } f_listCacheDeactivationListeners.clear(); if (err != null) { throw Base.ensureRuntimeException(err); } return VOID; }); } /** * Helper method to create a new {@link CacheEvent} to signal cache truncation or destruction. * * @param destroyed {@code true} if this event should represent a cache being destroyed * @param the {@link CacheEvent} key type * @param the {@link CacheEvent} value type * * @return a new {@link CacheEvent} to signal cache truncation or destruction */ @SuppressWarnings({"unchecked", "rawtypes"}) protected CacheEvent createDeactivationEvent(boolean destroyed) { return new CacheEvent(getNamedCache(), destroyed ? CacheEvent.ENTRY_DELETED : CacheEvent.ENTRY_UPDATED, null, null, null, true); } /** * Add a {@link DeactivationListener} that will be notified when this * {@link AsyncNamedCacheClient} is released or destroyed. * * @param listener the listener to add */ public synchronized void addDeactivationListener(DeactivationListener> listener) { assertActive(); if (listener != null) { f_listDeactivationListeners.add(listener); } } /** * Add a {@link NamedCacheDeactivationListener} that will be notified when this * {@link AsyncNamedCacheClient} is released or destroyed. * * @param listener the listener to add */ public synchronized void addDeactivationListener(NamedCacheDeactivationListener listener) { if (listener != null) { if (m_fReleased || m_fDestroyed) { listener.entryDeleted(createDeactivationEvent(m_fDestroyed)); } else { f_listCacheDeactivationListeners.add(listener); } } } /** * Remove a {@link DeactivationListener}. * * @param listener the listener to remove */ protected synchronized void removeDeactivationListener(DeactivationListener> listener) { if (listener != null) { f_listDeactivationListeners.remove(listener); } } /** * Asynchronous implementation of {@link NamedCache#addIndex}. * * @param extractor the ValueExtractor object that is used to extract an * indexable Object from a value stored in the indexed * Map. Must not be {@code null} * @param fOrdered true iff the contents of the indexed information should * be ordered; false otherwise * @param comparator the Comparator object which imposes an ordering on * entries in the indexed map; or null if the * entries' values natural ordering should be used * @param the type of the value to extract from * @param the type of value that will be extracted * * @return {@link CompletableFuture} returning type {@link Void} */ protected CompletableFuture addIndex(ValueExtractor extractor, boolean fOrdered, Comparator comparator) { return executeIfActive(() -> { ByteString serializedExtractor = toByteString(extractor); if (comparator == null) { return f_service.addIndex(Requests.addIndex(f_sScopeName, f_sCacheName, f_sFormat, serializedExtractor, fOrdered)) .thenApply(e -> VOID).toCompletableFuture(); } else { return f_service.addIndex(Requests.addIndex(f_sScopeName, f_sCacheName, f_sFormat, serializedExtractor, fOrdered, toByteString(comparator))) .thenApply(e -> VOID).toCompletableFuture(); } }); } /** * Add a standard map listener that will receive all events (inserts, * updates, deletes) that occur against the map, with the key, old-value * and new-value included * * @param mapListener the {@link MapEvent} listener to add * * @return {@link CompletableFuture} returning type {@link Void} */ protected CompletableFuture addMapListener(MapListener mapListener) { return addMapListener(mapListener, (Filter) null, false); } /** * Add a map listener for a specific key. *

* The listeners will receive MapEvent objects, but if fLite is passed as * true, they might not contain the OldValue and NewValue * properties. * * @param mapListener the {@link MapEvent} listener to add * @param key the key that identifies the entry for which to raise * events * @param fLite {@code true} to indicate that the {@link MapEvent} objects do * not have to include the OldValue and NewValue * property values in order to allow optimizations * * @return {@link CompletableFuture} returning type {@link Void} */ protected CompletableFuture addMapListener(MapListener mapListener, K key, boolean fLite) { return executeIfActive(() -> { try { return addKeyMapListener(mapListener, key, fLite); } catch (Exception e) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(e); return future; } }); } /** * Add a map listener that receives events based on a filter evaluation. *

* The listeners will receive MapEvent objects, but if fLite is passed as * true, they might not contain the OldValue and NewValue * properties. * * @param mapListener the {@link MapEvent} listener to add * @param filter a filter that will be passed {@link MapEvent} objects to select * from; a {@link MapEvent} will be delivered to the listener only * if the filter evaluates to {@code true} for that {@link MapEvent} (see * {@link com.tangosol.util.filter.MapEventFilter}); * {@code null} is equivalent to a filter that always returns {@code true} * @param fLite {@code true} to indicate that the {@link MapEvent} objects do * not have to include the OldValue and NewValue * property values in order to allow optimizations * * @return {@link CompletableFuture} returning type {@link Void} */ protected CompletableFuture addMapListener(MapListener mapListener, Filter filter, boolean fLite) { return executeIfActive(() -> { try { return addFilterMapListener(mapListener, filter, fLite); } catch (Exception e) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(e); return future; } }); } /** * Add a map listener for a specific key. *

* The listeners will receive MapEvent objects, but if {@code fLite} is passed as * {@code true}, they might not contain the OldValue and NewValue * properties. * * @param listener the {@link MapEvent} listener to add * @param key the key that identifies the entry for which to raise * events * @param fLite {@code true} to indicate that the {@link MapEvent} * objects do not have to include the OldValue and NewValue * property values in order to allow optimizations * * @return {@link CompletableFuture} returning type {@link Void} */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") protected CompletableFuture addKeyMapListener(MapListener listener, Object key, boolean fLite) { CompletableFuture future = new CompletableFuture<>(); MapListenerSupport support = getMapListenerSupport(); boolean first = support.addListenerWithCheck(listener, key, fLite); boolean priming = MapListenerSupport.isPrimingListener(listener); // "priming" request should be sent regardless if (first || priming) { String uid = ""; try { MapListenerRequest request = Requests .addKeyMapListener(f_sScopeName, f_sCacheName, f_sFormat, toByteString(key), fLite, priming, ByteString.EMPTY); uid = request.getUid(); f_mapFuture.put(uid, future); m_evtRequestObserver.onNext(request); } catch (RuntimeException e) { synchronized (support) { support.removeListener(listener, key); f_mapFuture.remove(uid); } future.completeExceptionally(e); } } else { // we're not actually adding the listener to the server so complete the future now. future.complete(VOID); } return future; } /** * Add a map listener that receives events based on a filter evaluation. *

* The listeners will receive MapEvent objects, but if {@code fLite} is passed as * {@code true}, they might not contain the OldValue and NewValue * properties. * * @param listener the {@link MapEvent} listener to add * @param filter a filter that will be passed {@link MapEvent} objects to select * from; a {@link MapEvent} will be delivered to the listener only * if the filter evaluates to {@code true} for that {@link MapEvent} (see * {@link com.tangosol.util.filter.MapEventFilter}); * {@code null} is equivalent to a filter that always returns {@code true} * @param fLite {@code true} to indicate that the {@link MapEvent} objects do * not have to include the OldValue and NewValue * property values in order to allow optimizations * * @return {@link CompletableFuture} returning type {@link Void} */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") protected CompletableFuture addFilterMapListener(MapListener listener, Filter filter, boolean fLite) { if (listener instanceof NamedCacheDeactivationListener) { synchronized (this) { if (f_listCacheDeactivationListeners.add((NamedCacheDeactivationListener) listener)) { f_cListener.incrementAndGet(); } } return CompletableFuture.completedFuture(VOID); } if (listener instanceof MapTriggerListener) { MapTriggerListener triggerListener = (MapTriggerListener) listener; return addRemoteFilterListener(ByteString.EMPTY, 0L, fLite, toByteString(triggerListener.getTrigger())); } boolean wasEmpty; boolean first; long filterId; CompletableFuture future; MapListenerSupport support = getMapListenerSupport(); synchronized (support) { wasEmpty = support.isEmpty(filter); first = support.addListenerWithCheck(listener, filter, fLite); filterId = wasEmpty ? registerFilter(filter) : getFilterId(filter); } if (wasEmpty || first) { future = addRemoteFilterListener(toByteString(filter), filterId, fLite, ByteString.EMPTY); if (future.isCompletedExceptionally()) { synchronized (support) { if (wasEmpty) { m_aEvtFilter.remove(filterId); } support.removeListener(listener, filter); } } } else { // we're not actually adding the listener to the server so complete the future now. future = CompletableFuture.completedFuture(VOID); } return future; } /** * Sends the serialized {@link Filter} for registration with the cache server. * * @param filterBytes the serialized bytes of the {@link Filter} * @param nFilterId the ID of the {@link Filter} * @param fLite {@code true} to indicate that the {@link MapEvent} objects do * not have to include the OldValue and NewValue * property values in order to allow optimizations * @param triggerBytes the serialized bytes of the {@link MapTriggerListener}; pass {@link ByteString#EMPTY} * if there is no trigger listener. * * @return {@link CompletableFuture} returning type {@link Void} */ protected CompletableFuture addRemoteFilterListener(ByteString filterBytes, long nFilterId, boolean fLite, ByteString triggerBytes) { String uid = ""; CompletableFuture future = new CompletableFuture<>(); try { MapListenerRequest request = Requests .addFilterMapListener(f_sScopeName, f_sCacheName, f_sFormat, filterBytes, nFilterId, fLite, false, triggerBytes); uid = request.getUid(); f_mapFuture.put(uid, future); m_evtRequestObserver.onNext(request); } catch (RuntimeException e) { f_mapFuture.remove(uid); future.completeExceptionally(e); } return future; } /** * Register the {@link Filter} locally. * * @param filter the {@link Filter} * * @return the registration ID of the {@link Filter} */ protected long registerFilter(Filter filter) { if (m_aEvtFilter.isEmpty()) { m_aEvtFilter.set(1, filter); return 1L; } else { return m_aEvtFilter.add(filter); } } /** * Obtain the registration ID for the provided filter. * * @param filter the filter whose ID it is to look up * * @return the registration ID or zero if the filter hasn't been registered */ @SuppressWarnings("rawtypes") protected long getFilterId(Filter filter) { for (Iterator iter = m_aEvtFilter.iterator(); iter.hasNext(); ) { Filter filterThat = (Filter) iter.next(); if (Base.equals(filter, filterThat)) { return iter.getIndex(); } } return 0L; } /** * Dispatch the received {@link MapEventResponse} for processing by local listeners. *

* This method is taken from code in the TDE RemoteNamedCache. * * @param response the {@link MapEventResponse} to process */ @SuppressWarnings({"unchecked", "rawtypes", "SynchronizationOnLocalVariableOrMethodParameter", "ConstantConditions"}) protected void dispatch(MapEventResponse response) { List listFilterIds = response.getFilterIdsList(); int cFilters = listFilterIds == null ? 0 : listFilterIds.size(); int nEventId = response.getId(); Object oKey = fromByteString(response.getKey()); Object oValueOld = fromByteString(response.getOldValue()); Object oValueNew = fromByteString(response.getNewValue()); boolean fSynthetic = response.getSynthetic(); boolean fPriming = response.getPriming(); MapListenerSupport support = getMapListenerSupport(); TransformationState transformState = TransformationState.valueOf(response.getTransformationState().toString()); CacheEvent evt = null; // collect key-based listeners Listeners listeners = transformState == TransformationState.TRANSFORMED ? null : support.getListeners(oKey); if (cFilters > 0) { LongArray> laFilters = m_aEvtFilter; List> listFilters = null; // collect filter-based listeners synchronized (support) { for (int i = 0; i < cFilters; i++) { long lFilterId = listFilterIds.get(i); if (laFilters.exists(lFilterId)) { Filter filter = laFilters.get(lFilterId); if (listFilters == null) { listFilters = new ArrayList<>(cFilters - i); // clone the key listeners before merging filter listeners Listeners listenersTemp = new Listeners(); listenersTemp.addAll(listeners); listeners = listenersTemp; } listFilters.add(filter); listeners.addAll(support.getListeners(filter)); } } } if (listFilters != null) { Filter[] aFilters = new Filter[listFilters.size()]; aFilters = listFilters.toArray(aFilters); evt = new MapListenerSupport.FilterEvent(getNamedMap(), nEventId, oKey, oValueOld, oValueNew, fSynthetic, transformState, fPriming, aFilters); } } //noinspection StatementWithEmptyBody if (listeners == null || listeners.isEmpty()) { // we cannot safely remove the orphaned listener because of the following // race condition: if another thread registers a listener for the same key // or filter associated with the event between the time that this thread // detected the orphaned listener, but before either sends a message to the // server, it is possible for this thread to inadvertently remove the new // listener // // since it is only possible for synchronous listeners to be leaked (due to // the extra synchronization in the SafeNamedCache), let's err on the side // of leaking a listener than possibly incorrectly removing a listener // // there is also a valid scenario of a client thread removing an async // listener while the event is already on the wire; hence no logging makes sense } else { if (evt == null) { evt = new CacheEvent(getNamedMap(), nEventId, oKey, oValueOld, oValueNew, fSynthetic, transformState, fPriming); } for (EventListener listener : listeners.listeners()) { EventTask task = new EventTask(evt, (MapListener) listener); if (listener instanceof SynchronousListener) { task.run(); } else { f_executor.execute(task); } } } } /** * Return the number of listeners registered with this map. * * @return the number of listeners registered with this map */ public int getListenerCount() { return f_cListener.get(); } /** * Obtain the default {@link Executor} to be used by the cache. * * @return the default {@link Executor} to be used by the cache */ protected static Executor createDefaultExecutor() { return Executors.newSingleThreadExecutor(); } /** * Obtain the default {@link Serializer} to be used by the cache. * * @return the default {@link Serializer} to be used by the cache */ protected static Serializer createSerializer(String sFormat) { return NamedSerializerFactory.DEFAULT.getNamedSerializer(sFormat, Classes.getContextClassLoader()); } /** * Return the default serialization format. *

* The default format will be Java serialization unless * the {@code coherence.pof.enabled} property is set * when it will be POF. * * @return the default serialization format */ protected static String getDefaultSerializerFormat() { return Config.getBoolean("coherence.pof.enabled") ? "pof" : "java"; } /** * Returns the event dispatcher for this cache. * * @return the event dispatcher for this cache */ protected GrpcCacheLifecycleEventDispatcher getEventDispatcher() { return f_dispatcher; } // ----- inner class: EventTask ----------------------------------------- /** * A simple {@link Runnable} to dispatch an event to a listener. */ @SuppressWarnings("rawtypes") static class EventTask implements Runnable { /** * Create an {@link EventTask}. * * @param event the event to dispatch * @param listener the listener to dispatch the event to */ EventTask(CacheEvent event, MapListener listener) { f_event = event; f_listener = listener; } @Override @SuppressWarnings("unchecked") public void run() { NamedCache cache = (NamedCache) f_event.getSource(); if (cache.isActive()) { try { f_event.dispatch(f_listener); } catch (Throwable thrown) { CacheFactory.err("Caught exception dispatching event to listener"); CacheFactory.err(thrown); } } } // ----- data members ----------------------------------------------- /** * The event to dispatch. */ private final CacheEvent f_event; /** * The listener to dispatch the event to. */ private final MapListener f_listener; } // ----- inner class: InvokeAllBiFunction ------------------------------- /** * A {@link BiFunction} that completes a {@link CompletableFuture}. * * @param key type * @param result type */ protected class InvokeAllBiFunction implements BiFunction, Map> { // ----- constructors ----------------------------------------------- /** * Constructs a new {@code InvokeAllBiFunction}. * * @param future the {@link CompletableFuture} to notify */ protected InvokeAllBiFunction(CompletableFuture> future) { f_future = future; } // ----- BiFunction interface --------------------------------------- @Override public Map apply(Entry e, Map m) { try { m.put(fromByteString(e.getKey()), fromByteString(e.getValue())); return m; } catch (Throwable ex) { f_future.completeExceptionally(ex); } return null; } // ----- data members ----------------------------------------------- /** * The {@link CompletableFuture} to notify */ protected final CompletableFuture> f_future; } // ----- EventStreamObserver ------------------------------------------- /** * A {@code EventStreamObserver} that processes {@link MapListenerResponse}s. */ protected class EventStreamObserver implements StreamObserver { // ----- constructors ----------------------------------------------- /** * Constructs a new EventStreamObserver * * @param uid the event ID to observe */ protected EventStreamObserver(String uid) { f_sUid = Objects.requireNonNull(uid); f_future = new CompletableFuture<>(); } // ----- public methods --------------------------------------------- /** * Subscription {@link CompletionStage}. * * @return a {@link CompletionStage} returning {@link Void} */ public CompletionStage whenSubscribed() { return f_future; } // ----- StreamObserver interface ----------------------------------- @Override public void onNext(MapListenerResponse response) { CompletableFuture future; switch (response.getResponseTypeCase()) { case SUBSCRIBED: MapListenerSubscribedResponse subscribed = response.getSubscribed(); String responseUid = subscribed.getUid(); if (f_sUid.equals(responseUid)) { f_future.complete(VOID); } future = f_mapFuture.remove(responseUid); if (future != null) { future.complete(VOID); } f_cListener.incrementAndGet(); break; case UNSUBSCRIBED: MapListenerUnsubscribedResponse unsubscribed = response.getUnsubscribed(); future = f_mapFuture.remove(unsubscribed.getUid()); if (future != null) { future.complete(VOID); } f_cListener.decrementAndGet(); break; case EVENT: dispatch(response.getEvent()); break; case ERROR: MapListenerErrorResponse error = response.getError(); responseUid = error.getUid(); if (f_sUid.equals(responseUid)) { f_future.completeExceptionally(new RuntimeException(error.getMessage())); } else { future = f_mapFuture.remove(responseUid); if (future != null) { future.completeExceptionally(new RuntimeException(error.getMessage())); } } break; case DESTROYED: if (response.getDestroyed().getCache().equals(f_sCacheName)) { synchronized (this) { if (isActiveInternal()) { m_fDestroyed = true; releaseInternal(true); } } f_cListener.set(0); } break; case TRUNCATED: if (response.getTruncated().getCache().equals(f_sCacheName)) { CacheEvent evt = createDeactivationEvent(/*destroyed*/ false); for (NamedCacheDeactivationListener listener : f_listCacheDeactivationListeners) { try { listener.entryUpdated(evt); } catch (Throwable t) { t.printStackTrace(); } } } break; case RESPONSETYPE_NOT_SET: LOGGER.log(Level.INFO, "Received unexpected event without response type! "); break; default: LOGGER.log(Level.INFO, "Received unexpected event " + response.getEvent()); } } @Override public void onError(Throwable t) { LOGGER.log(Level.SEVERE, t, () -> "Caught exception handling onError"); if (!f_future.isDone()) { f_future.completeExceptionally(t); } } @Override public void onCompleted() { if (!f_future.isDone()) { f_future.completeExceptionally( new IllegalStateException("Event observer completed without subscription")); } } // ----- data members ----------------------------------------------- /** * The event ID. */ protected final String f_sUid; /** * The {@link CompletableFuture} to notify */ protected final CompletableFuture f_future; } // ----- inner class: WrapperDeactivationListener ----------------------- /** * A {@link DeactivationListener} that wraps client {@link NamedCacheDeactivationListener}. */ @SuppressWarnings("unused") protected static class WrapperDeactivationListener implements DeactivationListener> { // ----- constructors ----------------------------------------------- /** * Constructs a new {@code WrapperDeactivationListener}. * * @param listener the {@link MapListener} to wrap */ protected WrapperDeactivationListener(MapListener listener) { m_listener = listener; } // ----- DeactivationListener interface ----------------------------- @Override public void released(AsyncNamedCacheClient client) { } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public void destroyed(AsyncNamedCacheClient client) { CacheEvent evt = client.createDeactivationEvent(true); m_listener.entryDeleted(evt); } // ----- data members ----------------------------------------------- /** * The delegate {@link MapListener}. */ protected final MapListener m_listener; } // ----- Dependencies --------------------------------------------------- /** * The dependencies used to create an {@link AsyncNamedCacheClient}. */ public interface Dependencies { /** * Returns the name of the underlying cache that this * AsyncNamedCacheClient represents. *

* This method must not return {@code null}. * * @return the name of the underlying cache that this * AsyncNamedCacheClient represents */ String getCacheName(); /** * Returns the gRPC {@link Channel}. *

* This method must not return {@code null}. * * @return the gRPC {@link Channel} */ Channel getChannel(); /** * Returns the scope named used to link requests * to a specific server side scope. * * @return the scope named used to for requests */ Optional getScopeName(); /** * Return the {@link Serializer} to use to * serialize request and response payloads. * * @return the {@link Serializer} to use for * request and response payloads */ Optional getSerializer(); /** * Return the name of the serialization format to be used * for requests and responses. * * @return the name of the serialization format to be used * for requests and responses */ Optional getSerializerFormat(); /** * Return the optional {@link NamedCacheGrpcClient} to use. * * @return the optional {@link NamedCacheGrpcClient} to use */ Optional getClient(); /** * Return the optional {@link Executor} to use. * * @return the optional {@link Executor} to use */ Optional getExecutor(); /** * Returns the event dispatcher for the cache. * * @return the event dispatcher for the cache */ GrpcCacheLifecycleEventDispatcher getEventDispatcher(); } // ----- DefaultDependencies ---------------------------------------- /** * The dependencies used to create an {@link AsyncNamedCacheClient}. */ public static class DefaultDependencies implements Dependencies { // ----- constructors ----------------------------------------------- /** * Create a {@link DefaultDependencies}. * * @param sCacheName the name of the underlying cache that * the {@link AsyncNamedCacheClient} will * represent * @param channel the gRPC {@link Channel} to use */ public DefaultDependencies(String sCacheName, Channel channel, GrpcCacheLifecycleEventDispatcher dispatcher) { f_sCacheName = sCacheName; f_channel = channel; m_sScopeName = GrpcDependencies.DEFAULT_SCOPE; f_dispatcher = dispatcher; } // ----- Dependencies methods --------------------------------------- @Override public String getCacheName() { return f_sCacheName; } @Override public Channel getChannel() { return f_channel; } @Override public Optional getScopeName() { return Optional.ofNullable(m_sScopeName); } @Override public Optional getSerializer() { return Optional.ofNullable(m_serializer); } @Override public Optional getSerializerFormat() { return Optional.ofNullable(m_serializerFormat); } @Override public Optional getClient() { return Optional.ofNullable(m_client); } @Override public Optional getExecutor() { return Optional.ofNullable(m_executor); } @Override public GrpcCacheLifecycleEventDispatcher getEventDispatcher() { return f_dispatcher; } // ----- setters ---------------------------------------------------- /** * Set the scope name. * * @param sScopeName the scope name */ public void setScope(String sScopeName) { m_sScopeName = GrpcDependencies.DEFAULT_SCOPE_ALIAS.equals(sScopeName) ? GrpcDependencies.DEFAULT_SCOPE : sScopeName; } /** * Set the optional {@link NamedCacheGrpcClient}. * * @param client the optional {@link NamedCacheGrpcClient} */ public void setClient(NamedCacheGrpcClient client) { m_client = client; } /** * Set the optional {@link Executor} to use instead * of a {@link SimpleDaemonPoolExecutor}. * * @param executor the optional {@link Executor} to use */ public void setExecutor(Executor executor) { m_executor = executor; } /** * Set the serialization format to use for requests and * responses. *

* Calling this method will remove any {@link Serializer} set by calling * {@link #setSerializer(Serializer)} or {@link #setSerializer(Serializer, String)}. *

* A {@link Serializer} configuration with the specified name must exist in the * Coherence Operational Configuration. * * @param sFormat the serialization format and reference to a {@link Serializer} * configuration in the Coherence Operational Configuration */ @SuppressWarnings("unused") public void setSerializerFormat(String sFormat) { m_serializerFormat = sFormat; m_serializer = null; } /** * Set the {@link Serializer} to use for requests and response payloads. *

* The serialization format used will be obtained by calling the * {@link Serializer#getName()} method on the serializer. * * @param serializer the {@link Serializer} to use to serialize request * and response payloads */ public void setSerializer(Serializer serializer) { String sFormat = serializer == null ? null : serializer.getName(); setSerializer(serializer, sFormat); } /** * Set the {@link Serializer} and serialization format name to use for * requests and response payloads. *

* * @param serializer the {@link Serializer} to use to serialize request * and response payloads * @param sFormat the serialization format name */ public void setSerializer(Serializer serializer, String sFormat) { m_serializer = serializer; m_serializerFormat = sFormat; } // ----- data members ----------------------------------------------- /** * The underlying cache name that the {@link AsyncNamedCacheClient} * will represent. */ private final String f_sCacheName; /** * The gRPC {@link Channel} to use to connect to the server. */ private final Channel f_channel; /** * The event dispatcher for the cache. */ private final GrpcCacheLifecycleEventDispatcher f_dispatcher; /** * The server side scope name to use in requests. */ private String m_sScopeName; /** * An optional {@link NamedCacheGrpcClient} to use */ private NamedCacheGrpcClient m_client; /** * An optional {@link Executor} to use. */ private Executor m_executor; /** * The {@link Serializer} to use for request and response payloads. */ private Serializer m_serializer; /** * The name of the serialization format. */ private String m_serializerFormat; } // ----- constants ------------------------------------------------------ /** * Java logging. */ protected static final Logger LOGGER = Logger.getLogger(AsyncNamedCacheClient.class.getName()); /** * A constant value to use for {@link Void} values. */ protected static final Void VOID = null; // ----- data members --------------------------------------------------- /** * The scope name to use for requests. */ protected final String f_sScopeName; /** * The cache name. */ protected final String f_sCacheName; /** * The {@link NamedCacheGrpcClient} to delegate calls. */ protected final NamedCacheGrpcClient f_service; /** * The {@link Serializer} to use. */ protected final Serializer f_serializer; /** * The serialization format. */ protected final String f_sFormat; /** * The synchronous version of this client. */ protected final NamedCacheClient f_synchronousCache; /** * A flag indicating whether this {@link AsyncNamedCacheClient} has been released. */ protected boolean m_fReleased; /** * A flag indicating whether this {@link AsyncNamedCacheClient} has been destroyed. */ protected boolean m_fDestroyed; /** * The list of {@link DeactivationListener} to be notified when this {@link AsyncNamedCacheClient} * is released or destroyed. */ protected final List>> f_listDeactivationListeners; /** * The list of {@link NamedCacheDeactivationListener} instances added to this client. */ protected final List f_listCacheDeactivationListeners = new ArrayList<>(); /** * The {@link Executor} to use to dispatch events. */ protected final Executor f_executor; /** * The client channel for events observer. */ protected StreamObserver m_evtRequestObserver; /** * The event response observer. */ protected EventStreamObserver m_evtResponseObserver; /** * The map listener support. */ protected MapListenerSupport m_listenerSupport; /** * The array of filter. */ protected LongArray> m_aEvtFilter; /** * The map of future keyed by request id. */ protected final Map> f_mapFuture = new ConcurrentHashMap<>(); /** * The event dispatcher for this cache. */ protected final GrpcCacheLifecycleEventDispatcher f_dispatcher; /** * The count of successfully registered {@link MapListener map listeners} */ private final AtomicInteger f_cListener = new AtomicInteger(0); /** * The owing cache service. */ private GrpcRemoteCacheService m_cacheService; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy