Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hazelcast.map.impl.querycache.subscriber.DefaultQueryCache Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2024, 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.querycache.subscriber;
import com.hazelcast.cluster.Member;
import com.hazelcast.config.IndexConfig;
import com.hazelcast.config.QueryCacheConfig;
import com.hazelcast.core.EntryEventType;
import com.hazelcast.internal.serialization.Data;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.internal.util.ContextMutexFactory;
import com.hazelcast.internal.util.FutureUtil;
import com.hazelcast.internal.util.MapUtil;
import com.hazelcast.map.IMap;
import com.hazelcast.map.impl.EntryEventFilter;
import com.hazelcast.map.impl.query.QueryEventFilter;
import com.hazelcast.map.impl.querycache.InvokerWrapper;
import com.hazelcast.map.impl.querycache.NodeInvokerWrapper;
import com.hazelcast.map.impl.querycache.QueryCacheContext;
import com.hazelcast.map.impl.querycache.QueryCacheEventService;
import com.hazelcast.map.impl.querycache.accumulator.Accumulator;
import com.hazelcast.map.impl.querycache.accumulator.AccumulatorInfoSupplier;
import com.hazelcast.map.impl.querycache.subscriber.record.QueryCacheRecord;
import com.hazelcast.map.listener.MapListener;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.Predicates;
import com.hazelcast.query.impl.CachedQueryEntry;
import com.hazelcast.query.impl.Index;
import com.hazelcast.query.impl.QueryEntry;
import com.hazelcast.spi.impl.eventservice.EventFilter;
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 java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import static com.hazelcast.query.impl.predicates.PredicateUtils.checkDoesNotContainPagingPredicate;
import static com.hazelcast.internal.nio.IOUtil.closeResource;
import static com.hazelcast.internal.util.FutureUtil.waitWithDeadline;
import static com.hazelcast.internal.util.Preconditions.checkNoNullInside;
import static com.hazelcast.internal.util.Preconditions.checkNotNull;
import static com.hazelcast.map.impl.querycache.subscriber.AbstractQueryCacheEndToEndConstructor.OPERATION_WAIT_TIMEOUT_MINUTES;
import static com.hazelcast.map.impl.querycache.subscriber.EventPublisherHelper.publishEntryEvent;
import static com.hazelcast.map.impl.querycache.subscriber.QueryCacheRequest.newQueryCacheRequest;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MINUTES;
/**
* Default implementation of {@link com.hazelcast.map.QueryCache QueryCache} interface which holds actual data.
*
* @param the key type for this {@link InternalQueryCache}
* @param the value type for this {@link InternalQueryCache}
*/
@SuppressWarnings({"checkstyle:methodcount", "checkstyle:classfanoutcomplexity"})
class DefaultQueryCache extends AbstractInternalQueryCache {
DefaultQueryCache(String cacheId, String cacheName, QueryCacheConfig queryCacheConfig,
IMap delegate, QueryCacheContext context) {
super(cacheId, cacheName, queryCacheConfig, delegate, context);
}
@Override
public void set(K key, V value, EntryEventType eventType) {
setInternal(key, value, eventType, true);
}
@Override
public void prepopulate(Iterator> entries) {
recordStore.addBatch(entries, (entry, oldRecord) -> publishEntryEvent(context, mapName, cacheId,
entry.getKey(), entry.getValue(), oldRecord, EntryEventType.ADDED, extractors));
}
/**
* @param doEvictionCheck when doing pre-population of query cache, set
* this to false since we quit population if we reach max capacity {@link
* #reachedMaxCapacity()}, eviction is not needed.
*/
private void setInternal(K key, V value, EntryEventType eventType, boolean doEvictionCheck) {
Object queryCacheKey = recordStore.toQueryCacheKey(key);
Data valueData = toData(value);
QueryCacheRecord oldRecord = doEvictionCheck
? recordStore.add(queryCacheKey, valueData)
: recordStore.addWithoutEvictionCheck(queryCacheKey, valueData);
if (eventType != null) {
publishEntryEvent(context, mapName, cacheId,
queryCacheKey, valueData, oldRecord, eventType, extractors);
}
}
@Override
public void delete(Object key, EntryEventType eventType) {
checkNotNull(key, "key cannot be null");
Object queryCacheKey = recordStore.toQueryCacheKey(key);
QueryCacheRecord oldRecord = recordStore.remove(queryCacheKey);
if (oldRecord == null) {
return;
}
if (eventType != null) {
publishEntryEvent(context, mapName, cacheId, queryCacheKey,
null, oldRecord, eventType, extractors);
}
}
@Override
public boolean tryRecover() {
SubscriberAccumulator subscriberAccumulator = getOrNullSubscriberAccumulator();
if (subscriberAccumulator == null) {
return false;
}
ConcurrentMap brokenSequences = subscriberAccumulator.getBrokenSequences();
if (brokenSequences.isEmpty()) {
return true;
}
return isTryRecoverSucceeded(brokenSequences);
}
/**
* This tries to reset cursor position of the accumulator to the supplied sequence,
* if that sequence is still there, it will be succeeded, otherwise query cache content stays inconsistent.
*/
private boolean isTryRecoverSucceeded(ConcurrentMap brokenSequences) {
int numberOfBrokenSequences = brokenSequences.size();
InvokerWrapper invokerWrapper = context.getInvokerWrapper();
SubscriberContext subscriberContext = context.getSubscriberContext();
SubscriberContextSupport subscriberContextSupport = subscriberContext.getSubscriberContextSupport();
List> futures = new ArrayList<>(numberOfBrokenSequences);
for (Map.Entry entry : brokenSequences.entrySet()) {
Integer partitionId = entry.getKey();
Long sequence = entry.getValue();
Object recoveryOperation
= subscriberContextSupport.createRecoveryOperation(mapName, cacheId, sequence, partitionId);
Future future
= (Future)
invokerWrapper.invokeOnPartitionOwner(recoveryOperation, partitionId);
futures.add(future);
}
Collection results = FutureUtil.returnWithDeadline(futures, 1, MINUTES);
int successCount = 0;
for (Object object : results) {
Boolean resolvedResponse = subscriberContextSupport.resolveResponseForRecoveryOperation(object);
if (TRUE.equals(resolvedResponse)) {
successCount++;
}
}
return successCount == numberOfBrokenSequences;
}
@Override
public void destroy() {
removeAccumulatorInfo();
removeSubscriberRegistry();
removeInternalQueryCache();
ContextMutexFactory.Mutex mutex = context.getLifecycleMutexFactory().mutexFor(mapName);
try {
synchronized (mutex) {
destroyRemoteResources();
removeAllUserDefinedListeners();
}
} finally {
closeResource(mutex);
}
}
private void destroyRemoteResources() {
SubscriberContext subscriberContext = context.getSubscriberContext();
SubscriberContextSupport subscriberContextSupport = subscriberContext.getSubscriberContextSupport();
InvokerWrapper invokerWrapper = context.getInvokerWrapper();
if (invokerWrapper instanceof NodeInvokerWrapper) {
subscriberContext.getEventService().removePublisherListener(mapName, cacheId, publisherListenerId);
Collection memberList = context.getMemberList();
List futures = new ArrayList<>(memberList.size());
for (Member member : memberList) {
Object removePublisher = subscriberContextSupport
.createDestroyQueryCacheOperation(mapName, cacheId);
Future future = invokerWrapper.invokeOnTarget(removePublisher, member);
futures.add(future);
}
waitWithDeadline(futures, OPERATION_WAIT_TIMEOUT_MINUTES, MINUTES);
} else {
try {
subscriberContext.getEventService()
.removePublisherListener(mapName, cacheId, publisherListenerId);
} finally {
Object removePublisher = subscriberContextSupport
.createDestroyQueryCacheOperation(mapName, cacheId);
invokerWrapper.invoke(removePublisher, false);
}
}
}
/**
* Removes listeners which are registered by users to query-cache via {@link com.hazelcast.map.QueryCache#addEntryListener}
* These listeners are called user defined listeners to distinguish them from listeners which are required for internal
* implementation of query-cache. User defined listeners are local to query-caches.
*/
private void removeAllUserDefinedListeners() {
context.getQueryCacheEventService().removeAllListeners(mapName, cacheId);
}
private boolean removeSubscriberRegistry() {
SubscriberContext subscriberContext = context.getSubscriberContext();
MapSubscriberRegistry mapSubscriberRegistry = subscriberContext.getMapSubscriberRegistry();
SubscriberRegistry subscriberRegistry = mapSubscriberRegistry.getOrNull(mapName);
if (subscriberRegistry == null) {
return true;
}
subscriberRegistry.remove(cacheId);
return false;
}
private void removeAccumulatorInfo() {
SubscriberContext subscriberContext = context.getSubscriberContext();
AccumulatorInfoSupplier accumulatorInfoSupplier = subscriberContext.getAccumulatorInfoSupplier();
accumulatorInfoSupplier.remove(mapName, cacheId);
}
private boolean removeInternalQueryCache() {
SubscriberContext subscriberContext = context.getSubscriberContext();
QueryCacheEndToEndProvider cacheProvider = subscriberContext.getEndToEndQueryCacheProvider();
cacheProvider.removeSingleQueryCache(mapName, cacheName);
clear();
return subscriberContext.getQueryCacheFactory().remove(this);
}
@Override
public boolean containsKey(Object key) {
checkNotNull(key, "key cannot be null");
Object queryCacheKey = recordStore.toQueryCacheKey(key);
return recordStore.containsKey(queryCacheKey);
}
@Override
public boolean containsValue(Object value) {
checkNotNull(value, "value cannot be null");
return recordStore.containsValue(value);
}
@Override
public V get(Object key) {
checkNotNull(key, "key cannot be null");
Object queryCacheKey = recordStore.toQueryCacheKey(key);
QueryCacheRecord record = recordStore.get(queryCacheKey);
// this is not a known key for the scope of this query-cache.
if (record == null) {
return null;
}
if (includeValue) {
Object valueInRecord = record.getValue();
return toObject(valueInRecord);
} else {
// if value caching is not enabled, we fetch the value from underlying IMap
// for every request
return (V) getDelegate().get(queryCacheKey);
}
}
@Override
public Map getAll(Set keys) {
checkNotNull(keys, "keys cannot be null");
checkNoNullInside(keys, "supplied key-set cannot contain null key");
if (keys.isEmpty()) {
return Collections.emptyMap();
}
if (!includeValue) {
return getDelegate().getAll(keys);
}
Map map = MapUtil.createHashMap(keys.size());
for (K key : keys) {
Object queryCacheKey = recordStore.toQueryCacheKey(key);
QueryCacheRecord record = recordStore.get(queryCacheKey);
if (record != null) {
V value = toObject(record.getValue());
map.put(key, value);
}
}
return map;
}
@Override
public Set keySet() {
return keySet(Predicates.alwaysTrue());
}
@Override
public Collection values() {
return values(Predicates.alwaysTrue());
}
@Override
public Set> entrySet() {
return entrySet(Predicates.alwaysTrue());
}
@Override
public Set keySet(Predicate predicate) {
checkNotNull(predicate, "Predicate cannot be null!");
return scanAndGetResult(predicate, ResultType.KEY);
}
@Override
public Collection values(Predicate predicate) {
checkNotNull(predicate, "Predicate cannot be null!");
if (!includeValue) {
return Collections.emptySet();
}
return scanAndGetResult(predicate, ResultType.VALUE);
}
@Override
public Set> entrySet(Predicate predicate) {
checkNotNull(predicate, "Predicate cannot be null!");
return scanAndGetResult(predicate, ResultType.ENTRY);
}
@Override
public boolean isEmpty() {
return recordStore.isEmpty();
}
@Override
public int size() {
return recordStore.size();
}
@Override
public UUID addEntryListener(MapListener listener, boolean includeValue) {
checkNotNull(listener, "listener cannot be null");
return addEntryListenerInternal(listener, null, includeValue);
}
@Override
public UUID addEntryListener(MapListener listener, K key, boolean includeValue) {
checkNotNull(listener, "listener cannot be null");
return addEntryListenerInternal(listener, key, includeValue);
}
private UUID addEntryListenerInternal(MapListener listener, K key, boolean includeValue) {
checkNotNull(listener, "listener cannot be null");
Data keyData = toData(key);
EventFilter filter = new EntryEventFilter(keyData, includeValue);
QueryCacheEventService eventService = getEventService();
String mapName = delegate.getName();
return eventService.addListener(mapName, cacheId, listener, filter);
}
@Override
public UUID addEntryListener(MapListener listener, Predicate predicate, boolean includeValue) {
checkNotNull(listener, "listener cannot be null");
checkNotNull(predicate, "predicate cannot be null");
checkDoesNotContainPagingPredicate(predicate, "addEntryListener");
QueryCacheEventService eventService = getEventService();
EventFilter filter = new QueryEventFilter(null, predicate, includeValue);
String mapName = delegate.getName();
return eventService.addListener(mapName, cacheId, listener, filter);
}
@Override
public UUID addEntryListener(MapListener listener,
Predicate predicate, K key, boolean includeValue) {
checkNotNull(listener, "listener cannot be null");
checkNotNull(predicate, "predicate cannot be null");
checkNotNull(key, "key cannot be null");
checkDoesNotContainPagingPredicate(predicate, "addEntryListener");
QueryCacheEventService eventService = getEventService();
EventFilter filter = new QueryEventFilter(toData(key), predicate, includeValue);
String mapName = delegate.getName();
return eventService.addListener(mapName, cacheId, listener, filter);
}
@Override
public boolean removeEntryListener(UUID id) {
checkNotNull(id, "listener ID cannot be null");
QueryCacheEventService eventService = getEventService();
return eventService.removeListener(mapName, cacheId, id);
}
@Override
public void addIndex(IndexConfig config) {
checkNotNull(config, "Index config cannot be null.");
assert indexRegistry.isGlobal();
IndexConfig config0 = getNormalizedIndexConfig(config);
indexRegistry.addOrGetIndex(config0);
InternalSerializationService serializationService = context.getSerializationService();
CachedQueryEntry, ?> newEntry = new CachedQueryEntry<>(serializationService, extractors);
Set> entries = recordStore.entrySet();
for (Map.Entry entry : entries) {
Object queryCacheKey = entry.getKey();
QueryCacheRecord record = entry.getValue();
Object value = record.getValue();
Data keyData = toData(queryCacheKey);
QueryEntry queryable = new QueryEntry(serializationService, keyData, value, extractors);
newEntry.init(keyData, value);
indexRegistry.putEntry(newEntry, null, queryable, Index.OperationSource.USER);
}
}
@Override
public String getName() {
return cacheName;
}
@Override
public IMap getDelegate() {
return delegate;
}
@Override
public void recreate() {
ContextMutexFactory.Mutex mutex = context.getLifecycleMutexFactory().mutexFor(mapName);
try {
// done other mutex to prevent races with `destroy`
synchronized (mutex) {
SubscriberContext subscriberContext = context.getSubscriberContext();
// 0. Check subscriber still exists
SubscriberAccumulator subscriberAccumulator = getOrNullSubscriberAccumulator();
if (subscriberAccumulator == null) {
return;
}
// 1. Reset client-side subscriber resources
subscriberAccumulator.reset();
// 2. Reset/recreate server-side publisher resources
QueryCacheRequest request = newQueryCacheRequest()
.withCacheName(cacheName)
.forMap(delegate)
.urgent(true)
.withContext(context);
QueryCacheEndToEndProvider queryCacheEndToEndProvider
= subscriberContext.getEndToEndQueryCacheProvider();
queryCacheEndToEndProvider.tryCreateQueryCache(mapName, cacheName,
subscriberContext.newEndToEndConstructor(request));
}
} finally {
closeResource(mutex);
}
}
private SubscriberAccumulator getOrNullSubscriberAccumulator() {
SubscriberContext subscriberContext = context.getSubscriberContext();
MapSubscriberRegistry mapSubscriberRegistry = subscriberContext.getMapSubscriberRegistry();
SubscriberRegistry subscriberRegistry = mapSubscriberRegistry.getOrNull(mapName);
if (subscriberRegistry == null) {
return null;
}
Accumulator accumulator = subscriberRegistry.getOrNull(cacheId);
if (accumulator == null) {
return null;
}
return (SubscriberAccumulator) accumulator;
}
@Override
public int removeEntriesOf(int partitionId) {
int removedEntryCount = 0;
Set keys = recordStore.keySet();
for (Object queryCacheKey : keys) {
if (context.getPartitionId(queryCacheKey) == partitionId) {
if (recordStore.remove(queryCacheKey) != null) {
removedEntryCount++;
}
}
}
return removedEntryCount;
}
@Override
public String toString() {
return "DefaultQueryCache{"
+ "mapName='" + mapName + '\''
+ ", cacheId='" + cacheId + '\''
+ ", cacheName='" + cacheName + '\''
+ '}';
}
}