/* * 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.cache.impl; import com.hazelcast.cache.CacheNotExistsException; import com.hazelcast.cache.impl.maxsize.impl.EntryCountCacheEvictionChecker; import com.hazelcast.cache.impl.record.CacheRecord; import com.hazelcast.cache.impl.record.SampleableCacheRecordMap; import com.hazelcast.config.CacheConfig; import com.hazelcast.config.EvictionConfig; import com.hazelcast.config.EvictionConfig.MaxSizePolicy; import com.hazelcast.config.InMemoryFormat; import com.hazelcast.core.HazelcastInstanceAware; import com.hazelcast.internal.diagnostics.StoreLatencyPlugin; import com.hazelcast.internal.eviction.EvictionListener; import com.hazelcast.internal.eviction.EvictionPolicyEvaluatorProvider; import com.hazelcast.internal.eviction.EvictionChecker; import com.hazelcast.internal.eviction.impl.evaluator.EvictionPolicyEvaluator; import com.hazelcast.internal.eviction.impl.strategy.sampling.SamplingEvictionStrategy; import com.hazelcast.map.impl.MapEntries; import com.hazelcast.nio.Address; import com.hazelcast.nio.serialization.Data; import com.hazelcast.spi.EventRegistration; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.impl.NodeEngineImpl; import com.hazelcast.spi.impl.eventservice.InternalEventService; import com.hazelcast.util.Clock; import com.hazelcast.util.EmptyStatement; import com.hazelcast.util.ExceptionUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javax.cache.configuration.Factory; import javax.cache.expiry.Duration; import javax.cache.expiry.ExpiryPolicy; import javax.cache.integration.CacheLoader; import javax.cache.integration.CacheLoaderException; import javax.cache.integration.CacheWriter; import javax.cache.integration.CacheWriterException; import javax.cache.processor.EntryProcessor; import java.io.Closeable; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import static com.hazelcast.cache.impl.CacheEventContextUtil.createBaseEventContext; import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheCompleteEvent; import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheCreatedEvent; import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheExpiredEvent; import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheRemovedEvent; import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheUpdatedEvent; import static com.hazelcast.cache.impl.operation.MutableOperation.IGNORE_COMPLETION; import static com.hazelcast.cache.impl.record.CacheRecordFactory.isExpiredAt; import static com.hazelcast.internal.config.ConfigValidator.checkEvictionConfig; @SuppressWarnings({"checkstyle:methodcount", "checkstyle:classfanoutcomplexity"}) public abstract class AbstractCacheRecordStore<R extends CacheRecord, CRM extends SampleableCacheRecordMap<Data, R>> implements ICacheRecordStore, EvictionListener<Data, R> { public static final String SOURCE_NOT_AVAILABLE = "<NA>"; protected static final int DEFAULT_INITIAL_CAPACITY = 256; protected final String name; protected final int partitionId; protected final int partitionCount; protected final NodeEngine nodeEngine; protected final AbstractCacheService cacheService; protected final CacheConfig cacheConfig; protected final EvictionConfig evictionConfig; protected final Map<CacheEventType, Set<CacheEventData>> batchEvent = new HashMap<CacheEventType, Set<CacheEventData>>(); protected final EvictionChecker evictionChecker; protected final EvictionPolicyEvaluator<Data, R> evictionPolicyEvaluator; protected final SamplingEvictionStrategy<Data, R, CRM> evictionStrategy; protected final boolean wanReplicationEnabled; protected final boolean disablePerEntryInvalidationEvents; protected CRM records; protected CacheContext cacheContext; protected CacheStatisticsImpl statistics; protected CacheLoader cacheLoader; protected CacheWriter cacheWriter; protected boolean eventsEnabled = true; protected boolean eventsBatchingEnabled; protected ExpiryPolicy defaultExpiryPolicy; protected boolean primary; @SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:executablestatementcount"}) public AbstractCacheRecordStore(String name, int partitionId, NodeEngine nodeEngine, AbstractCacheService cacheService) { this.name = name; this.partitionId = partitionId; this.nodeEngine = nodeEngine; this.partitionCount = nodeEngine.getPartitionService().getPartitionCount(); this.cacheService = cacheService; this.cacheConfig = cacheService.getCacheConfig(name); if (cacheConfig == null) { throw new CacheNotExistsException("Cache " + name + " is already destroyed or not created yet, on " + nodeEngine.getLocalMember()); } evictionConfig = cacheConfig.getEvictionConfig(); if (evictionConfig == null) { throw new IllegalStateException("Eviction config cannot be null!"); } wanReplicationEnabled = cacheService.isWanReplicationEnabled(name); disablePerEntryInvalidationEvents = cacheConfig.isDisablePerEntryInvalidationEvents(); if (cacheConfig.isStatisticsEnabled()) { statistics = cacheService.createCacheStatIfAbsent(name); } if (cacheConfig.getCacheLoaderFactory() != null) { Factory<CacheLoader> cacheLoaderFactory = cacheConfig.getCacheLoaderFactory(); injectDependencies(cacheLoaderFactory); cacheLoader = cacheLoaderFactory.create(); injectDependencies(cacheLoader); } if (cacheConfig.getCacheWriterFactory() != null) { Factory<CacheWriter> cacheWriterFactory = cacheConfig.getCacheWriterFactory(); injectDependencies(cacheWriterFactory); cacheWriter = cacheWriterFactory.create(); injectDependencies(cacheWriter); } if (cacheConfig.getExpiryPolicyFactory() != null) { Factory<ExpiryPolicy> expiryPolicyFactory = cacheConfig.getExpiryPolicyFactory(); injectDependencies(expiryPolicyFactory); defaultExpiryPolicy = expiryPolicyFactory.create(); injectDependencies(defaultExpiryPolicy); } else { throw new IllegalStateException("Expiry policy factory cannot be null!"); } cacheContext = cacheService.getOrCreateCacheContext(name); records = createRecordCacheMap(); evictionChecker = createCacheEvictionChecker(evictionConfig.getSize(), evictionConfig.getMaximumSizePolicy()); evictionPolicyEvaluator = createEvictionPolicyEvaluator(evictionConfig); evictionStrategy = createEvictionStrategy(evictionConfig); injectDependencies(evictionPolicyEvaluator.getEvictionPolicyComparator()); registerResourceIfItIsClosable(cacheWriter); registerResourceIfItIsClosable(cacheLoader); registerResourceIfItIsClosable(defaultExpiryPolicy); init(); } public void instrument(NodeEngine nodeEngine) { StoreLatencyPlugin plugin = ((NodeEngineImpl) nodeEngine).getDiagnostics().getPlugin(StoreLatencyPlugin.class); if (plugin == null) { return; } if (cacheLoader != null) { cacheLoader = new LatencyTrackingCacheLoader(cacheLoader, plugin, cacheConfig.getName()); } if (cacheWriter != null) { cacheWriter = new LatencyTrackingCacheWriter(cacheWriter, plugin, cacheConfig.getName()); } } private boolean isPrimary() { final Address owner = nodeEngine.getPartitionService().getPartition(partitionId, false).getOwnerOrNull(); final Address thisAddress = nodeEngine.getThisAddress(); return owner != null && owner.equals(thisAddress); } private void injectDependencies(Object obj) { if (obj instanceof HazelcastInstanceAware) { ((HazelcastInstanceAware) obj).setHazelcastInstance(nodeEngine.getHazelcastInstance()); } } private void registerResourceIfItIsClosable(Object resource) { if (resource instanceof Closeable) { cacheService.addCacheResource(name, (Closeable) resource); } } @Override public void init() { primary = isPrimary(); records.setEntryCounting(primary); } protected boolean isReadThrough() { return cacheConfig.isReadThrough(); } protected boolean isWriteThrough() { return cacheConfig.isWriteThrough(); } protected boolean isStatisticsEnabled() { return statistics != null; } protected abstract CRM createRecordCacheMap(); protected abstract CacheEntryProcessorEntry createCacheEntryProcessorEntry(Data key, R record, long now, int completionId); protected abstract R createRecord(Object value, long creationTime, long expiryTime); protected abstract Data valueToData(Object value); protected abstract Object dataToValue(Data data); protected abstract Object recordToValue(R record); protected abstract Data recordToData(R record); protected abstract Data toHeapData(Object obj); /** * Creates an instance for checking if the maximum cache size has been reached. Supports only the * {@link MaxSizePolicy#ENTRY_COUNT} policy. Returns null if other {@code maxSizePolicy} is used. * * @param size the maximum number of entries * @param maxSizePolicy the way in which the size is interpreted, only the {@link MaxSizePolicy#ENTRY_COUNT} policy is * supported. * @return the instance which will check if the maximum number of entries has been reached or null if the * {@code maxSizePolicy} is not {@link MaxSizePolicy#ENTRY_COUNT} * @throws IllegalArgumentException if the {@code maxSizePolicy} is null */ protected EvictionChecker createCacheEvictionChecker(int size, MaxSizePolicy maxSizePolicy) { if (maxSizePolicy == null) { throw new IllegalArgumentException("Max-Size policy cannot be null"); } if (maxSizePolicy == MaxSizePolicy.ENTRY_COUNT) { return new EntryCountCacheEvictionChecker(size, records, partitionCount); } return null; } protected EvictionPolicyEvaluator<Data, R> createEvictionPolicyEvaluator(EvictionConfig evictionConfig) { checkEvictionConfig(evictionConfig, false); return EvictionPolicyEvaluatorProvider.getEvictionPolicyEvaluator(evictionConfig, nodeEngine.getConfigClassLoader()); } protected SamplingEvictionStrategy<Data, R, CRM> createEvictionStrategy(EvictionConfig cacheEvictionConfig) { return SamplingEvictionStrategy.INSTANCE; } protected boolean isEvictionEnabled() { return evictionStrategy != null && evictionPolicyEvaluator != null; } protected boolean isEventsEnabled() { return eventsEnabled && (cacheContext.getCacheEntryListenerCount() > 0 || wanReplicationEnabled); } protected boolean isInvalidationEnabled() { return primary && cacheContext.getInvalidationListenerCount() > 0; } @Override public int evictIfRequired() { int evictedCount = 0; if (isEvictionEnabled()) { evictedCount = evictionStrategy.evict(records, evictionPolicyEvaluator, evictionChecker, this); if (isStatisticsEnabled() && evictedCount > 0) { statistics.increaseCacheEvictions(evictedCount); } } return evictedCount; } protected Data toData(Object obj) { if (obj instanceof Data) { return (Data) obj; } else if (obj instanceof CacheRecord) { return recordToData((R) obj); } else { return valueToData(obj); } } protected Object toValue(Object obj) { if (obj instanceof Data) { return dataToValue((Data) obj); } else if (obj instanceof CacheRecord) { return recordToValue((R) obj); } else { return obj; } } protected Object toStorageValue(Object obj) { if (obj instanceof Data) { if (cacheConfig.getInMemoryFormat() == InMemoryFormat.OBJECT) { return dataToValue((Data) obj); } else { return obj; } } else if (obj instanceof CacheRecord) { return recordToValue((R) obj); } else { return obj; } } public Data toEventData(Object obj) { return isEventsEnabled() ? toHeapData(obj) : null; } private long getAdjustedExpireTime(Duration duration, long now) { return duration.getAdjustedTime(now); } protected ExpiryPolicy getExpiryPolicy(ExpiryPolicy expiryPolicy) { return expiryPolicy != null ? expiryPolicy : defaultExpiryPolicy; } protected boolean processExpiredEntry(Data key, R record, long now) { return processExpiredEntry(key, record, now, SOURCE_NOT_AVAILABLE); } protected boolean processExpiredEntry(Data key, R record, long now, String source) { return processExpiredEntry(key, record, now, source, null); } protected boolean processExpiredEntry(Data key, R record, long now, String source, String origin) { boolean isExpired = record != null && record.isExpiredAt(now); if (!isExpired) { return false; } if (isStatisticsEnabled()) { statistics.increaseCacheExpiries(1); } R removedRecord = doRemoveRecord(key, source); Data keyEventData = toEventData(key); Data recordEventData = toEventData(removedRecord); onProcessExpiredEntry(key, removedRecord, removedRecord.getExpirationTime(), now, source, origin); if (isEventsEnabled()) { publishEvent(createCacheExpiredEvent(keyEventData, recordEventData, CacheRecord.TIME_NOT_AVAILABLE, origin, IGNORE_COMPLETION)); } return true; } protected R processExpiredEntry(Data key, R record, long expiryTime, long now, String source) { return processExpiredEntry(key, record, expiryTime, now, source, null); } protected R processExpiredEntry(Data key, R record, long expiryTime, long now, String source, String origin) { if (!isExpiredAt(expiryTime, now)) { return record; } if (isStatisticsEnabled()) { statistics.increaseCacheExpiries(1); } R removedRecord = doRemoveRecord(key, source); Data keyEventData = toEventData(key); Data recordEventData = toEventData(removedRecord); onProcessExpiredEntry(key, removedRecord, expiryTime, now, source, origin); if (isEventsEnabled()) { publishEvent(createCacheExpiredEvent(keyEventData, recordEventData, CacheRecord.TIME_NOT_AVAILABLE, origin, IGNORE_COMPLETION)); } return null; } protected void onProcessExpiredEntry(Data key, R record, long expiryTime, long now, String source, String origin) { } public R accessRecord(Data key, R record, ExpiryPolicy expiryPolicy, long now) { onRecordAccess(key, record, getExpiryPolicy(expiryPolicy), now); return record; } @Override public void onEvict(Data key, R record, boolean wasExpired) { invalidateEntry(key); } protected void invalidateEntry(Data key, String source) { if (isInvalidationEnabled()) { if (key == null) { cacheService.sendInvalidationEvent(name, null, source); } else if (!disablePerEntryInvalidationEvents) { cacheService.sendInvalidationEvent(name, toHeapData(key), source); } } } protected void invalidateEntry(Data key) { invalidateEntry(key, SOURCE_NOT_AVAILABLE); } protected void invalidateAllEntries() { invalidateAllEntries(SOURCE_NOT_AVAILABLE); } protected void invalidateAllEntries(String source) { invalidateEntry(null, source); } protected void updateGetAndPutStat(boolean isPutSucceed, boolean getValue, boolean oldValueNull, long start) { if (isStatisticsEnabled()) { if (isPutSucceed) { statistics.increaseCachePuts(1); statistics.addPutTimeNanos(System.nanoTime() - start); } if (getValue) { if (oldValueNull) { statistics.increaseCacheMisses(1); } else { statistics.increaseCacheHits(1); } statistics.addGetTimeNanos(System.nanoTime() - start); } } } protected long updateAccessDuration(Data key, R record, ExpiryPolicy expiryPolicy, long now) { long expiryTime = CacheRecord.TIME_NOT_AVAILABLE; try { Duration expiryDuration = expiryPolicy.getExpiryForAccess(); if (expiryDuration != null) { expiryTime = getAdjustedExpireTime(expiryDuration, now); record.setExpirationTime(expiryTime); if (isEventsEnabled()) { CacheEventContext cacheEventContext = createBaseEventContext(CacheEventType.EXPIRATION_TIME_UPDATED, toEventData(key), toEventData(record.getValue()), expiryTime, null, IGNORE_COMPLETION); cacheEventContext.setAccessHit(record.getAccessHit()); publishEvent(cacheEventContext); } } } catch (Exception e) { EmptyStatement.ignore(e); } return expiryTime; } protected long onRecordAccess(Data key, R record, ExpiryPolicy expiryPolicy, long now) { record.setAccessTime(now); record.incrementAccessHit(); return updateAccessDuration(key, record, expiryPolicy, now); } protected void updateReplaceStat(boolean result, boolean isHit, long start) { if (isStatisticsEnabled()) { if (result) { statistics.increaseCachePuts(1); statistics.addPutTimeNanos(System.nanoTime() - start); } if (isHit) { statistics.increaseCacheHits(1); } else { statistics.increaseCacheMisses(1); } } } protected void publishEvent(CacheEventContext cacheEventContext) { if (isEventsEnabled()) { cacheEventContext.setCacheName(name); if (eventsBatchingEnabled) { CacheEventDataImpl cacheEventData = new CacheEventDataImpl(name, cacheEventContext.getEventType(), cacheEventContext.getDataKey(), cacheEventContext.getDataValue(), cacheEventContext.getDataOldValue(), cacheEventContext.isOldValueAvailable()); Set<CacheEventData> cacheEventDataSet = batchEvent.remove(cacheEventContext.getEventType()); if (cacheEventDataSet == null) { cacheEventDataSet = new HashSet<CacheEventData>(); batchEvent.put(cacheEventContext.getEventType(), cacheEventDataSet); } cacheEventDataSet.add(cacheEventData); } else { cacheService.publishEvent(cacheEventContext); } } } protected void publishBatchedEvents(String cacheName, CacheEventType cacheEventType, int orderKey) { if (isEventsEnabled()) { Set<CacheEventData> cacheEventDatas = batchEvent.remove(cacheEventType); if (cacheEventDatas != null) { cacheService.publishEvent(cacheName, new CacheEventSet(cacheEventType, cacheEventDatas), orderKey); } } } protected boolean compare(Object v1, Object v2) { if (v1 == null && v2 == null) { return true; } if (v1 == null || v2 == null) { return false; } return v1.equals(v2); } protected R createRecord(long expiryTime) { return createRecord(null, Clock.currentTimeMillis(), expiryTime); } protected R createRecord(Object value, long expiryTime) { return createRecord(value, Clock.currentTimeMillis(), expiryTime); } protected R createRecord(Data keyData, Object value, long expirationTime, int completionId) { R record = createRecord(value, expirationTime); if (isEventsEnabled()) { publishEvent(createCacheCreatedEvent(toEventData(keyData), toEventData(value), expirationTime, null, completionId)); } return record; } @SuppressWarnings("checkstyle:parameternumber") protected void onCreateRecordError(Data key, Object value, long expiryTime, long now, boolean disableWriteThrough, int completionId, String origin, R record, Throwable error) { } protected R createRecord(Data key, Object value, long expiryTime, long now, boolean disableWriteThrough, int completionId, String origin) { R record = createRecord(value, now, expiryTime); try { doPutRecord(key, record); } catch (Throwable error) { onCreateRecordError(key, value, expiryTime, now, disableWriteThrough, completionId, origin, record, error); throw ExceptionUtil.rethrow(error); } try { if (!disableWriteThrough) { writeThroughCache(key, value); } } catch (Throwable error) { // Writing to `CacheWriter` failed, so we should revert entry (remove added record). records.remove(key); // Disposing key/value/record should be handled inside `onCreateRecordWithExpiryError`. onCreateRecordError(key, value, expiryTime, now, disableWriteThrough, completionId, origin, record, error); throw ExceptionUtil.rethrow(error); } if (isEventsEnabled()) { publishEvent(createCacheCreatedEvent(toEventData(key), toEventData(value), expiryTime, origin, completionId)); } return record; } protected R createRecordWithExpiry(Data key, Object value, long expiryTime, long now, boolean disableWriteThrough, int completionId, String origin) { if (!isExpiredAt(expiryTime, now)) { return createRecord(key, value, expiryTime, now, disableWriteThrough, completionId, origin); } if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), CacheRecord.TIME_NOT_AVAILABLE, origin, completionId)); } return null; } protected R createRecordWithExpiry(Data key, Object value, long expiryTime, long now, boolean disableWriteThrough, int completionId) { return createRecordWithExpiry(key, value, expiryTime, now, disableWriteThrough, completionId, null); } protected R createRecordWithExpiry(Data key, Object value, ExpiryPolicy expiryPolicy, long now, boolean disableWriteThrough, int completionId) { return createRecordWithExpiry(key, value, expiryPolicy, now, disableWriteThrough, completionId, null); } protected R createRecordWithExpiry(Data key, Object value, ExpiryPolicy expiryPolicy, long now, boolean disableWriteThrough, int completionId, String origin) { expiryPolicy = getExpiryPolicy(expiryPolicy); Duration expiryDuration; try { expiryDuration = expiryPolicy.getExpiryForCreation(); } catch (Exception e) { expiryDuration = Duration.ETERNAL; } long expiryTime = getAdjustedExpireTime(expiryDuration, now); return createRecordWithExpiry(key, value, expiryTime, now, disableWriteThrough, completionId, origin); } protected void onUpdateRecord(Data key, R record, Object value, Data oldDataValue) { } protected void onUpdateRecordError(Data key, R record, Object value, Data newDataValue, Data oldDataValue, Throwable error) { } @SuppressWarnings("checkstyle:parameternumber") protected void updateRecord(Data key, R record, Object value, long expiryTime, long now, boolean disableWriteThrough, int completionId, String source, String origin) { Data dataOldValue = null; Data dataValue = null; Object recordValue = value; try { if (expiryTime != CacheRecord.TIME_NOT_AVAILABLE) { record.setExpirationTime(expiryTime); } if (isExpiredAt(expiryTime, now)) { // No need to update record value if it is expired if (!disableWriteThrough) { writeThroughCache(key, value); } } else { switch (cacheConfig.getInMemoryFormat()) { case BINARY: recordValue = toData(value); dataValue = (Data) recordValue; dataOldValue = toData(record); break; case OBJECT: if (value instanceof Data) { recordValue = dataToValue((Data) value); dataValue = (Data) value; } else { dataValue = valueToData(value); } dataOldValue = toData(record); break; case NATIVE: recordValue = toData(value); dataValue = (Data) recordValue; dataOldValue = toData(record); break; default: throw new IllegalArgumentException("Invalid storage format: " + cacheConfig.getInMemoryFormat()); } if (!disableWriteThrough) { writeThroughCache(key, value); // If writing to `CacheWriter` fails no need to revert. Because we have not update record value yet // with its new value but just converted new value to required storage type. } Data eventDataKey = toEventData(key); Data eventDataValue = toEventData(dataValue); Data eventDataOldValue = toEventData(dataOldValue); updateRecordValue(record, recordValue); onUpdateRecord(key, record, value, dataOldValue); invalidateEntry(key, source); if (isEventsEnabled()) { publishEvent(createCacheUpdatedEvent(eventDataKey, eventDataValue, eventDataOldValue, record.getCreationTime(), record.getExpirationTime(), record.getLastAccessTime(), record.getAccessHit(), origin, completionId)); } } } catch (Throwable error) { onUpdateRecordError(key, record, value, dataValue, dataOldValue, error); throw ExceptionUtil.rethrow(error); } } protected void updateRecordValue(R record, Object recordValue) { record.setValue(recordValue); } @SuppressWarnings("checkstyle:parameternumber") protected boolean updateRecordWithExpiry(Data key, Object value, R record, long expiryTime, long now, boolean disableWriteThrough, int completionId, String source, String origin) { updateRecord(key, record, value, expiryTime, now, disableWriteThrough, completionId, source, origin); return processExpiredEntry(key, record, expiryTime, now, source) != null; } protected boolean updateRecordWithExpiry(Data key, Object value, R record, long expiryTime, long now, boolean disableWriteThrough, int completionId) { return updateRecordWithExpiry(key, value, record, expiryTime, now, disableWriteThrough, completionId, SOURCE_NOT_AVAILABLE); } protected boolean updateRecordWithExpiry(Data key, Object value, R record, long expiryTime, long now, boolean disableWriteThrough, int completionId, String source) { return updateRecordWithExpiry(key, value, record, expiryTime, now, disableWriteThrough, completionId, source, null); } protected boolean updateRecordWithExpiry(Data key, Object value, R record, ExpiryPolicy expiryPolicy, long now, boolean disableWriteThrough, int completionId) { return updateRecordWithExpiry(key, value, record, expiryPolicy, now, disableWriteThrough, completionId, SOURCE_NOT_AVAILABLE); } protected boolean updateRecordWithExpiry(Data key, Object value, R record, ExpiryPolicy expiryPolicy, long now, boolean disableWriteThrough, int completionId, String source) { return updateRecordWithExpiry(key, value, record, expiryPolicy, now, disableWriteThrough, completionId, source, null); } @SuppressWarnings("checkstyle:parameternumber") protected boolean updateRecordWithExpiry(Data key, Object value, R record, ExpiryPolicy expiryPolicy, long now, boolean disableWriteThrough, int completionId, String source, String origin) { expiryPolicy = getExpiryPolicy(expiryPolicy); long expiryTime = CacheRecord.TIME_NOT_AVAILABLE; try { Duration expiryDuration = expiryPolicy.getExpiryForUpdate(); if (expiryDuration != null) { expiryTime = getAdjustedExpireTime(expiryDuration, now); } } catch (Exception e) { EmptyStatement.ignore(e); } return updateRecordWithExpiry(key, value, record, expiryTime, now, disableWriteThrough, completionId, source, origin); } protected void onDeleteRecord(Data key, R record, Data dataValue, boolean deleted) { } protected void onDeleteRecordError(Data key, R record, Data dataValue, boolean deleted, Throwable error) { } protected boolean deleteRecord(Data key, int completionId) { return deleteRecord(key, completionId, SOURCE_NOT_AVAILABLE); } protected boolean deleteRecord(Data key, int completionId, String source) { return deleteRecord(key, completionId, source, null); } protected boolean deleteRecord(Data key, int completionId, String source, String origin) { R record = doRemoveRecord(key, source); Data dataValue = null; try { switch (cacheConfig.getInMemoryFormat()) { case BINARY: case OBJECT: case NATIVE: dataValue = toData(record); break; default: throw new IllegalArgumentException("Invalid storage format: " + cacheConfig.getInMemoryFormat()); } Data eventDataKey = toEventData(key); Data eventDataValue = toEventData(dataValue); onDeleteRecord(key, record, dataValue, record != null); if (isEventsEnabled()) { publishEvent(createCacheRemovedEvent(eventDataKey, eventDataValue, CacheRecord.TIME_NOT_AVAILABLE, origin, completionId)); } return record != null; } catch (Throwable error) { onDeleteRecordError(key, record, dataValue, record != null, error); throw ExceptionUtil.rethrow(error); } } public R readThroughRecord(Data key, long now) { Object value = readThroughCache(key); if (value == null) { return null; } Duration expiryDuration; try { expiryDuration = defaultExpiryPolicy.getExpiryForCreation(); } catch (Exception e) { expiryDuration = Duration.ETERNAL; } long expiryTime = getAdjustedExpireTime(expiryDuration, now); if (isExpiredAt(expiryTime, now)) { return null; } return createRecord(key, value, expiryTime, IGNORE_COMPLETION); } public Object readThroughCache(Data key) throws CacheLoaderException { if (isReadThrough() && cacheLoader != null) { try { Object o = dataToValue(key); return cacheLoader.load(o); } catch (Exception e) { if (!(e instanceof CacheLoaderException)) { throw new CacheLoaderException("Exception in CacheLoader during load", e); } else { throw (CacheLoaderException) e; } } } return null; } public void writeThroughCache(Data key, Object value) throws CacheWriterException { if (isWriteThrough() && cacheWriter != null) { try { Object objKey = dataToValue(key); Object objValue = toValue(value); cacheWriter.write(new CacheEntry<Object, Object>(objKey, objValue)); } catch (Exception e) { if (!(e instanceof CacheWriterException)) { throw new CacheWriterException("Exception in CacheWriter during write", e); } else { throw (CacheWriterException) e; } } } } protected void deleteCacheEntry(Data key) { if (isWriteThrough() && cacheWriter != null) { try { Object objKey = dataToValue(key); cacheWriter.delete(objKey); } catch (Exception e) { if (!(e instanceof CacheWriterException)) { throw new CacheWriterException("Exception in CacheWriter during delete", e); } else { throw (CacheWriterException) e; } } } } @SuppressFBWarnings("WMI_WRONG_MAP_ITERATOR") protected void deleteAllCacheEntry(Set<Data> keys) { if (isWriteThrough() && cacheWriter != null && keys != null && !keys.isEmpty()) { Map<Object, Data> keysToDelete = new HashMap<Object, Data>(); for (Data key : keys) { Object localKeyObj = dataToValue(key); keysToDelete.put(localKeyObj, key); } Set<Object> keysObject = keysToDelete.keySet(); try { cacheWriter.deleteAll(keysObject); } catch (Exception e) { if (!(e instanceof CacheWriterException)) { throw new CacheWriterException("Exception in CacheWriter during deleteAll", e); } else { throw (CacheWriterException) e; } } finally { for (Object undeletedKey : keysObject) { Data undeletedKeyData = keysToDelete.get(undeletedKey); keys.remove(undeletedKeyData); } } } } protected Map<Data, Object> loadAllCacheEntry(Set<Data> keys) { if (cacheLoader != null) { Map<Object, Data> keysToLoad = new HashMap<Object, Data>(); for (Data key : keys) { Object localKeyObj = dataToValue(key); keysToLoad.put(localKeyObj, key); } Map<Object, Object> loaded; try { loaded = cacheLoader.loadAll(keysToLoad.keySet()); } catch (Throwable e) { if (!(e instanceof CacheLoaderException)) { throw new CacheLoaderException("Exception in CacheLoader during loadAll", e); } else { throw (CacheLoaderException) e; } } Map<Data, Object> result = new HashMap<Data, Object>(); for (Map.Entry<Object, Data> entry : keysToLoad.entrySet()) { Object keyObj = entry.getKey(); Object valueObject = loaded.get(keyObj); Data keyData = entry.getValue(); result.put(keyData, valueObject); } return result; } return null; } @Override public CacheRecord getRecord(Data key) { return records.get(key); } @Override public void putRecord(Data key, CacheRecord record) { evictIfRequired(); doPutRecord(key, (R) record); } public final R doPutRecord(Data key, R record) { return doPutRecord(key, record, SOURCE_NOT_AVAILABLE); } protected R doPutRecord(Data key, R record, String source) { R oldRecord = records.put(key, record); if (oldRecord != null) { invalidateEntry(key, source); } return oldRecord; } @Override public CacheRecord removeRecord(Data key) { return doRemoveRecord(key); } protected R doRemoveRecord(Data key) { return doRemoveRecord(key, SOURCE_NOT_AVAILABLE); } protected R doRemoveRecord(Data key, String source) { R removedRecord = records.remove(key); if (removedRecord != null) { invalidateEntry(key, source); } return removedRecord; } protected void onGet(Data key, ExpiryPolicy expiryPolicy, Object value, R record) { } protected void onGetError(Data key, ExpiryPolicy expiryPolicy, Object value, R record, Throwable error) { } @Override public Object get(Data key, ExpiryPolicy expiryPolicy) { expiryPolicy = getExpiryPolicy(expiryPolicy); long start = isStatisticsEnabled() ? System.nanoTime() : 0; long now = Clock.currentTimeMillis(); Object value = null; R record = records.get(key); boolean isExpired = processExpiredEntry(key, record, now); try { if (recordNotExistOrExpired(record, isExpired)) { if (isStatisticsEnabled()) { statistics.increaseCacheMisses(1); } value = readThroughCache(key); if (value == null) { if (isStatisticsEnabled()) { statistics.addGetTimeNanos(System.nanoTime() - start); } return null; } record = createRecordWithExpiry(key, value, expiryPolicy, now, true, IGNORE_COMPLETION); } else { value = recordToValue(record); onRecordAccess(key, record, expiryPolicy, now); if (isStatisticsEnabled()) { statistics.increaseCacheHits(1); } } if (isStatisticsEnabled()) { statistics.addGetTimeNanos(System.nanoTime() - start); } onGet(key, expiryPolicy, value, record); return value; } catch (Throwable error) { onGetError(key, expiryPolicy, value, record, error); throw ExceptionUtil.rethrow(error); } } @Override public boolean contains(Data key) { long now = Clock.currentTimeMillis(); R record = records.get(key); boolean isExpired = processExpiredEntry(key, record, now); return record != null && !isExpired; } @SuppressWarnings("checkstyle:parameternumber") protected void onPut(Data key, Object value, ExpiryPolicy expiryPolicy, String source, boolean getValue, boolean disableWriteThrough, R record, Object oldValue, boolean isExpired, boolean isNewPut, boolean isSaveSucceed) { } @SuppressWarnings("checkstyle:parameternumber") protected void onPutError(Data key, Object value, ExpiryPolicy expiryPolicy, String source, boolean getValue, boolean disableWriteThrough, R record, Object oldValue, boolean wouldBeNewPut, Throwable error) { } protected Object put(Data key, Object value, ExpiryPolicy expiryPolicy, String source, boolean getValue, boolean disableWriteThrough, int completionId) { expiryPolicy = getExpiryPolicy(expiryPolicy); long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; boolean isOnNewPut = false; boolean isSaveSucceed; Object oldValue = null; R record = records.get(key); boolean isExpired = processExpiredEntry(key, record, now, source); try { // Check that new entry is not already expired, in which case it should // not be added to the cache or listeners called or writers called. if (record == null || isExpired) { isOnNewPut = true; record = createRecordWithExpiry(key, value, expiryPolicy, now, disableWriteThrough, completionId); isSaveSucceed = record != null; } else { if (getValue) { oldValue = toValue(record); } isSaveSucceed = updateRecordWithExpiry(key, value, record, expiryPolicy, now, disableWriteThrough, completionId, source); } onPut(key, value, expiryPolicy, source, getValue, disableWriteThrough, record, oldValue, isExpired, isOnNewPut, isSaveSucceed); updateGetAndPutStat(isSaveSucceed, getValue, oldValue == null, start); if (getValue) { return oldValue; } else { return record; } } catch (Throwable error) { onPutError(key, value, expiryPolicy, source, getValue, disableWriteThrough, record, oldValue, isOnNewPut, error); throw ExceptionUtil.rethrow(error); } } protected Object put(Data key, Object value, ExpiryPolicy expiryPolicy, String source, boolean getValue, int completionId) { return put(key, value, expiryPolicy, source, getValue, false, completionId); } @Override public R put(Data key, Object value, ExpiryPolicy expiryPolicy, String source, int completionId) { return (R) put(key, value, expiryPolicy, source, false, false, completionId); } @Override public Object getAndPut(Data key, Object value, ExpiryPolicy expiryPolicy, String source, int completionId) { return put(key, value, expiryPolicy, source, true, false, completionId); } protected void onPutIfAbsent(Data key, Object value, ExpiryPolicy expiryPolicy, String source, boolean disableWriteThrough, R record, boolean isExpired, boolean isSaveSucceed) { } protected void onPutIfAbsentError(Data key, Object value, ExpiryPolicy expiryPolicy, String source, boolean disableWriteThrough, R record, Throwable error) { } protected boolean putIfAbsent(Data key, Object value, ExpiryPolicy expiryPolicy, String source, boolean disableWriteThrough, int completionId) { expiryPolicy = getExpiryPolicy(expiryPolicy); long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; boolean saved = false; R record = records.get(key); boolean isExpired = processExpiredEntry(key, record, now, source); try { if (record == null || isExpired) { saved = createRecordWithExpiry(key, value, expiryPolicy, now, disableWriteThrough, completionId) != null; } else { if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), completionId)); } } onPutIfAbsent(key, value, expiryPolicy, source, disableWriteThrough, record, isExpired, saved); if (saved && isStatisticsEnabled()) { statistics.increaseCachePuts(1); statistics.addPutTimeNanos(System.nanoTime() - start); } return saved; } catch (Throwable error) { onPutIfAbsentError(key, value, expiryPolicy, source, disableWriteThrough, record, error); throw ExceptionUtil.rethrow(error); } } @Override public boolean putIfAbsent(Data key, Object value, ExpiryPolicy expiryPolicy, String source, int completionId) { return putIfAbsent(key, value, expiryPolicy, source, false, completionId); } @SuppressWarnings("checkstyle:parameternumber") protected void onReplace(Data key, Object oldValue, Object newValue, ExpiryPolicy expiryPolicy, String source, boolean getValue, R record, boolean isExpired, boolean replaced) { } @SuppressWarnings("checkstyle:parameternumber") protected void onReplaceError(Data key, Object oldValue, Object newValue, ExpiryPolicy expiryPolicy, String source, boolean getValue, R record, boolean isExpired, boolean replaced, Throwable error) { } @Override public boolean replace(Data key, Object value, ExpiryPolicy expiryPolicy, String source, int completionId) { expiryPolicy = getExpiryPolicy(expiryPolicy); long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; boolean replaced = false; R record = records.get(key); boolean isExpired = record != null && record.isExpiredAt(now); try { if (recordNotExistOrExpired(record, isExpired)) { if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), completionId)); } } else { replaced = updateRecordWithExpiry(key, value, record, expiryPolicy, now, false, completionId, source); } onReplace(key, null, value, expiryPolicy, source, false, record, isExpired, replaced); if (isStatisticsEnabled()) { if (replaced) { statistics.increaseCachePuts(1); statistics.increaseCacheHits(1); statistics.addPutTimeNanos(System.nanoTime() - start); } else { statistics.increaseCacheMisses(1); } } return replaced; } catch (Throwable error) { onReplaceError(key, null, value, expiryPolicy, source, false, record, isExpired, replaced, error); throw ExceptionUtil.rethrow(error); } } @Override public boolean replace(Data key, Object oldValue, Object newValue, ExpiryPolicy expiryPolicy, String source, int completionId) { expiryPolicy = getExpiryPolicy(expiryPolicy); long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; boolean isHit = false; boolean replaced = false; R record = records.get(key); boolean isExpired = record != null && record.isExpiredAt(now); try { if (record != null && !isExpired) { isHit = true; Object currentValue = toStorageValue(record); if (compare(currentValue, toStorageValue(oldValue))) { replaced = updateRecordWithExpiry(key, newValue, record, expiryPolicy, now, false, completionId, source); } else { onRecordAccess(key, record, expiryPolicy, now); } } if (!replaced) { if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), completionId)); } } onReplace(key, oldValue, newValue, expiryPolicy, source, false, record, isExpired, replaced); updateReplaceStat(replaced, isHit, start); return replaced; } catch (Throwable error) { onReplaceError(key, oldValue, newValue, expiryPolicy, source, false, record, isExpired, replaced, error); throw ExceptionUtil.rethrow(error); } } @Override public Object getAndReplace(Data key, Object value, ExpiryPolicy expiryPolicy, String source, int completionId) { expiryPolicy = getExpiryPolicy(expiryPolicy); long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; boolean replaced = false; R record = records.get(key); boolean isExpired = record != null && record.isExpiredAt(now); try { Object obj = toValue(record); if (recordNotExistOrExpired(record, isExpired)) { obj = null; if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), completionId)); } } else { replaced = updateRecordWithExpiry(key, value, record, expiryPolicy, now, false, completionId, source); } onReplace(key, null, value, expiryPolicy, source, false, record, isExpired, replaced); if (isStatisticsEnabled()) { statistics.addGetTimeNanos(System.nanoTime() - start); if (obj != null) { statistics.increaseCacheHits(1); statistics.increaseCachePuts(1); statistics.addPutTimeNanos(System.nanoTime() - start); } else { statistics.increaseCacheMisses(1); } } return obj; } catch (Throwable error) { onReplaceError(key, null, value, expiryPolicy, source, false, record, isExpired, replaced, error); throw ExceptionUtil.rethrow(error); } } protected void onRemove(Data key, Object value, String source, boolean getValue, R record, boolean removed) { } protected void onRemoveError(Data key, Object value, String source, boolean getValue, R record, boolean removed, Throwable error) { } @Override public boolean remove(Data key, String source, int completionId) { return remove(key, source, completionId, null); } public boolean remove(Data key, String source, int completionId, String origin) { long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; deleteCacheEntry(key); R record = records.get(key); boolean removed = false; try { if (recordNotExistOrExpired(record, now)) { if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), CacheRecord.TIME_NOT_AVAILABLE, origin, completionId)); } } else { removed = deleteRecord(key, completionId, source, origin); } onRemove(key, null, source, false, record, removed); if (removed && isStatisticsEnabled()) { statistics.increaseCacheRemovals(1); statistics.addRemoveTimeNanos(System.nanoTime() - start); } return removed; } catch (Throwable error) { onRemoveError(key, null, source, false, record, removed, error); throw ExceptionUtil.rethrow(error); } } @Override public boolean remove(Data key, Object value, String source, int completionId) { return remove(key, value, source, completionId, null); } public boolean remove(Data key, Object value, String source, int completionId, String origin) { long now = Clock.currentTimeMillis(); long start = System.nanoTime(); R record = records.get(key); int hitCount = 0; boolean removed = false; try { if (recordNotExistOrExpired(record, now)) { if (isStatisticsEnabled()) { statistics.increaseCacheMisses(1); } } else { hitCount++; if (compare(toStorageValue(record), toStorageValue(value))) { deleteCacheEntry(key); removed = deleteRecord(key, completionId, source, origin); } else { long expiryTime = onRecordAccess(key, record, defaultExpiryPolicy, now); processExpiredEntry(key, record, expiryTime, now, source, origin); } } if (!removed) { if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), CacheRecord.TIME_NOT_AVAILABLE, origin, completionId)); } } onRemove(key, value, source, false, record, removed); updateRemoveStatistics(removed, hitCount, start); return removed; } catch (Throwable error) { onRemoveError(key, null, source, false, record, removed, error); throw ExceptionUtil.rethrow(error); } } private void updateRemoveStatistics(boolean result, int hitCount, long start) { if (result && isStatisticsEnabled()) { statistics.increaseCacheRemovals(1); statistics.addRemoveTimeNanos(System.nanoTime() - start); if (hitCount == 1) { statistics.increaseCacheHits(hitCount); } else { statistics.increaseCacheMisses(1); } } } @Override public Object getAndRemove(Data key, String source, int completionId) { return getAndRemove(key, source, completionId, null); } public Object getAndRemove(Data key, String source, int completionId, String origin) { long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; deleteCacheEntry(key); R record = records.get(key); Object obj; boolean removed = false; try { if (recordNotExistOrExpired(record, now)) { obj = null; if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(toEventData(key), CacheRecord.TIME_NOT_AVAILABLE, origin, completionId)); } } else { obj = toValue(record); removed = deleteRecord(key, completionId, source, origin); } onRemove(key, null, source, false, record, removed); if (isStatisticsEnabled()) { statistics.addGetTimeNanos(System.nanoTime() - start); if (obj != null) { statistics.increaseCacheHits(1); statistics.increaseCacheRemovals(1); statistics.addRemoveTimeNanos(System.nanoTime() - start); } else { statistics.increaseCacheMisses(1); } } return obj; } catch (Throwable error) { onRemoveError(key, null, source, false, record, removed, error); throw ExceptionUtil.rethrow(error); } } @Override public MapEntries getAll(Set<Data> keySet, ExpiryPolicy expiryPolicy) { expiryPolicy = getExpiryPolicy(expiryPolicy); MapEntries result = new MapEntries(keySet.size()); for (Data key : keySet) { Object value = get(key, expiryPolicy); if (value != null) { result.add(key, toHeapData(value)); } } return result; } @Override public void removeAll(Set<Data> keys, int completionId) { long now = Clock.currentTimeMillis(); Set<Data> localKeys = new HashSet<Data>(keys.isEmpty() ? records.keySet() : keys); try { deleteAllCacheEntry(localKeys); } finally { Set<Data> keysToClean = new HashSet<Data>(keys.isEmpty() ? records.keySet() : keys); for (Data key : keysToClean) { eventsBatchingEnabled = true; R record = records.get(key); if (localKeys.contains(key) && record != null) { boolean isExpired = processExpiredEntry(key, record, now); if (!isExpired) { deleteRecord(key, IGNORE_COMPLETION); if (isStatisticsEnabled()) { statistics.increaseCacheRemovals(1); } } keys.add(key); } else { keys.remove(key); } eventsBatchingEnabled = false; } int orderKey = keys.hashCode(); publishBatchedEvents(name, CacheEventType.REMOVED, orderKey); if (isEventsEnabled()) { publishEvent(createCacheCompleteEvent(completionId)); } } } @Override public Set<Data> loadAll(Set<Data> keys, boolean replaceExistingValues) { Set<Data> keysLoaded = new HashSet<Data>(); Map<Data, Object> loaded = loadAllCacheEntry(keys); if (loaded == null || loaded.isEmpty()) { return keysLoaded; } if (replaceExistingValues) { for (Map.Entry<Data, Object> entry : loaded.entrySet()) { Data key = entry.getKey(); Object value = entry.getValue(); if (value != null) { put(key, value, null, SOURCE_NOT_AVAILABLE, false, true, IGNORE_COMPLETION); keysLoaded.add(key); } } } else { for (Map.Entry<Data, Object> entry : loaded.entrySet()) { Data key = entry.getKey(); Object value = entry.getValue(); if (value != null) { boolean hasPut = putIfAbsent(key, value, null, SOURCE_NOT_AVAILABLE, true, IGNORE_COMPLETION); if (hasPut) { keysLoaded.add(key); } } } } return keysLoaded; } @Override public CacheKeyIterationResult fetchKeys(int tableIndex, int size) { return records.fetchKeys(tableIndex, size); } @Override public CacheEntryIterationResult fetchEntries(int tableIndex, int size) { return records.fetchEntries(tableIndex, size); } @Override public Object invoke(Data key, EntryProcessor entryProcessor, Object[] arguments, int completionId) { long now = Clock.currentTimeMillis(); long start = isStatisticsEnabled() ? System.nanoTime() : 0; R record = records.get(key); boolean isExpired = processExpiredEntry(key, record, now); if (isExpired) { record = null; } if (isStatisticsEnabled()) { if (recordNotExistOrExpired(record, isExpired)) { statistics.increaseCacheMisses(1); } else { statistics.increaseCacheHits(1); } statistics.addGetTimeNanos(System.nanoTime() - start); } CacheEntryProcessorEntry entry = createCacheEntryProcessorEntry(key, record, now, completionId); injectDependencies(entryProcessor); Object result = entryProcessor.process(entry, arguments); entry.applyChanges(); return result; } private boolean recordNotExistOrExpired(R record, boolean isExpired) { return record == null || isExpired; } private boolean recordNotExistOrExpired(R record, long now) { return record == null || record.isExpiredAt(now); } @Override public int size() { return records.size(); } @Override public CacheStatisticsImpl getCacheStats() { return statistics; } @Override public CacheConfig getConfig() { return cacheConfig; } @Override public String getName() { return name; } @Override public Map<Data, CacheRecord> getReadOnlyRecords() { return (Map<Data, CacheRecord>) Collections.unmodifiableMap(records); } @Override public void clear() { records.clear(); } @Override public void close(boolean onShutdown) { clear(); closeListeners(); } @Override public void destroy() { clear(); closeListeners(); onDestroy(); } protected void onDestroy() { } protected void closeListeners() { InternalEventService eventService = (InternalEventService) cacheService.getNodeEngine().getEventService(); Collection<EventRegistration> candidates = eventService.getRegistrations(ICacheService.SERVICE_NAME, name); for (EventRegistration eventRegistration : candidates) { eventService.close(eventRegistration); } } @Override public boolean isWanReplicationEnabled() { return wanReplicationEnabled; } }