/* * 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.querycache.subscriber; import com.hazelcast.core.EntryEventType; import com.hazelcast.core.IMap; import com.hazelcast.core.Member; import com.hazelcast.internal.serialization.InternalSerializationService; 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.QueryCacheConfigurator; 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.nio.Address; import com.hazelcast.nio.serialization.Data; import com.hazelcast.query.Predicate; import com.hazelcast.query.TruePredicate; import com.hazelcast.query.impl.Indexes; import com.hazelcast.query.impl.QueryEntry; import com.hazelcast.query.impl.QueryableEntry; import com.hazelcast.query.impl.getters.Extractors; import com.hazelcast.spi.EventFilter; import com.hazelcast.util.FutureUtil; import com.hazelcast.util.MapUtil; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import static com.hazelcast.util.Preconditions.checkNotNull; 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 <K> the key type for this {@link InternalQueryCache} * @param <V> the value type for this {@link InternalQueryCache} */ @SuppressWarnings("checkstyle:methodcount") class DefaultQueryCache<K, V> extends AbstractInternalQueryCache<K, V> { public DefaultQueryCache(String cacheName, String userGivenCacheName, IMap delegate, QueryCacheContext context) { super(cacheName, userGivenCacheName, delegate, context); } @Override public void setInternal(K key, V value, boolean callDelegate, EntryEventType eventType) { Data keyData = toData(key); Data valueData = toData(value); if (callDelegate) { getDelegate().set(keyData, valueData); } QueryCacheRecord oldRecord = recordStore.add(keyData, valueData); if (eventType != null) { EventPublisherHelper.publishEntryEvent(context, mapName, cacheName, keyData, valueData, oldRecord, eventType); } } @Override public void deleteInternal(Object key, boolean callDelegate, EntryEventType eventType) { checkNotNull(key, "key cannot be null"); Data keyData = toData(key); if (callDelegate) { getDelegate().delete(keyData); } QueryCacheRecord oldRecord = recordStore.remove(keyData); if (oldRecord == null) { return; } if (eventType != null) { EventPublisherHelper.publishEntryEvent(context, mapName, cacheName, keyData, null, oldRecord, eventType); } } @Override public void clearInternal(EntryEventType eventType) { int removedCount = recordStore.clear(); if (removedCount < 1) { return; } if (eventType != null) { EventPublisherHelper.publishCacheWideEvent(context, mapName, cacheName, removedCount, eventType); } } @Override public boolean tryRecover() { SubscriberContext subscriberContext = context.getSubscriberContext(); MapSubscriberRegistry mapSubscriberRegistry = subscriberContext.getMapSubscriberRegistry(); SubscriberRegistry subscriberRegistry = mapSubscriberRegistry.getOrNull(mapName); if (subscriberRegistry == null) { return true; } Accumulator accumulator = subscriberRegistry.getOrNull(cacheName); if (accumulator == null) { return true; } SubscriberAccumulator subscriberAccumulator = (SubscriberAccumulator) accumulator; ConcurrentMap<Integer, Long> 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<Integer, Long> brokenSequences) { int numberOfBrokenSequences = brokenSequences.size(); InvokerWrapper invokerWrapper = context.getInvokerWrapper(); SubscriberContext subscriberContext = context.getSubscriberContext(); SubscriberContextSupport subscriberContextSupport = subscriberContext.getSubscriberContextSupport(); List<Future<Object>> futures = new ArrayList<Future<Object>>(numberOfBrokenSequences); for (Map.Entry<Integer, Long> entry : brokenSequences.entrySet()) { Integer partitionId = entry.getKey(); Long sequence = entry.getValue(); Object recoveryOperation = subscriberContextSupport.createRecoveryOperation(mapName, cacheName, sequence, partitionId); Future<Object> future = (Future<Object>) invokerWrapper.invokeOnPartitionOwner(recoveryOperation, partitionId); futures.add(future); } Collection<Object> 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() { boolean destroyed = destroyLocalResources(); if (!destroyed) { return; } destroyRemoteResources(); } private void destroyRemoteResources() { SubscriberContext subscriberContext = context.getSubscriberContext(); SubscriberContextSupport subscriberContextSupport = subscriberContext.getSubscriberContextSupport(); InvokerWrapper invokerWrapper = context.getInvokerWrapper(); if (invokerWrapper instanceof NodeInvokerWrapper) { Collection<Member> memberList = context.getMemberList(); for (Member member : memberList) { Address address = member.getAddress(); Object removePublisher = subscriberContextSupport.createDestroyQueryCacheOperation(mapName, userGivenCacheName); invokerWrapper.invokeOnTarget(removePublisher, address); } } else { try { subscriberContext.getEventService().removePublisherListener(mapName, publisherListenerId); } finally { Object removePublisher = subscriberContextSupport.createDestroyQueryCacheOperation(mapName, userGivenCacheName); invokerWrapper.invoke(removePublisher); } } } private boolean destroyLocalResources() { removeConfig(); removeAccumulatorInfo(); removeSubscriberRegistry(); return removeInternalQueryCache(); } private boolean removeSubscriberRegistry() { SubscriberContext subscriberContext = context.getSubscriberContext(); MapSubscriberRegistry mapSubscriberRegistry = subscriberContext.getMapSubscriberRegistry(); SubscriberRegistry subscriberRegistry = mapSubscriberRegistry.getOrNull(mapName); if (subscriberRegistry == null) { return true; } subscriberRegistry.remove(cacheName); return false; } private void removeAccumulatorInfo() { SubscriberContext subscriberContext = context.getSubscriberContext(); AccumulatorInfoSupplier accumulatorInfoSupplier = subscriberContext.getAccumulatorInfoSupplier(); accumulatorInfoSupplier.remove(mapName, cacheName); } private void removeConfig() { SubscriberContext subscriberContext = context.getSubscriberContext(); QueryCacheConfigurator queryCacheConfigurator = subscriberContext.geQueryCacheConfigurator(); queryCacheConfigurator.removeConfiguration(mapName, userGivenCacheName); } private boolean removeInternalQueryCache() { SubscriberContext subscriberContext = context.getSubscriberContext(); QueryCacheEndToEndProvider cacheProvider = subscriberContext.getEndToEndQueryCacheProvider(); InternalQueryCache internalQueryCache = cacheProvider.remove(mapName, userGivenCacheName); boolean exists = internalQueryCache != null; if (exists) { internalQueryCache.clear(); } return exists; } @Override public boolean containsKey(Object key) { checkNotNull(key, "key cannot be null"); Data keyData = toData(key); return recordStore.containsKey(keyData); } @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"); Data keyData = toData(key); QueryCacheRecord record = recordStore.get(keyData); // 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(keyData); } } @Override public Map<K, V> getAll(Set<K> keys) { checkNotNull(keys, "keys cannot be null"); if (keys.isEmpty()) { return Collections.emptyMap(); } if (!includeValue) { return getDelegate().getAll(keys); } Map<K, V> map = MapUtil.createHashMap(keys.size()); for (K key : keys) { Data keyData = toData(key); QueryCacheRecord record = recordStore.get(keyData); Object valueInRecord = record.getValue(); V value = toObject(valueInRecord); map.put(key, value); } return map; } @Override public Set<K> keySet() { return keySet(TruePredicate.INSTANCE); } @Override public Collection<V> values() { return values(TruePredicate.INSTANCE); } @Override public Set<Map.Entry<K, V>> entrySet() { return entrySet(TruePredicate.INSTANCE); } @Override public Set<K> keySet(Predicate predicate) { checkNotNull(predicate, "Predicate cannot be null!"); Set<K> resultingSet = new HashSet<K>(); Set<QueryableEntry> query = indexes.query(predicate); if (query != null) { for (QueryableEntry entry : query) { K key = (K) entry.getKey(); resultingSet.add(key); } } else { doFullKeyScan(predicate, resultingSet); } return resultingSet; } @Override public Set<Map.Entry<K, V>> entrySet(Predicate predicate) { checkNotNull(predicate, "Predicate cannot be null!"); Set<Map.Entry<K, V>> resultingSet = new HashSet<Map.Entry<K, V>>(); Set<QueryableEntry> query = indexes.query(predicate); if (query != null) { if (query.isEmpty()) { return Collections.emptySet(); } for (QueryableEntry entry : query) { resultingSet.add(entry); } } else { doFullEntryScan(predicate, resultingSet); } return resultingSet; } @Override public Collection<V> values(Predicate predicate) { checkNotNull(predicate, "Predicate cannot be null!"); if (!includeValue) { return Collections.emptySet(); } Set<V> resultingSet = new HashSet<V>(); Set<QueryableEntry> query = indexes.query(predicate); if (query != null) { for (QueryableEntry entry : query) { resultingSet.add((V) entry.getValue()); } } else { doFullValueScan(predicate, resultingSet); } return resultingSet; } @Override public boolean isEmpty() { return recordStore.isEmpty(); } @Override public int size() { return recordStore.size(); } @Override public String addEntryListener(MapListener listener, boolean includeValue) { checkNotNull(listener, "listener cannot be null"); return addEntryListenerInternal(listener, null, includeValue); } @Override public String addEntryListener(MapListener listener, K key, boolean includeValue) { checkNotNull(listener, "listener cannot be null"); return addEntryListenerInternal(listener, key, includeValue); } private String addEntryListenerInternal(MapListener listener, K key, boolean includeValue) { checkNotNull(listener, "listener cannot be null"); Data keyData = toData(key); EventFilter filter = new EntryEventFilter(includeValue, keyData); QueryCacheEventService eventService = getEventService(); String mapName = delegate.getName(); return eventService.addListener(mapName, cacheName, listener, filter); } @Override public String addEntryListener(MapListener listener, Predicate<K, V> predicate, boolean includeValue) { checkNotNull(listener, "listener cannot be null"); checkNotNull(predicate, "predicate cannot be null"); QueryCacheEventService eventService = getEventService(); EventFilter filter = new QueryEventFilter(includeValue, null, predicate); String mapName = delegate.getName(); return eventService.addListener(mapName, cacheName, listener, filter); } @Override public String addEntryListener(MapListener listener, Predicate<K, V> predicate, K key, boolean includeValue) { checkNotNull(listener, "listener cannot be null"); checkNotNull(predicate, "predicate cannot be null"); checkNotNull(key, "key cannot be null"); QueryCacheEventService eventService = getEventService(); EventFilter filter = new QueryEventFilter(includeValue, toData(key), predicate); String mapName = delegate.getName(); return eventService.addListener(mapName, cacheName, listener, filter); } @Override public boolean removeEntryListener(String id) { checkNotNull(id, "listener id cannot be null"); QueryCacheEventService eventService = getEventService(); return eventService.removeListener(mapName, cacheName, id); } @Override public void addIndex(String attribute, boolean ordered) { checkNotNull(attribute, "attribute cannot be null"); getIndexes().addOrGetIndex(attribute, ordered); InternalSerializationService serializationService = context.getSerializationService(); Set<Map.Entry<Data, QueryCacheRecord>> entries = recordStore.entrySet(); for (Map.Entry<Data, QueryCacheRecord> entry : entries) { Data keyData = entry.getKey(); QueryCacheRecord record = entry.getValue(); Object value = record.getValue(); QueryEntry queryable = new QueryEntry(serializationService, keyData, value, Extractors.empty()); indexes.saveEntryIndex(queryable, null); } } @Override public String getName() { return userGivenCacheName; } @Override public IMap getDelegate() { return delegate; } @Override public Indexes getIndexes() { return indexes; } @Override public String toString() { return recordStore.toString(); } }