/*
* 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.map.impl.proxy;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.NearCacheConfig;
import com.hazelcast.core.ExecutionCallback;
import com.hazelcast.internal.nearcache.NearCache;
import com.hazelcast.internal.nearcache.impl.invalidation.BatchNearCacheInvalidation;
import com.hazelcast.internal.nearcache.impl.invalidation.Invalidation;
import com.hazelcast.internal.nearcache.impl.invalidation.RepairingHandler;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.map.impl.MapEntries;
import com.hazelcast.map.impl.MapService;
import com.hazelcast.map.impl.nearcache.MapNearCacheManager;
import com.hazelcast.map.impl.nearcache.invalidation.InvalidationListener;
import com.hazelcast.map.impl.nearcache.invalidation.UuidFilter;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.query.Predicate;
import com.hazelcast.spi.EventFilter;
import com.hazelcast.spi.ExecutionService;
import com.hazelcast.spi.InternalCompletableFuture;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.util.executor.CompletedFuture;
import java.util.Collection;
import java.util.Iterator;
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.spi.ExecutionService.ASYNC_EXECUTOR;
import static com.hazelcast.util.ExceptionUtil.rethrow;
import static com.hazelcast.util.MapUtil.createHashMap;
import static java.util.Collections.emptyMap;
/**
* A server-side {@code IMap} implementation which is fronted by a Near Cache.
*
* @param <K> the key type for this {@code IMap} proxy.
* @param <V> the value type for this {@code IMap} proxy.
*/
public class NearCachedMapProxyImpl<K, V> extends MapProxyImpl<K, V> {
private final boolean cacheLocalEntries;
private final boolean invalidateOnChange;
private MapNearCacheManager mapNearCacheManager;
private NearCache<Object, Object> nearCache;
private RepairingHandler repairingHandler;
private volatile String invalidationListenerId;
public NearCachedMapProxyImpl(String name, MapService mapService, NodeEngine nodeEngine, MapConfig mapConfig) {
super(name, mapService, nodeEngine, mapConfig);
NearCacheConfig nearCacheConfig = mapConfig.getNearCacheConfig();
cacheLocalEntries = nearCacheConfig.isCacheLocalEntries();
invalidateOnChange = nearCacheConfig.isInvalidateOnChange();
}
public NearCache<Object, Object> getNearCache() {
return nearCache;
}
@Override
public void initialize() {
super.initialize();
mapNearCacheManager = mapServiceContext.getMapNearCacheManager();
nearCache = mapNearCacheManager.getOrCreateNearCache(name, mapConfig.getNearCacheConfig());
if (invalidateOnChange) {
registerInvalidationListener();
}
}
// this operation returns the value as Data, except when it's retrieved from Near Cache with in-memory-format OBJECT
@Override
@SuppressWarnings("unchecked")
protected V getInternal(Data key) {
V value = (V) getCachedValue(key, true);
if (value != NOT_CACHED) {
return value;
}
try {
long reservationId = tryReserveForUpdate(key);
value = (V) super.getInternal(key);
if (reservationId != NOT_RESERVED) {
value = (V) tryPublishReserved(key, value, reservationId);
}
return value;
} catch (Throwable throwable) {
invalidateNearCache(key);
throw rethrow(throwable);
}
}
@Override
protected InternalCompletableFuture<Data> getAsyncInternal(final Data key) {
Object value = getCachedValue(key, false);
if (value != NOT_CACHED) {
ExecutionService executionService = getNodeEngine().getExecutionService();
return new CompletedFuture<Data>(serializationService, value, executionService.getExecutor(ASYNC_EXECUTOR));
}
final long reservationId = tryReserveForUpdate(key);
InternalCompletableFuture<Data> future;
try {
future = super.getAsyncInternal(key);
} catch (Throwable t) {
invalidateNearCache(key);
throw rethrow(t);
}
if (reservationId != NOT_RESERVED) {
future.andThen(new ExecutionCallback<Data>() {
@Override
public void onResponse(Data value) {
nearCache.tryPublishReserved(key, value, reservationId, false);
}
@Override
public void onFailure(Throwable t) {
invalidateNearCache(key);
}
});
}
return future;
}
@Override
protected Data putInternal(Data key, Data value, long ttl, TimeUnit timeunit) {
try {
return super.putInternal(key, value, ttl, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected boolean tryPutInternal(Data key, Data value, long timeout, TimeUnit timeunit) {
try {
return super.tryPutInternal(key, value, timeout, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected Data putIfAbsentInternal(Data key, Data value, long ttl, TimeUnit timeunit) {
try {
return super.putIfAbsentInternal(key, value, ttl, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected void putTransientInternal(Data key, Data value, long ttl, TimeUnit timeunit) {
try {
super.putTransientInternal(key, value, ttl, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected InternalCompletableFuture<Data> putAsyncInternal(Data key, Data value, long ttl, TimeUnit timeunit) {
try {
return super.putAsyncInternal(key, value, ttl, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected InternalCompletableFuture<Data> setAsyncInternal(Data key, Data value, long ttl, TimeUnit timeunit) {
try {
return super.setAsyncInternal(key, value, ttl, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected boolean replaceInternal(Data key, Data expect, Data update) {
try {
return super.replaceInternal(key, expect, update);
} finally {
invalidateNearCache(key);
}
}
@Override
protected Data replaceInternal(Data key, Data value) {
try {
return super.replaceInternal(key, value);
} finally {
invalidateNearCache(key);
}
}
@Override
protected void setInternal(Data key, Data value, long ttl, TimeUnit timeunit) {
try {
super.setInternal(key, value, ttl, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected boolean evictInternal(Data key) {
try {
return super.evictInternal(key);
} finally {
invalidateNearCache(key);
}
}
@Override
protected void evictAllInternal() {
try {
super.evictAllInternal();
} finally {
nearCache.clear();
}
}
@Override
public void clearInternal() {
try {
super.clearInternal();
} finally {
nearCache.clear();
}
}
@Override
public void loadAllInternal(boolean replaceExistingValues) {
try {
super.loadAllInternal(replaceExistingValues);
} finally {
if (replaceExistingValues) {
nearCache.clear();
}
}
}
@Override
protected void loadInternal(Iterable<Data> keys, boolean replaceExistingValues) {
try {
super.loadInternal(keys, replaceExistingValues);
} finally {
invalidateNearCache(keys);
}
}
@Override
protected Data removeInternal(Data key) {
try {
return super.removeInternal(key);
} finally {
invalidateNearCache(key);
}
}
@Override
protected void removeAllInternal(Predicate predicate) {
try {
super.removeAllInternal(predicate);
} finally {
nearCache.clear();
}
}
@Override
protected void deleteInternal(Data key) {
try {
super.deleteInternal(key);
} finally {
invalidateNearCache(key);
}
}
@Override
protected boolean removeInternal(Data key, Data value) {
try {
return super.removeInternal(key, value);
} finally {
invalidateNearCache(key);
}
}
@Override
protected boolean tryRemoveInternal(Data key, long timeout, TimeUnit timeunit) {
try {
return super.tryRemoveInternal(key, timeout, timeunit);
} finally {
invalidateNearCache(key);
}
}
@Override
protected InternalCompletableFuture<Data> removeAsyncInternal(Data key) {
try {
return super.removeAsyncInternal(key);
} finally {
invalidateNearCache(key);
}
}
@Override
protected boolean containsKeyInternal(Data keyData) {
Object cachedValue = getCachedValue(keyData, false);
if (cachedValue != NOT_CACHED) {
return true;
}
return super.containsKeyInternal(keyData);
}
@Override
protected void getAllObjectInternal(List<Data> dataKeys, List<Object> resultingKeyValuePairs) {
getCachedValues(dataKeys, resultingKeyValuePairs);
Map<Data, Long> reservations = emptyMap();
try {
reservations = tryReserveForUpdate(dataKeys);
int currentSize = resultingKeyValuePairs.size();
super.getAllObjectInternal(dataKeys, resultingKeyValuePairs);
for (int i = currentSize; i < resultingKeyValuePairs.size(); ) {
Data key = toData(resultingKeyValuePairs.get(i++));
Data value = toData(resultingKeyValuePairs.get(i++));
Long reservationId = reservations.get(key);
if (reservationId != null) {
Object cachedValue = tryPublishReserved(key, value, reservationId);
resultingKeyValuePairs.set(i - 1, cachedValue);
reservations.remove(key);
}
}
} finally {
releaseReservedKeys(reservations);
}
}
private Map<Data, Long> tryReserveForUpdate(List<Data> keys) {
Map<Data, Long> reservedKeys = createHashMap(keys.size());
for (Data key : keys) {
long reservationId = tryReserveForUpdate(key);
if (reservationId != NOT_RESERVED) {
reservedKeys.put(key, reservationId);
}
}
return reservedKeys;
}
private void releaseReservedKeys(Map<Data, Long> reservationResults) {
for (Data key : reservationResults.keySet()) {
invalidateNearCache(key);
}
}
@Override
protected void invokePutAllOperationFactory(Address address, long size, int[] partitions, MapEntries[] entries)
throws Exception {
try {
super.invokePutAllOperationFactory(address, size, partitions, entries);
} finally {
for (MapEntries mapEntries : entries) {
for (int i = 0; i < mapEntries.size(); i++) {
invalidateNearCache(mapEntries.getKey(i));
}
}
}
}
@Override
public Data executeOnKeyInternal(Data key, EntryProcessor entryProcessor) {
try {
return super.executeOnKeyInternal(key, entryProcessor);
} finally {
invalidateNearCache(key);
}
}
@Override
public Map executeOnKeysInternal(Set<Data> keys, EntryProcessor entryProcessor) {
try {
return super.executeOnKeysInternal(keys, entryProcessor);
} finally {
invalidateNearCache(keys);
}
}
@Override
public InternalCompletableFuture<Object> executeOnKeyInternal(Data key, EntryProcessor entryProcessor,
ExecutionCallback<Object> callback) {
try {
return super.executeOnKeyInternal(key, entryProcessor, callback);
} finally {
invalidateNearCache(key);
}
}
@Override
public void executeOnEntriesInternal(EntryProcessor entryProcessor, Predicate predicate, List<Data> resultingKeyValuePairs) {
try {
super.executeOnEntriesInternal(entryProcessor, predicate, resultingKeyValuePairs);
} finally {
for (int i = 0; i < resultingKeyValuePairs.size(); i += 2) {
Data key = resultingKeyValuePairs.get(i);
invalidateNearCache(key);
}
}
}
@Override
protected boolean preDestroy() {
if (invalidateOnChange) {
mapNearCacheManager.deregisterRepairingHandler(name);
removeEntryListener(invalidationListenerId);
}
return super.preDestroy();
}
private void getCachedValues(List<Data> keys, List<Object> resultingKeyValuePairs) {
Iterator<Data> iterator = keys.iterator();
while (iterator.hasNext()) {
Data key = iterator.next();
Object value = getCachedValue(key, true);
if (value == null || value == NOT_CACHED) {
continue;
}
resultingKeyValuePairs.add(toObject(key));
resultingKeyValuePairs.add(toObject(value));
iterator.remove();
}
}
protected void invalidateNearCache(Data key) {
if (key == null) {
return;
}
nearCache.remove(key);
}
protected void invalidateNearCache(Collection<Data> keys) {
for (Data key : keys) {
nearCache.remove(key);
}
}
protected void invalidateNearCache(Iterable<Data> keys) {
for (Data key : keys) {
nearCache.remove(key);
}
}
private Object tryPublishReserved(Data key, Object value, long reservationId) {
assert value != NOT_CACHED;
// `value` is cached even if it's null
Object cachedValue = nearCache.tryPublishReserved(key, value, reservationId, true);
return cachedValue != null ? cachedValue : value;
}
private Object getCachedValue(Data key, boolean deserializeValue) {
Object value = nearCache.get(key);
if (value == null) {
return NOT_CACHED;
}
if (value == CACHED_AS_NULL) {
return null;
}
mapServiceContext.interceptAfterGet(name, value);
return deserializeValue ? toObject(value) : value;
}
private long tryReserveForUpdate(Data key) {
if (!cachingAllowedFor(key)) {
return NOT_RESERVED;
}
return nearCache.tryReserveForUpdate(key);
}
private boolean cachingAllowedFor(Data key) {
return cacheLocalEntries || !isOwn(key);
}
private boolean isOwn(Data key) {
int partitionId = partitionService.getPartitionId(key);
Address partitionOwner = partitionService.getPartitionOwner(partitionId);
return thisAddress.equals(partitionOwner);
}
public String addNearCacheInvalidationListener(InvalidationListener listener) {
// local member uuid may change after a split-brain merge
String localMemberUuid = getNodeEngine().getClusterService().getLocalMember().getUuid();
EventFilter eventFilter = new UuidFilter(localMemberUuid);
return mapServiceContext.addEventListener(listener, eventFilter, name);
}
private void registerInvalidationListener() {
repairingHandler = mapNearCacheManager.newRepairingHandler(name, nearCache);
invalidationListenerId = addNearCacheInvalidationListener(new NearCacheInvalidationListener());
}
private final class NearCacheInvalidationListener implements InvalidationListener {
@Override
public void onInvalidate(Invalidation invalidation) {
assert invalidation != null;
if (invalidation instanceof BatchNearCacheInvalidation) {
List<Invalidation> batch = ((BatchNearCacheInvalidation) invalidation).getInvalidations();
for (Invalidation single : batch) {
handleInternal(single);
}
} else {
handleInternal(invalidation);
}
}
private void handleInternal(Invalidation single) {
repairingHandler.handle(single.getKey(), single.getSourceUuid(), single.getPartitionUuid(), single.getSequence());
}
}
}