com.hazelcast.client.cache.impl.ClientCacheProxy Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2015, 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.CacheProxyUtil;
import com.hazelcast.cache.impl.nearcache.NearCache;
import com.hazelcast.client.impl.ClientMessageDecoder;
import com.hazelcast.client.impl.HazelcastClientInstanceImpl;
import com.hazelcast.client.impl.protocol.ClientMessage;
import com.hazelcast.client.impl.protocol.codec.CacheAddEntryListenerCodec;
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.spi.ClientContext;
import com.hazelcast.client.spi.ClientListenerService;
import com.hazelcast.client.spi.EventHandler;
import com.hazelcast.client.spi.impl.ClientInvocation;
import com.hazelcast.client.spi.impl.ListenerRemoveCodec;
import com.hazelcast.config.CacheConfig;
import com.hazelcast.core.ICompletableFuture;
import com.hazelcast.core.Member;
import com.hazelcast.instance.AbstractMember;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.util.ExceptionUtil;
import javax.cache.CacheException;
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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import static com.hazelcast.cache.impl.CacheProxyUtil.validateNotNull;
/**
* ICache implementation for client
*
* This proxy is the implementation of ICache and javax.cache.Cache which is returned by
* HazelcastClientCacheManager. Represent a cache on client.
*
* This implementation is a thin proxy implementation using hazelcast client infrastructure
*
* @param key type
* @param value type
*/
public class ClientCacheProxy
extends AbstractClientCacheProxy {
public ClientCacheProxy(CacheConfig cacheConfig, ClientContext clientContext,
HazelcastClientCacheManager cacheManager) {
super(cacheConfig, clientContext, cacheManager);
}
@Override
public V get(K key) {
return get(key, null);
}
@Override
public Map getAll(Set keys) {
return getAll(keys, null);
}
@Override
public boolean containsKey(K key) {
ensureOpen();
validateNotNull(key);
final Data keyData = toData(key);
Object cached = nearCache != null ? nearCache.get(keyData) : null;
if (cached != null && !NearCache.NULL_OBJECT.equals(cached)) {
return true;
}
ClientMessage request = CacheContainsKeyCodec.encodeRequest(nameWithPrefix, keyData);
ClientMessage result = invoke(request, keyData);
return CacheContainsKeyCodec.decodeResponse(result).response;
}
@Override
public void loadAll(Set keys, boolean replaceExistingValues, CompletionListener completionListener) {
ensureOpen();
validateNotNull(keys);
for (K key : keys) {
CacheProxyUtil.validateConfiguredTypes(cacheConfig, key);
}
validateCacheLoader(completionListener);
HashSet keysData = new HashSet();
for (K key : keys) {
keysData.add(toData(key));
}
ClientMessage request = CacheLoadAllCodec.encodeRequest(nameWithPrefix, keysData, replaceExistingValues);
try {
submitLoadAllTask(request, completionListener, keysData);
} catch (Exception e) {
if (completionListener != null) {
completionListener.onException(e);
}
throw new CacheException(e);
}
}
@Override
protected void onLoadAll(Set keys, Object response, long start, long end) {
if (statisticsEnabled) {
// We don't know how many of keys are actually loaded so we assume that all of them are loaded
// and calculates statistics based on this assumption.
statistics.increaseCachePuts(keys.size());
statistics.addPutTimeNanos(end - start);
}
}
@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 map) {
putAll(map, null);
}
@Override
public boolean putIfAbsent(K key, V value) {
return putIfAbsent(key, value, null);
}
@Override
public boolean remove(K key) {
final long start = System.nanoTime();
final ICompletableFuture f = removeAsyncInternal(key, null, false, true, false);
try {
boolean removed = f.get();
if (statisticsEnabled) {
handleStatisticsOnRemove(false, start, removed);
}
return removed;
} catch (Throwable e) {
throw ExceptionUtil.rethrowAllowedTypeFirst(e, CacheException.class);
}
}
@Override
public boolean remove(K key, V oldValue) {
final long start = System.nanoTime();
final ICompletableFuture f = removeAsyncInternal(key, oldValue, true, true, false);
try {
boolean removed = f.get();
if (statisticsEnabled) {
handleStatisticsOnRemove(false, start, removed);
}
return removed;
} catch (Throwable e) {
throw ExceptionUtil.rethrowAllowedTypeFirst(e, CacheException.class);
}
}
@Override
public V getAndRemove(K key) {
final long start = System.nanoTime();
final ICompletableFuture f = getAndRemoveAsyncInternal(key, true, false);
try {
V removedValue = toObject(f.get());
if (statisticsEnabled) {
handleStatisticsOnRemove(true, start, removedValue);
}
return removedValue;
} catch (Throwable e) {
throw ExceptionUtil.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 keys) {
ensureOpen();
validateNotNull(keys);
removeAllKeysInternal(keys);
}
@Override
public void removeAll() {
ensureOpen();
removeAllInternal();
}
@Override
public void clear() {
ensureOpen();
clearInternal();
}
@Override
public > C getConfiguration(Class 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 invoke(K key, EntryProcessor entryProcessor, Object... arguments)
throws EntryProcessorException {
ensureOpen();
validateNotNull(key);
if (entryProcessor == null) {
throw new NullPointerException("Entry Processor is null");
}
final Data keyData = toData(key);
Data epData = toData(entryProcessor);
List argumentsData = null;
if (arguments != null) {
argumentsData = new ArrayList(arguments.length);
for (int i = 0; i < arguments.length; i++) {
argumentsData.add(toData(arguments[i]));
}
}
final int completionId = nextCompletionId();
ClientMessage request =
CacheEntryProcessorCodec.encodeRequest(nameWithPrefix, keyData, epData, argumentsData, completionId);
try {
final ICompletableFuture f = invoke(request, keyData, completionId);
final ClientMessage response = getSafely(f);
final 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 Map> invokeAll(Set keys, EntryProcessor 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> allResult = new HashMap>();
for (K key : keys) {
CacheEntryProcessorResult ceResult;
try {
final T result = this.invoke(key, entryProcessor, arguments);
ceResult = result != null ? new CacheEntryProcessorResult(result) : null;
} catch (Exception e) {
ceResult = new CacheEntryProcessorResult(e);
}
if (ceResult != null) {
allResult.put(key, ceResult);
}
}
// At client side, we don't know what entry processor does so we ignore it from statistics perspective
return allResult;
}
@Override
public T unwrap(Class 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 cacheEntryListenerConfiguration) {
ensureOpen();
if (cacheEntryListenerConfiguration == null) {
throw new NullPointerException("CacheEntryListenerConfiguration can't be null");
}
final CacheEventListenerAdaptor adaptor =
new CacheEventListenerAdaptor(this, cacheEntryListenerConfiguration,
clientContext.getSerializationService());
final EventHandler handler = createHandler(adaptor);
final ClientMessage registrationRequest = CacheAddEntryListenerCodec.encodeRequest(nameWithPrefix);
final String regId = clientContext.getListenerService().startListening(registrationRequest, null, handler,
new ClientMessageDecoder() {
@Override
public T decodeClientMessage(ClientMessage clientMessage) {
return (T) CacheAddEntryListenerCodec.decodeResponse(clientMessage).response;
}
});
if (regId != null) {
cacheConfig.addCacheEntryListenerConfiguration(cacheEntryListenerConfiguration);
addListenerLocally(regId, cacheEntryListenerConfiguration);
updateCacheListenerConfigOnOtherNodes(cacheEntryListenerConfiguration, true);
}
}
@Override
public void deregisterCacheEntryListener(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) {
if (cacheEntryListenerConfiguration == null) {
throw new NullPointerException("CacheEntryListenerConfiguration can't be null");
}
final String regId = getListenerIdLocal(cacheEntryListenerConfiguration);
if (regId == null) {
return;
}
ClientListenerService listenerService = clientContext.getListenerService();
boolean isDeregistered = listenerService.stopListening(regId, new ListenerRemoveCodec() {
@Override
public ClientMessage encodeRequest(String realRegistrationId) {
return CacheRemoveEntryListenerCodec.encodeRequest(nameWithPrefix, realRegistrationId);
}
@Override
public boolean decodeResponse(ClientMessage clientMessage) {
return CacheRemoveEntryListenerCodec.decodeResponse(clientMessage).response;
}
});
if (isDeregistered) {
removeListenerLocally(cacheEntryListenerConfiguration);
cacheConfig.removeCacheEntryListenerConfiguration(cacheEntryListenerConfiguration);
updateCacheListenerConfigOnOtherNodes(cacheEntryListenerConfiguration, false);
}
}
protected void updateCacheListenerConfigOnOtherNodes(CacheEntryListenerConfiguration cacheEntryListenerConfiguration,
boolean isRegister) {
final Collection members = clientContext.getClusterService().getMemberList();
final HazelcastClientInstanceImpl client = (HazelcastClientInstanceImpl) clientContext.getHazelcastInstance();
final Collection futures = new ArrayList();
for (Member member : members) {
try {
final Address address = ((AbstractMember) member).getAddress();
Data configData = toData(cacheEntryListenerConfiguration);
final ClientMessage request =
CacheListenerRegistrationCodec.encodeRequest(nameWithPrefix, configData,
isRegister, address.getHost(), address.getPort());
final ClientInvocation invocation = new ClientInvocation(client, request, address);
final Future future = invocation.invoke();
futures.add(future);
} catch (Exception e) {
ExceptionUtil.sneakyThrow(e);
}
}
}
@Override
public Iterator> iterator() {
ensureOpen();
return new ClientClusterWideIterator(this, clientContext);
}
}