/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hazelcast.client.cache.impl; import com.hazelcast.cache.impl.CacheEntryProcessorResult; import com.hazelcast.cache.impl.CacheEventListenerAdaptor; import com.hazelcast.cache.impl.event.CachePartitionLostEvent; import com.hazelcast.cache.impl.event.CachePartitionLostListener; import com.hazelcast.client.impl.protocol.ClientMessage; import com.hazelcast.client.impl.protocol.codec.CacheAddEntryListenerCodec; import com.hazelcast.client.impl.protocol.codec.CacheAddPartitionLostListenerCodec; import com.hazelcast.client.impl.protocol.codec.CacheContainsKeyCodec; import com.hazelcast.client.impl.protocol.codec.CacheEntryProcessorCodec; import com.hazelcast.client.impl.protocol.codec.CacheListenerRegistrationCodec; import com.hazelcast.client.impl.protocol.codec.CacheLoadAllCodec; import com.hazelcast.client.impl.protocol.codec.CacheRemoveEntryListenerCodec; import com.hazelcast.client.impl.protocol.codec.CacheRemovePartitionLostListenerCodec; import com.hazelcast.client.spi.ClientContext; import com.hazelcast.client.spi.EventHandler; import com.hazelcast.client.spi.impl.ClientInvocation; import com.hazelcast.client.spi.impl.ListenerMessageCodec; import com.hazelcast.config.CacheConfig; import com.hazelcast.core.ICompletableFuture; import com.hazelcast.core.Member; import com.hazelcast.nio.Address; import com.hazelcast.nio.serialization.Data; import javax.cache.CacheException; import javax.cache.CacheManager; import javax.cache.configuration.CacheEntryListenerConfiguration; import javax.cache.configuration.Configuration; import javax.cache.expiry.ExpiryPolicy; import javax.cache.integration.CompletionListener; import javax.cache.processor.EntryProcessor; import javax.cache.processor.EntryProcessorException; import javax.cache.processor.EntryProcessorResult; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static com.hazelcast.cache.impl.CacheEventType.PARTITION_LOST; import static com.hazelcast.cache.impl.CacheProxyUtil.NULL_KEY_IS_NOT_ALLOWED; import static com.hazelcast.cache.impl.CacheProxyUtil.validateConfiguredTypes; import static com.hazelcast.cache.impl.CacheProxyUtil.validateNotNull; import static com.hazelcast.util.CollectionUtil.objectToDataCollection; import static com.hazelcast.util.ExceptionUtil.rethrowAllowedTypeFirst; import static com.hazelcast.util.ExceptionUtil.sneakyThrow; import static com.hazelcast.util.MapUtil.createHashMap; import static com.hazelcast.util.Preconditions.checkNotNull; /** * {@link com.hazelcast.cache.ICache} implementation for Hazelcast clients. * * This proxy is the implementation of {@link com.hazelcast.cache.ICache} and {@link javax.cache.Cache} which is returned by * {@link HazelcastClientCacheManager}. Represents a cache on client. * * This implementation is a thin proxy implementation using hazelcast client infrastructure. * * @param <K> key type * @param <V> value type */ public class ClientCacheProxy<K, V> extends AbstractClientCacheProxy<K, V> { ClientCacheProxy(CacheConfig<K, V> cacheConfig, ClientContext context) { super(cacheConfig, context); } @Override public V get(K key) { return get(key, null); } @Override public Map<K, V> getAll(Set<? extends K> keys) { return getAll(keys, null); } @Override public boolean containsKey(K key) { ensureOpen(); validateNotNull(key); Data keyData = toData(key); return containsKeyInternal(keyData); } protected boolean containsKeyInternal(Data keyData) { ClientMessage request = CacheContainsKeyCodec.encodeRequest(nameWithPrefix, keyData); ClientMessage result = invoke(request, keyData); return CacheContainsKeyCodec.decodeResponse(result).response; } @Override public void loadAll(Set<? extends K> keys, boolean replaceExistingValues, CompletionListener completionListener) { ensureOpen(); validateNotNull(keys); List<Data> dataKeys = new ArrayList<Data>(keys.size()); for (K key : keys) { validateConfiguredTypes(cacheConfig, key); dataKeys.add(toData(key)); } loadAllInternal(dataKeys, replaceExistingValues, completionListener); } protected void loadAllInternal(List<Data> dataKeys, boolean replaceExistingValues, CompletionListener completionListener) { ClientMessage request = CacheLoadAllCodec.encodeRequest(nameWithPrefix, dataKeys, replaceExistingValues); try { submitLoadAllTask(request, completionListener, dataKeys); } catch (Exception e) { if (completionListener != null) { completionListener.onException(e); } throw new CacheException(e); } } @Override protected void onLoadAll(List<Data> keys, Object response, long startNanos) { if (statisticsEnabled) { statsHandler.onBatchPut(startNanos, keys.size()); } } @Override public void put(K key, V value) { put(key, value, null); } @Override public V getAndPut(K key, V value) { return getAndPut(key, value, null); } @Override public void putAll(Map<? extends K, ? extends V> map) { putAll(map, null); } @Override public boolean putIfAbsent(K key, V value) { return putIfAbsent(key, value, null); } @Override public boolean remove(K key) { long start = nowInNanosOrDefault(); try { boolean removed = (Boolean) removeAsyncInternal(key, null, false, true, false); if (statisticsEnabled) { statsHandler.onRemove(false, start, removed); } return removed; } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } @Override public boolean remove(K key, V oldValue) { long start = nowInNanosOrDefault(); try { boolean removed = (Boolean) removeAsyncInternal(key, oldValue, true, true, false); if (statisticsEnabled) { statsHandler.onRemove(false, start, removed); } return removed; } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } @Override public V getAndRemove(K key) { long start = nowInNanosOrDefault(); ICompletableFuture<V> future = getAndRemoveSyncInternal(key); try { V removedValue = toObject(future.get()); if (statisticsEnabled) { statsHandler.onRemove(true, start, removedValue); } return removedValue; } catch (Throwable e) { throw rethrowAllowedTypeFirst(e, CacheException.class); } } @Override public boolean replace(K key, V oldValue, V newValue) { return replace(key, oldValue, newValue, null); } @Override public boolean replace(K key, V value) { return replace(key, value, (ExpiryPolicy) null); } @Override public V getAndReplace(K key, V value) { return getAndReplace(key, value, null); } @Override public void removeAll(Set<? extends K> keys) { long startNanos = nowInNanosOrDefault(); ensureOpen(); checkNotNull(keys, NULL_KEY_IS_NOT_ALLOWED); if (keys.isEmpty()) { return; } List<Data> dataKeys = new ArrayList<Data>(keys.size()); objectToDataCollection(keys, dataKeys, getSerializationService(), NULL_KEY_IS_NOT_ALLOWED); removeAllKeysInternal(dataKeys, startNanos); } @Override public void removeAll() { ensureOpen(); removeAllInternal(); } @Override public void clear() { ensureOpen(); clearInternal(); } @Override public <C extends Configuration<K, V>> C getConfiguration(Class<C> clazz) { if (clazz.isInstance(cacheConfig)) { return clazz.cast(cacheConfig.getAsReadOnly()); } throw new IllegalArgumentException("The configuration class " + clazz + " is not supported by this implementation"); } @Override public <T> T invoke(K key, EntryProcessor<K, V, T> entryProcessor, Object... arguments) throws EntryProcessorException { ensureOpen(); validateNotNull(key); if (entryProcessor == null) { throw new NullPointerException("Entry Processor is null"); } Data keyData = toData(key); Data epData = toData(entryProcessor); return (T) invokeInternal(keyData, epData, arguments); } protected Object invokeInternal(Data keyData, Data epData, Object... arguments) { List<Data> argumentsData; if (arguments != null) { argumentsData = new ArrayList<Data>(arguments.length); for (int i = 0; i < arguments.length; i++) { argumentsData.add(toData(arguments[i])); } } else { argumentsData = Collections.emptyList(); } int completionId = nextCompletionId(); ClientMessage request = CacheEntryProcessorCodec.encodeRequest(nameWithPrefix, keyData, epData, argumentsData, completionId); try { ICompletableFuture<ClientMessage> future = invoke(request, keyData, completionId); ClientMessage response = getSafely(future); Data data = CacheEntryProcessorCodec.decodeResponse(response).response; // at client side, we don't know what entry processor does so we ignore it from statistics perspective return toObject(data); } catch (CacheException ce) { throw ce; } catch (Exception e) { throw new EntryProcessorException(e); } } @Override public <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> keys, EntryProcessor<K, V, T> entryProcessor, Object... arguments) { // TODO: implement a multiple (batch) invoke operation and its factory ensureOpen(); validateNotNull(keys); if (entryProcessor == null) { throw new NullPointerException("Entry Processor is null"); } Map<K, EntryProcessorResult<T>> allResult = createHashMap(keys.size()); for (K key : keys) { CacheEntryProcessorResult<T> cepResult; try { T result = invoke(key, entryProcessor, arguments); cepResult = result != null ? new CacheEntryProcessorResult<T>(result) : null; } catch (Exception e) { cepResult = new CacheEntryProcessorResult<T>(e); } if (cepResult != null) { allResult.put(key, cepResult); } } // at client side, we don't know what entry processor does so we ignore it from statistics perspective return allResult; } @Override public CacheManager getCacheManager() { return cacheManager; } @Override public <T> T unwrap(Class<T> clazz) { if (clazz.isAssignableFrom(((Object) this).getClass())) { return clazz.cast(this); } throw new IllegalArgumentException("Unwrapping to " + clazz + " is not supported by this implementation"); } @Override public void registerCacheEntryListener(CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) { registerCacheEntryListener(cacheEntryListenerConfiguration, true); } @Override public void registerCacheEntryListener(CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration, boolean addToConfig) { ensureOpen(); if (cacheEntryListenerConfiguration == null) { throw new NullPointerException("CacheEntryListenerConfiguration can't be null"); } CacheEventListenerAdaptor<K, V> adaptor = new CacheEventListenerAdaptor<K, V>(this, cacheEntryListenerConfiguration, getSerializationService(), getClient()); EventHandler handler = createHandler(adaptor); String regId = getContext().getListenerService().registerListener(createCacheEntryListenerCodec(), handler); if (regId != null) { if (addToConfig) { cacheConfig.addCacheEntryListenerConfiguration(cacheEntryListenerConfiguration); } addListenerLocally(regId, cacheEntryListenerConfiguration); if (addToConfig) { updateCacheListenerConfigOnOtherNodes(cacheEntryListenerConfiguration, true); } } } private ListenerMessageCodec createCacheEntryListenerCodec() { return new ListenerMessageCodec() { @Override public ClientMessage encodeAddRequest(boolean localOnly) { return CacheAddEntryListenerCodec.encodeRequest(nameWithPrefix, localOnly); } @Override public String decodeAddResponse(ClientMessage clientMessage) { return CacheAddEntryListenerCodec.decodeResponse(clientMessage).response; } @Override public ClientMessage encodeRemoveRequest(String realRegistrationId) { return CacheRemoveEntryListenerCodec.encodeRequest(nameWithPrefix, realRegistrationId); } @Override public boolean decodeRemoveResponse(ClientMessage clientMessage) { return CacheRemoveEntryListenerCodec.decodeResponse(clientMessage).response; } }; } @Override public void deregisterCacheEntryListener(CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) { if (cacheEntryListenerConfiguration == null) { throw new NullPointerException("CacheEntryListenerConfiguration can't be null"); } String regId = getListenerIdLocal(cacheEntryListenerConfiguration); if (regId == null) { return; } boolean isDeregistered = getContext().getListenerService().deregisterListener(regId); if (isDeregistered) { removeListenerLocally(cacheEntryListenerConfiguration); cacheConfig.removeCacheEntryListenerConfiguration(cacheEntryListenerConfiguration); updateCacheListenerConfigOnOtherNodes(cacheEntryListenerConfiguration, false); } } protected void updateCacheListenerConfigOnOtherNodes(CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration, boolean isRegister) { Collection<Member> members = getContext().getClusterService().getMemberList(); for (Member member : members) { try { Address address = member.getAddress(); Data configData = toData(cacheEntryListenerConfiguration); ClientMessage request = CacheListenerRegistrationCodec.encodeRequest(nameWithPrefix, configData, isRegister, address); ClientInvocation invocation = new ClientInvocation(getClient(), request, address); invocation.invoke(); } catch (Exception e) { sneakyThrow(e); } } } @Override public Iterator<Entry<K, V>> iterator() { ensureOpen(); return new ClientClusterWideIterator<K, V>(this, getContext(), false); } @Override public Iterator<Entry<K, V>> iterator(int fetchSize) { ensureOpen(); return new ClientClusterWideIterator<K, V>(this, getContext(), fetchSize, false); } @Override public Iterator<Entry<K, V>> iterator(int fetchSize, int partitionId, boolean prefetchValues) { ensureOpen(); return new ClientCachePartitionIterator<K, V>(this, getContext(), fetchSize, partitionId, prefetchValues); } @Override public String addPartitionLostListener(CachePartitionLostListener listener) { EventHandler<ClientMessage> handler = new ClientCachePartitionLostEventHandler(listener); injectDependencies(listener); return getContext().getListenerService().registerListener(createPartitionLostListenerCodec(), handler); } private ListenerMessageCodec createPartitionLostListenerCodec() { return new ListenerMessageCodec() { @Override public ClientMessage encodeAddRequest(boolean localOnly) { return CacheAddPartitionLostListenerCodec.encodeRequest(name, localOnly); } @Override public String decodeAddResponse(ClientMessage clientMessage) { return CacheAddPartitionLostListenerCodec.decodeResponse(clientMessage).response; } @Override public ClientMessage encodeRemoveRequest(String realRegistrationId) { return CacheRemovePartitionLostListenerCodec.encodeRequest(name, realRegistrationId); } @Override public boolean decodeRemoveResponse(ClientMessage clientMessage) { return CacheRemovePartitionLostListenerCodec.decodeResponse(clientMessage).response; } }; } @Override public boolean removePartitionLostListener(String id) { return getContext().getListenerService().deregisterListener(id); } private final class ClientCachePartitionLostEventHandler extends CacheAddPartitionLostListenerCodec.AbstractEventHandler implements EventHandler<ClientMessage> { private CachePartitionLostListener listener; private ClientCachePartitionLostEventHandler(CachePartitionLostListener listener) { this.listener = listener; } @Override public void beforeListenerRegister() { } @Override public void onListenerRegister() { } @Override public void handle(int partitionId, String uuid) { Member member = getContext().getClusterService().getMember(uuid); listener.partitionLost(new CachePartitionLostEvent(name, member, PARTITION_LOST.getType(), partitionId)); } } }