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

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

There is a newer version: 3.6.8
Show newest version
/*
 * Copyright (c) 2008-2016, 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.cache.impl.nearcache.NearCache;
import com.hazelcast.client.impl.client.BaseClientRemoveListenerRequest;
import com.hazelcast.client.map.impl.nearcache.ClientHeapNearCache;
import com.hazelcast.client.spi.ClientProxy;
import com.hazelcast.client.spi.EventHandler;
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.logging.Logger;
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.replicatedmap.impl.client.ClientReplicatedMapAddEntryListenerRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapAddNearCacheListenerRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapClearRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapContainsKeyRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapContainsValueRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapEntrySetRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapGetRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapIsEmptyRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapKeySetRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapPutAllRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapPutTtlRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapRemoveEntryListenerRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapRemoveRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapSizeRequest;
import com.hazelcast.replicatedmap.impl.client.ClientReplicatedMapValuesRequest;
import com.hazelcast.replicatedmap.impl.client.ReplicatedMapEntries;
import com.hazelcast.replicatedmap.impl.client.ReplicatedMapKeys;
import com.hazelcast.replicatedmap.impl.client.ReplicatedMapPortableEntryEvent;
import com.hazelcast.replicatedmap.impl.client.ReplicatedMapValueCollection;
import com.hazelcast.replicatedmap.impl.record.ResultSet;
import com.hazelcast.spi.Operation;
import com.hazelcast.util.IterationType;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

import static com.hazelcast.cache.impl.nearcache.NearCache.NULL_OBJECT;

/**
 * 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 Random RANDOM_PARTITION_ID_GENERATOR = new Random();

    private static final AtomicIntegerFieldUpdater TARGET_PARTITION_ID_UPDATER =
            AtomicIntegerFieldUpdater.newUpdater(ClientReplicatedMapProxy.class, "targetPartitionId");

    private volatile NearCache nearCache;
    private volatile String invalidationListenerId;
    private final AtomicBoolean nearCacheInitialized = new AtomicBoolean();

    // all requests are forwarded to the same node via a random partition id
    // do not use this field directly. use getOrInitTargetPartitionId() instead.
    private volatile int targetPartitionId = Operation.GENERIC_PARTITION_ID;

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

    @Override
    protected void onDestroy() {
        if (nearCache != null) {
            removeNearCacheInvalidationListener();
            nearCache.destroy();
        }
    }

    @Override
    public V put(K key, V value, long ttl, TimeUnit timeUnit) {
        Data dataKey = toData(key);
        Data dataValue = toData(value);
        ClientReplicatedMapPutTtlRequest request = new ClientReplicatedMapPutTtlRequest(getName(), dataKey, dataValue,
                timeUnit.toMillis(ttl));
        return (V) invoke(request, dataKey);
    }

    @Override
    public int size() {
        ClientReplicatedMapSizeRequest request = new ClientReplicatedMapSizeRequest(getName(),
                getOrInitTargetPartitionId());
        return (Integer) invokeOnPartition(request, getOrInitTargetPartitionId());
    }

    @Override
    public boolean isEmpty() {
        ClientReplicatedMapIsEmptyRequest request = new ClientReplicatedMapIsEmptyRequest(getName(),
                getOrInitTargetPartitionId());
        return (Boolean) invokeOnPartition(request, getOrInitTargetPartitionId());
    }

    @Override
    public boolean containsKey(Object key) {
        Data dataKey = toData(key);
        return (Boolean) invoke(new ClientReplicatedMapContainsKeyRequest(getName(), dataKey), dataKey);
    }

    @Override
    public boolean containsValue(Object value) {
        Data dataValue = toData(value);
        ClientReplicatedMapContainsValueRequest request = new ClientReplicatedMapContainsValueRequest(getName(),
                dataValue, getOrInitTargetPartitionId());
        return (Boolean) invokeOnPartition(request, getOrInitTargetPartitionId());
    }

    @Override
    public V get(Object key) {
        initNearCache();
        if (nearCache != null) {
            Object cached = nearCache.get(key);
            if (cached != null) {
                if (cached.equals(NULL_OBJECT)) {
                    return null;
                }
                return (V) cached;
            }
        }
        Data dataKey = toData(key);
        ClientReplicatedMapGetRequest request = new ClientReplicatedMapGetRequest(getName(), dataKey);
        Object response = invoke(request, dataKey);
        V value = response != null ? (V) response : null;
        if (nearCache != null) {
            nearCache.put(key, value);
        }
        return value;
    }

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

    @Override
    public V remove(Object key) {
        Data dataKey = toData(key);
        ClientReplicatedMapRemoveRequest request = new ClientReplicatedMapRemoveRequest(getName(), dataKey);
        return (V) invoke(request, dataKey);
    }

    @Override
    public void putAll(Map m) {
        Set> entries = m.entrySet();
        List> dataEntries = new ArrayList>(m.size());
        for (Entry entry : entries) {
            dataEntries.add(new AbstractMap.SimpleImmutableEntry(toData(entry.getKey()),
                    toData(entry.getValue())));
        }
        ReplicatedMapEntries entrySet = new ReplicatedMapEntries(dataEntries);
        ClientReplicatedMapPutAllRequest req = new ClientReplicatedMapPutAllRequest(getName(), entrySet);
        invoke(req);
    }

    @Override
    public void clear() {
        ClientReplicatedMapClearRequest request = new ClientReplicatedMapClearRequest(getName());
        invoke(request);
    }

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

    @Override
    public String addEntryListener(EntryListener listener) {
        ClientReplicatedMapAddEntryListenerRequest addRequest =
                new ClientReplicatedMapAddEntryListenerRequest(getName(), null, null);
        EventHandler handler = createHandler(listener);
        ClientReplicatedMapRemoveEntryListenerRequest removeRequest =
                new ClientReplicatedMapRemoveEntryListenerRequest(getName());
        return registerListener(addRequest, removeRequest, handler);
    }

    @Override
    public String addEntryListener(EntryListener listener, K key) {
        Data dataKey = toData(key);
        ClientReplicatedMapAddEntryListenerRequest addRequest =
                new ClientReplicatedMapAddEntryListenerRequest(getName(), null, dataKey);
        ClientReplicatedMapRemoveEntryListenerRequest removeRequest =
                new ClientReplicatedMapRemoveEntryListenerRequest(getName());
        EventHandler handler = createHandler(listener);
        return registerListener(addRequest, removeRequest, handler);
    }

    @Override
    public String addEntryListener(EntryListener listener, Predicate predicate) {
        ClientReplicatedMapAddEntryListenerRequest addRequest = new ClientReplicatedMapAddEntryListenerRequest(getName()
                , predicate, null);
        EventHandler handler = createHandler(listener);
        ClientReplicatedMapRemoveEntryListenerRequest removeRequest =
                new ClientReplicatedMapRemoveEntryListenerRequest(getName());
        return registerListener(addRequest, removeRequest, handler);
    }

    @Override
    public String addEntryListener(EntryListener listener, Predicate predicate, K key) {
        Data dataKey = toData(key);
        ClientReplicatedMapAddEntryListenerRequest addRequest =
                new ClientReplicatedMapAddEntryListenerRequest(getName(), predicate, dataKey);
        EventHandler handler = createHandler(listener);
        ClientReplicatedMapRemoveEntryListenerRequest removeRequest =
                new ClientReplicatedMapRemoveEntryListenerRequest(getName());
        return registerListener(addRequest, removeRequest, handler);
    }

    @Override
    public Set keySet() {
        ClientReplicatedMapKeySetRequest request = new ClientReplicatedMapKeySetRequest(getName(),
                getOrInitTargetPartitionId());
        ReplicatedMapKeys response = invokeOnPartition(request, getOrInitTargetPartitionId());
        List dataKeys = response.getKeys();
        List> keys = new ArrayList>(dataKeys.size());
        for (Data dataKey : dataKeys) {
            keys.add(new AbstractMap.SimpleImmutableEntry((K) toObject(dataKey), null));
        }
        return new ResultSet(keys, IterationType.KEY);
    }

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

    @Override
    public Collection values() {
        ClientReplicatedMapValuesRequest request = new ClientReplicatedMapValuesRequest(getName(),
                getOrInitTargetPartitionId());
        ReplicatedMapValueCollection result = invokeOnPartition(request, getOrInitTargetPartitionId());
        Collection dataValues = result.getValues();
        ArrayList values = new ArrayList(dataValues.size());
        for (Data dataValue : dataValues) {
            values.add((V) toObject(dataValue));
        }
        return values;
    }

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

    @Override
    public Set> entrySet() {
        ClientReplicatedMapEntrySetRequest request = new ClientReplicatedMapEntrySetRequest(getName(),
                getOrInitTargetPartitionId());
        ReplicatedMapEntries response = invokeOnPartition(request, getOrInitTargetPartitionId());
        List> dataEntries = response.getEntries();
        List> entries = new ArrayList>(dataEntries.size());
        for (Entry dataEntry : dataEntries) {
            K key = toObject(dataEntry.getKey());
            V value = toObject(dataEntry.getValue());
            entries.add(new AbstractMap.SimpleImmutableEntry(key, value));
        }
        return new ResultSet(entries, IterationType.ENTRY);
    }

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

    private void initNearCache() {
        if (nearCacheInitialized.compareAndSet(false, true)) {
            final NearCacheConfig nearCacheConfig = getContext().getClientConfig().getNearCacheConfig(getName());
            if (nearCacheConfig == null) {
                return;
            }
            ClientHeapNearCache nearCache =
                    new ClientHeapNearCache(getName(), getContext(), nearCacheConfig);
            this.nearCache = nearCache;
            if (nearCache.isInvalidateOnChange()) {
                addNearCacheInvalidateListener();
            }
        }
    }

    private void addNearCacheInvalidateListener() {
        try {
            ClientReplicatedMapAddNearCacheListenerRequest addRequest =
                    new ClientReplicatedMapAddNearCacheListenerRequest(getName());
            BaseClientRemoveListenerRequest removeRequest =
                    new ClientReplicatedMapRemoveEntryListenerRequest(getName());
            EventHandler handler = new ReplicatedMapNearCacheEventHandler();
            invalidationListenerId = registerListener(addRequest, removeRequest, handler);
        } catch (Exception e) {
            Logger.getLogger(ClientHeapNearCache.class).severe(
                    "-----------------\n Near Cache is not initialized!!! \n-----------------", e);
        }
    }

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

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

    private class ReplicatedMapEventHandler implements EventHandler {
        private final EntryListener listener;

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

        public void handle(ReplicatedMapPortableEntryEvent event) {
            EntryEventType eventType = event.getEventType();
            Data keyData = event.getKey();
            Data valueData = event.getValue();
            Data oldValueData = event.getOldValue();
            Member member = getContext().getClusterService().getMember(event.getUuid());
            int type = eventType.getType();
            EntryEvent entryEvent = new DataAwareEntryEvent(member, type, getName(), keyData, valueData,
                    oldValueData, null, getContext().getSerializationService());

            switch (event.getEventType()) {
                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, EntryEventType.CLEAR_ALL.getType(),
                            event.getNumberOfAffectedEntries());
                    listener.mapCleared(mapEvent);
                    break;
                default:
                    throw new IllegalArgumentException("Not a known event type " + event.getEventType());
            }
        }

        @Override
        public void beforeListenerRegister() {
        }

        @Override
        public void onListenerRegister() {
        }
    }

    private void invalidateNearCache() {
        if (nearCache != null) {
            nearCache.clear();
        }
    }

    private int getOrInitTargetPartitionId() {
        int targetPartitionId = this.targetPartitionId;
        while (targetPartitionId == Operation.GENERIC_PARTITION_ID) {
            final int partitionCount = getContext().getPartitionService().getPartitionCount();
            targetPartitionId = RANDOM_PARTITION_ID_GENERATOR.nextInt(partitionCount);
            if (!TARGET_PARTITION_ID_UPDATER.compareAndSet(this, -1, targetPartitionId)) {
                targetPartitionId = this.targetPartitionId;
            }
        }

        return targetPartitionId;
    }


    private class ReplicatedMapNearCacheEventHandler implements EventHandler {
        @Override
        public void handle(ReplicatedMapPortableEntryEvent event) {
            switch (event.getEventType()) {
                case ADDED:
                case REMOVED:
                case UPDATED:
                case EVICTED:
                    nearCache.remove(toObject(event.getKey()));
                    break;
                case CLEAR_ALL:
                    nearCache.clear();
                    break;
                default:
                    throw new IllegalArgumentException("Not a known event type " + event.getEventType());
            }
        }

        @Override
        public void beforeListenerRegister() {
            invalidateNearCache();
        }

        @Override
        public void onListenerRegister() {
            invalidateNearCache();
        }
    }

}