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

com.hazelcast.client.proxy.ClientReplicatedMapProxy Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2020, 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.proxy;

import com.hazelcast.client.impl.protocol.ClientMessage;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapAddEntryListenerCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapAddEntryListenerToKeyCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapAddEntryListenerToKeyWithPredicateCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapAddEntryListenerWithPredicateCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapAddNearCacheEntryListenerCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapClearCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapContainsKeyCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapContainsValueCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapEntrySetCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapGetCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapIsEmptyCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapKeySetCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapPutAllCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapPutCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapRemoveCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapRemoveEntryListenerCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapSizeCodec;
import com.hazelcast.client.impl.protocol.codec.ReplicatedMapValuesCodec;
import com.hazelcast.client.spi.ClientContext;
import com.hazelcast.client.spi.ClientProxy;
import com.hazelcast.client.spi.EventHandler;
import com.hazelcast.client.spi.impl.ListenerMessageCodec;
import com.hazelcast.config.InvalidConfigurationException;
import com.hazelcast.config.NearCacheConfig;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryEventType;
import com.hazelcast.core.EntryListener;
import com.hazelcast.core.MapEvent;
import com.hazelcast.core.Member;
import com.hazelcast.core.ReplicatedMap;
import com.hazelcast.internal.nearcache.NearCache;
import com.hazelcast.internal.util.ResultSet;
import com.hazelcast.internal.util.ThreadLocalRandomProvider;
import com.hazelcast.logging.ILogger;
import com.hazelcast.map.impl.DataAwareEntryEvent;
import com.hazelcast.monitor.LocalReplicatedMapStats;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.query.Predicate;
import com.hazelcast.spi.impl.UnmodifiableLazyList;
import com.hazelcast.util.IterationType;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static com.hazelcast.internal.nearcache.NearCache.CACHED_AS_NULL;
import static com.hazelcast.internal.nearcache.NearCache.NOT_CACHED;
import static com.hazelcast.internal.nearcache.NearCacheRecord.NOT_RESERVED;
import static com.hazelcast.util.ExceptionUtil.rethrow;
import static com.hazelcast.util.Preconditions.checkNotNull;
import static com.hazelcast.util.Preconditions.isNotNull;
import static java.util.Collections.sort;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
 * The replicated map client side proxy implementation proxying all requests to a member node
 *
 * @param  key type
 * @param  value type
 */
public class ClientReplicatedMapProxy extends ClientProxy implements ReplicatedMap {

    private static final String NULL_KEY_IS_NOT_ALLOWED = "Null key is not allowed!";
    private static final String NULL_VALUE_IS_NOT_ALLOWED = "Null value is not allowed!";

    private int targetPartitionId;

    private volatile NearCache nearCache;
    private volatile String invalidationListenerId;

    public ClientReplicatedMapProxy(String serviceName, String objectName, ClientContext context) {
        super(serviceName, objectName, context);
    }

    @Override
    protected void onInitialize() {
        int partitionCount = getContext().getPartitionService().getPartitionCount();
        targetPartitionId = ThreadLocalRandomProvider.get().nextInt(partitionCount);

        initNearCache();
    }

    private void initNearCache() {
        NearCacheConfig nearCacheConfig = getContext().getClientConfig().getNearCacheConfig(name);
        if (nearCacheConfig != null) {
            if (nearCacheConfig.isSerializeKeys()) {
                throw new InvalidConfigurationException("ReplicatedMap doesn't support serializeKeys option of NearCacheConfig");
            }

            nearCache = getContext().getNearCacheManager().getOrCreateNearCache(name, nearCacheConfig);
            if (nearCacheConfig.isInvalidateOnChange()) {
                registerInvalidationListener();
            }
        }
    }

    @Override
    protected void postDestroy() {
        try {
            if (nearCache != null) {
                removeNearCacheInvalidationListener();
                getContext().getNearCacheManager().destroyNearCache(name);
            }
        } finally {
            super.postDestroy();
        }
    }

    @Override
    public V put(K key, V value, long ttl, TimeUnit timeUnit) {
        checkNotNull(key, NULL_KEY_IS_NOT_ALLOWED);
        checkNotNull(value, NULL_VALUE_IS_NOT_ALLOWED);

        try {
            Data valueData = toData(value);
            Data keyData = toData(key);
            ClientMessage request = ReplicatedMapPutCodec.encodeRequest(name, keyData, valueData, timeUnit.toMillis(ttl));
            ClientMessage response = invoke(request, keyData);
            ReplicatedMapPutCodec.ResponseParameters result = ReplicatedMapPutCodec.decodeResponse(response);

            return toObject(result.response);
        } finally {
            invalidate(key);
        }
    }

    @Override
    public int size() {
        ClientMessage request = ReplicatedMapSizeCodec.encodeRequest(name);
        ClientMessage response = invokeOnPartition(request, targetPartitionId);
        ReplicatedMapSizeCodec.ResponseParameters result = ReplicatedMapSizeCodec.decodeResponse(response);
        return result.response;
    }

    @Override
    public boolean isEmpty() {
        ClientMessage request = ReplicatedMapIsEmptyCodec.encodeRequest(name);
        ClientMessage response = invokeOnPartition(request, targetPartitionId);
        ReplicatedMapIsEmptyCodec.ResponseParameters result = ReplicatedMapIsEmptyCodec.decodeResponse(response);
        return result.response;
    }

    @Override
    public boolean containsKey(Object key) {
        checkNotNull(key, NULL_KEY_IS_NOT_ALLOWED);
        Data keyData = toData(key);
        ClientMessage request = ReplicatedMapContainsKeyCodec.encodeRequest(name, keyData);
        ClientMessage response = invoke(request, keyData);
        ReplicatedMapContainsKeyCodec.ResponseParameters result = ReplicatedMapContainsKeyCodec.decodeResponse(response);
        return result.response;
    }

    @Override
    public boolean containsValue(Object value) {
        checkNotNull(value, NULL_KEY_IS_NOT_ALLOWED);
        Data valueData = toData(value);
        ClientMessage request = ReplicatedMapContainsValueCodec.encodeRequest(name, valueData);
        ClientMessage response = invokeOnPartition(request, targetPartitionId);
        ReplicatedMapContainsValueCodec.ResponseParameters result = ReplicatedMapContainsValueCodec.decodeResponse(response);
        return result.response;
    }

    @Override
    public V get(Object userKey) {
        K key = validateKey(userKey);

        V cachedValue = getCachedValue(key);
        if (cachedValue != NOT_CACHED) {
            return cachedValue;
        }

        try {
            Data keyData = toData(key);
            long reservationId = tryReserveForUpdate(key, keyData);
            ClientMessage request = ReplicatedMapGetCodec.encodeRequest(name, keyData);
            ClientMessage response = invoke(request, keyData);

            ReplicatedMapGetCodec.ResponseParameters result = ReplicatedMapGetCodec.decodeResponse(response);
            V value = toObject(result.response);
            tryPublishReserved(key, value, reservationId);
            return value;
        } catch (Throwable t) {
            invalidate(key);
            throw rethrow(t);
        }
    }

    @Override
    public V put(K key, V value) {
        return put(key, value, 0, MILLISECONDS);
    }

    @Override
    public V remove(Object userKey) {
        K key = validateKey(userKey);

        try {
            Data keyData = toData(key);
            ClientMessage request = ReplicatedMapRemoveCodec.encodeRequest(name, keyData);
            ClientMessage response = invoke(request, keyData);
            ReplicatedMapRemoveCodec.ResponseParameters result = ReplicatedMapRemoveCodec.decodeResponse(response);
            return toObject(result.response);
        } finally {
            invalidate(key);
        }
    }

    @Override
    public void putAll(Map map) {
        try {
            List> dataEntries = new ArrayList>(map.size());
            for (Entry entry : map.entrySet()) {
                Data keyData = toData(entry.getKey());
                Data valueData = toData(entry.getValue());

                dataEntries.add(new AbstractMap.SimpleImmutableEntry(keyData, valueData));
            }

            ClientMessage request = ReplicatedMapPutAllCodec.encodeRequest(name, dataEntries);
            invoke(request);
        } finally {
            if (nearCache != null) {
                for (K key : map.keySet()) {
                    invalidate(key);
                }
            }
        }
    }

    @Override
    public void clear() {
        try {
            ClientMessage request = ReplicatedMapClearCodec.encodeRequest(name);
            invoke(request);
        } finally {
            if (nearCache != null) {
                nearCache.clear();
            }
        }
    }

    @Override
    public boolean removeEntryListener(String registrationId) {
        return deregisterListener(registrationId);
    }

    @Override
    public String addEntryListener(EntryListener listener) {
        isNotNull(listener, "listener");
        EventHandler handler = createHandler(listener);
        return registerListener(createEntryListenerCodec(), handler);
    }

    private ListenerMessageCodec createEntryListenerCodec() {
        return new ListenerMessageCodec() {
            @Override
            public ClientMessage encodeAddRequest(boolean localOnly) {
                return ReplicatedMapAddEntryListenerCodec.encodeRequest(name, localOnly);
            }

            @Override
            public String decodeAddResponse(ClientMessage clientMessage) {
                return ReplicatedMapAddEntryListenerCodec.decodeResponse(clientMessage).response;
            }

            @Override
            public ClientMessage encodeRemoveRequest(String realRegistrationId) {
                return ReplicatedMapRemoveEntryListenerCodec.encodeRequest(name, realRegistrationId);
            }

            @Override
            public boolean decodeRemoveResponse(ClientMessage clientMessage) {
                return ReplicatedMapRemoveEntryListenerCodec.decodeResponse(clientMessage).response;
            }
        };
    }

    @Override
    public String addEntryListener(EntryListener listener, K key) {
        checkNotNull(key, NULL_KEY_IS_NOT_ALLOWED);
        isNotNull(listener, "listener");
        final Data keyData = toData(key);
        EventHandler handler = createHandler(listener);
        return registerListener(createEntryListenerToKeyCodec(keyData), handler);
    }

    private ListenerMessageCodec createEntryListenerToKeyCodec(final Data keyData) {
        return new ListenerMessageCodec() {
            @Override
            public ClientMessage encodeAddRequest(boolean localOnly) {
                return ReplicatedMapAddEntryListenerToKeyCodec.encodeRequest(name, keyData, localOnly);
            }

            @Override
            public String decodeAddResponse(ClientMessage clientMessage) {
                return ReplicatedMapAddEntryListenerToKeyCodec.decodeResponse(clientMessage).response;
            }

            @Override
            public ClientMessage encodeRemoveRequest(String realRegistrationId) {
                return ReplicatedMapRemoveEntryListenerCodec.encodeRequest(name, realRegistrationId);
            }

            @Override
            public boolean decodeRemoveResponse(ClientMessage clientMessage) {
                return ReplicatedMapRemoveEntryListenerCodec.decodeResponse(clientMessage).response;
            }
        };
    }

    @Override
    public String addEntryListener(EntryListener listener, Predicate predicate) {
        isNotNull(listener, "listener");
        isNotNull(predicate, "predicate");
        final Data predicateData = toData(predicate);
        EventHandler handler = createHandler(listener);
        return registerListener(createEntryListenerWithPredicateCodec(predicateData), handler);
    }

    private ListenerMessageCodec createEntryListenerWithPredicateCodec(final Data predicateData) {
        return new ListenerMessageCodec() {
            @Override
            public ClientMessage encodeAddRequest(boolean localOnly) {
                return ReplicatedMapAddEntryListenerWithPredicateCodec.encodeRequest(name, predicateData, localOnly);
            }

            @Override
            public String decodeAddResponse(ClientMessage clientMessage) {
                return ReplicatedMapAddEntryListenerWithPredicateCodec.decodeResponse(clientMessage).response;
            }

            @Override
            public ClientMessage encodeRemoveRequest(String realRegistrationId) {
                return ReplicatedMapRemoveEntryListenerCodec.encodeRequest(name, realRegistrationId);
            }

            @Override
            public boolean decodeRemoveResponse(ClientMessage clientMessage) {
                return ReplicatedMapRemoveEntryListenerCodec.decodeResponse(clientMessage).response;
            }
        };
    }

    @Override
    public String addEntryListener(EntryListener listener, Predicate predicate, K key) {
        checkNotNull(key, NULL_KEY_IS_NOT_ALLOWED);
        isNotNull(listener, "listener");
        isNotNull(predicate, "predicate");
        final Data keyData = toData(key);
        final Data predicateData = toData(predicate);
        EventHandler handler = createHandler(listener);
        return registerListener(createEntryListenerToKeyWithPredicateCodec(keyData, predicateData), handler);
    }

    private ListenerMessageCodec createEntryListenerToKeyWithPredicateCodec(final Data keyData,
                                                                            final Data predicateData) {
        return new ListenerMessageCodec() {
            @Override
            public ClientMessage encodeAddRequest(boolean localOnly) {
                return ReplicatedMapAddEntryListenerToKeyWithPredicateCodec
                        .encodeRequest(name, keyData, predicateData, localOnly);
            }

            @Override
            public String decodeAddResponse(ClientMessage clientMessage) {
                return ReplicatedMapAddEntryListenerToKeyWithPredicateCodec.decodeResponse(clientMessage).response;
            }

            @Override
            public ClientMessage encodeRemoveRequest(String realRegistrationId) {
                return ReplicatedMapRemoveEntryListenerCodec.encodeRequest(name, realRegistrationId);
            }

            @Override
            public boolean decodeRemoveResponse(ClientMessage clientMessage) {
                return ReplicatedMapRemoveEntryListenerCodec.decodeResponse(clientMessage).response;
            }
        };
    }

    @Override
    @SuppressWarnings("unchecked")
    public Set keySet() {
        ClientMessage request = ReplicatedMapKeySetCodec.encodeRequest(name);
        ClientMessage response = invokeOnPartition(request, targetPartitionId);
        ReplicatedMapKeySetCodec.ResponseParameters result = ReplicatedMapKeySetCodec.decodeResponse(response);
        List keys = new ArrayList(result.response.size());
        for (Data dataKey : result.response) {
            keys.add(new AbstractMap.SimpleImmutableEntry((K) toObject(dataKey), null));
        }
        Set resultSet = new ResultSet(keys, IterationType.KEY);
        return resultSet;
    }

    @Override
    public LocalReplicatedMapStats getReplicatedMapStats() {
        throw new UnsupportedOperationException("Replicated Map statistics are not available for client!");
    }

    @Override
    public Collection values() {
        ClientMessage request = ReplicatedMapValuesCodec.encodeRequest(name);
        ClientMessage response = invokeOnPartition(request, targetPartitionId);
        ReplicatedMapValuesCodec.ResponseParameters result = ReplicatedMapValuesCodec.decodeResponse(response);
        return new UnmodifiableLazyList(result.response, getSerializationService());
    }

    @Override
    public Collection values(Comparator comparator) {
        List values = (List) values();
        sort(values, comparator);
        return values;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Set> entrySet() {
        ClientMessage request = ReplicatedMapEntrySetCodec.encodeRequest(name);
        ClientMessage response = invokeOnPartition(request, targetPartitionId);
        ReplicatedMapEntrySetCodec.ResponseParameters result = ReplicatedMapEntrySetCodec.decodeResponse(response);
        List entries = new ArrayList(result.response.size());
        for (Entry dataEntry : result.response) {
            K key = toObject(dataEntry.getKey());
            V value = toObject(dataEntry.getValue());
            entries.add(new AbstractMap.SimpleImmutableEntry(key, value));
        }
        Set resultSet = new ResultSet(entries, IterationType.ENTRY);
        return resultSet;
    }

    private EventHandler createHandler(EntryListener listener) {
        return new ReplicatedMapEventHandler(listener);
    }


    public String addNearCacheInvalidationListener(EventHandler handler) {
        return registerListener(createNearCacheInvalidationListenerCodec(), handler);
    }

    private void registerInvalidationListener() {
        try {
            invalidationListenerId = addNearCacheInvalidationListener(new ReplicatedMapAddNearCacheEventHandler());
        } catch (Exception e) {
            ILogger logger = getContext().getLoggingService().getLogger(ClientReplicatedMapProxy.class);
            logger.severe("-----------------\nNear Cache is not initialized!\n-----------------", e);
        }
    }

    private ListenerMessageCodec createNearCacheInvalidationListenerCodec() {
        return new ListenerMessageCodec() {
            @Override
            public ClientMessage encodeAddRequest(boolean localOnly) {
                return ReplicatedMapAddNearCacheEntryListenerCodec.encodeRequest(name, false, localOnly);
            }

            @Override
            public String decodeAddResponse(ClientMessage clientMessage) {
                return ReplicatedMapAddNearCacheEntryListenerCodec.decodeResponse(clientMessage).response;
            }

            @Override
            public ClientMessage encodeRemoveRequest(String realRegistrationId) {
                return ReplicatedMapRemoveEntryListenerCodec.encodeRequest(name, realRegistrationId);
            }

            @Override
            public boolean decodeRemoveResponse(ClientMessage clientMessage) {
                return ReplicatedMapRemoveEntryListenerCodec.decodeResponse(clientMessage).response;
            }
        };
    }

    private void removeNearCacheInvalidationListener() {
        if (nearCache != null && invalidationListenerId != null) {
            getContext().getListenerService().deregisterListener(invalidationListenerId);
        }
    }

    @Override
    public String toString() {
        return "ReplicatedMap{" + "name='" + name + '\'' + '}';
    }

    @SuppressWarnings("unchecked")
    private K validateKey(Object key) {
        return checkNotNull((K) key, NULL_KEY_IS_NOT_ALLOWED);
    }

    @SuppressWarnings("unchecked")
    private V getCachedValue(K key) {
        if (nearCache == null) {
            return (V) NOT_CACHED;
        }

        V value = nearCache.get(key);
        if (value == null) {
            return (V) NOT_CACHED;
        }
        if (value == CACHED_AS_NULL) {
            return null;
        }
        return toObject(value);
    }

    private void tryPublishReserved(K key, V value, long reservationId) {
        if (nearCache == null) {
            return;
        }
        if (reservationId != NOT_RESERVED) {
            nearCache.tryPublishReserved(key, value, reservationId, false);
        }
    }

    private long tryReserveForUpdate(K key, Data keyData) {
        if (nearCache == null) {
            return NOT_RESERVED;
        }
        return nearCache.tryReserveForUpdate(key, keyData);
    }

    private void invalidate(K key) {
        if (nearCache == null) {
            return;
        }
        nearCache.invalidate(key);
    }

    private class ReplicatedMapEventHandler
            extends ReplicatedMapAddEntryListenerCodec.AbstractEventHandler
            implements EventHandler {

        private final EntryListener listener;

        ReplicatedMapEventHandler(EntryListener listener) {
            this.listener = listener;
        }

        @Override
        public void handleEntryEventV10(Data keyData, Data valueData, Data oldValueData, Data mergingValue,
                                        int eventTypeId, String uuid, int numberOfAffectedEntries) {
            Member member = getContext().getClusterService().getMember(uuid);
            EntryEventType eventType = EntryEventType.getByType(eventTypeId);
            EntryEvent entryEvent = new DataAwareEntryEvent(member, eventTypeId, name, keyData, valueData,
                    oldValueData, null, getSerializationService());
            switch (eventType) {
                case ADDED:
                    listener.entryAdded(entryEvent);
                    break;
                case REMOVED:
                    listener.entryRemoved(entryEvent);
                    break;
                case UPDATED:
                    listener.entryUpdated(entryEvent);
                    break;
                case EVICTED:
                    listener.entryEvicted(entryEvent);
                    break;
                case CLEAR_ALL:
                    MapEvent mapEvent = new MapEvent(getName(), member, eventTypeId, numberOfAffectedEntries);
                    listener.mapCleared(mapEvent);
                    break;
                default:
                    throw new IllegalArgumentException("Not a known event type: " + eventType);
            }
        }

        @Override
        public void beforeListenerRegister() {
        }

        @Override
        public void onListenerRegister() {
        }
    }

    private class ReplicatedMapAddNearCacheEventHandler
            extends ReplicatedMapAddNearCacheEntryListenerCodec.AbstractEventHandler
            implements EventHandler {

        @Override
        public void beforeListenerRegister() {
            if (nearCache != null) {
                nearCache.clear();
            }
        }

        @Override
        public void onListenerRegister() {
            if (nearCache != null) {
                nearCache.clear();
            }
        }

        @Override
        public void handleEntryEventV10(Data dataKey, Data value, Data oldValue, Data mergingValue,
                                        int eventType, String uuid, int numberOfAffectedEntries) {
            EntryEventType entryEventType = EntryEventType.getByType(eventType);
            switch (entryEventType) {
                case ADDED:
                case REMOVED:
                case UPDATED:
                case EVICTED:
                    K key = toObject(dataKey);
                    nearCache.invalidate(key);
                    break;
                case CLEAR_ALL:
                    nearCache.clear();
                    break;
                default:
                    throw new IllegalArgumentException("Not a known event type " + entryEventType);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy