All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.map.DefaultRecordStore Maven / Gradle / Ivy

There is a newer version: 5.0-BETA-1
Show newest version
/*
 * Copyright (c) 2008-2013, 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;

import com.hazelcast.concurrent.lock.LockService;
import com.hazelcast.concurrent.lock.LockStore;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.core.EntryView;
import com.hazelcast.logging.ILogger;
import com.hazelcast.map.merge.MapMergePolicy;
import com.hazelcast.map.operation.PutAllOperation;
import com.hazelcast.map.record.Record;
import com.hazelcast.map.record.RecordFactory;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.nio.serialization.SerializationService;
import com.hazelcast.query.impl.IndexService;
import com.hazelcast.query.impl.QueryEntry;
import com.hazelcast.query.impl.QueryableEntry;
import com.hazelcast.spi.DefaultObjectNamespace;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.OperationAccessor;
import com.hazelcast.spi.ResponseHandler;
import com.hazelcast.spi.exception.RetryableHazelcastException;
import com.hazelcast.util.ExceptionUtil;
import com.hazelcast.util.scheduler.EntryTaskScheduler;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author enesakar 1/17/13
 */
public class DefaultRecordStore implements RecordStore {
    private static final long DEFAULT_TTL = -1;
    private final String name;
    private final int partitionId;
    private final ConcurrentMap records = new ConcurrentHashMap(1000);
    private final Set toBeRemovedKeys = new HashSet();
    private final MapContainer mapContainer;
    private final MapService mapService;
    private final LockStore lockStore;
    private final RecordFactory recordFactory;
    private final ILogger logger;

    final SizeEstimator sizeEstimator;
    final AtomicBoolean loaded = new AtomicBoolean(false);

    public DefaultRecordStore(String name, MapService mapService, int partitionId) {
        this.name = name;
        this.partitionId = partitionId;
        this.mapService = mapService;
        this.mapContainer = mapService.getMapContainer(name);
        this.logger = mapService.getNodeEngine().getLogger(this.getName());
        recordFactory = mapContainer.getRecordFactory();
        NodeEngine nodeEngine = mapService.getNodeEngine();
        final LockService lockService = nodeEngine.getSharedService(LockService.SERVICE_NAME);
        this.lockStore = lockService == null ? null :
                lockService.createLockStore(partitionId, new DefaultObjectNamespace(MapService.SERVICE_NAME, name));
        this.sizeEstimator = SizeEstimators.createMapSizeEstimator();
        final int mapLoadChunkSize = nodeEngine.getGroupProperties().MAP_LOAD_CHUNK_SIZE.getInteger();
        final Queue chunks = new LinkedList();
        if (nodeEngine.getThisAddress().equals(nodeEngine.getPartitionService().getPartitionOwner(partitionId))) {
            if (mapContainer.getStore() != null && !loaded.get()) {
                Map loadedKeys = mapContainer.getInitialKeys();
                if (loadedKeys != null && !loadedKeys.isEmpty()) {
                    Map partitionKeys = new HashMap();
                    Iterator> iterator = loadedKeys.entrySet().iterator();
                    while (iterator.hasNext()) {
                        final Map.Entry entry = iterator.next();
                        final Data data = entry.getKey();
                        if (partitionId == nodeEngine.getPartitionService().getPartitionId(data)) {
                            partitionKeys.put(data, entry.getValue());
                            //split into chunks
                            if (partitionKeys.size() >= mapLoadChunkSize) {
                                chunks.add(partitionKeys);
                                partitionKeys = new HashMap();
                            }
                            iterator.remove();
                        }
                    }
                    if (!partitionKeys.isEmpty()) {
                        chunks.add(partitionKeys);
                    }
                    if (!chunks.isEmpty()) {
                        try {
                            Map chunkedKeys;
                            final AtomicInteger checkIfMapLoaded = new AtomicInteger(chunks.size());
                            while ((chunkedKeys = chunks.poll()) != null) {
                                nodeEngine.getExecutionService().submit("hz:map-load", new MapLoadAllTask(chunkedKeys, checkIfMapLoaded));
                            }
                        } catch (Throwable t) {
                            throw ExceptionUtil.rethrow(t);
                        }
                    } else {
                        loaded.set(true);
                    }
                } else {
                    loaded.set(true);
                }
            }
        } else {
            loaded.set(true);
        }
    }

    public boolean isLoaded() {
        return loaded.get();
    }

    public void setLoaded(boolean isLoaded) {
        loaded.set(isLoaded);
    }

    public void checkIfLoaded() {
        if (mapContainer.getStore() != null && !loaded.get()) {
            throw ExceptionUtil.rethrow(new RetryableHazelcastException("Map is not ready!!!"));
        }
    }

    public String getName() {
        return name;
    }

    public void flush() {
        checkIfLoaded();
        Set keys = new HashSet();
        for (Record record : records.values()) {
            keys.add(record.getKey());
        }
        EntryTaskScheduler writeScheduler = mapContainer.getMapStoreWriteScheduler();
        if (writeScheduler != null) {
            Set processedKeys = writeScheduler.flush(keys);
            for (Data key : processedKeys) {
                records.get(key).onStore();
            }
        }
        EntryTaskScheduler deleteScheduler = mapContainer.getMapStoreDeleteScheduler();
        if (deleteScheduler != null) {
            deleteScheduler.flush(toBeRemovedKeys);
            toBeRemovedKeys.clear();
        }
    }

    private void flush(Data key) {
        checkIfLoaded();
        EntryTaskScheduler writeScheduler = mapContainer.getMapStoreWriteScheduler();
        Set keys = new HashSet(1);
        keys.add(key);
        if (writeScheduler != null) {
            Set processedKeys = writeScheduler.flush(keys);
            for (Data pkey : processedKeys) {
                records.get(pkey).onStore();
            }
        }
        EntryTaskScheduler deleteScheduler = mapContainer.getMapStoreDeleteScheduler();
        if (deleteScheduler != null) {
            if (toBeRemovedKeys.contains(key)) {
                deleteScheduler.flush(keys);
                toBeRemovedKeys.remove(key);
            }
        }
    }

    public MapContainer getMapContainer() {
        return mapContainer;
    }

    public Record getRecord(Data key) {
        return records.get(key);
    }

    public void putRecord(Data key, Record record) {
        records.put(key, record);
    }

    public Record putBackup(Data key, Object value) {
        return putBackup(key, value, DEFAULT_TTL, true);
    }

    public Record putBackup(Data key, Object value, long ttl, boolean shouldSchedule) {
        Record record = records.get(key);
        if (record == null) {
            record = mapService.createRecord(name, key, value, ttl, shouldSchedule);
            records.put(key, record);
            updateSizeEstimator(calculateRecordSize(record));
        } else {
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            updateSizeEstimator(calculateRecordSize(record));
        }
        return record;
    }

    public void deleteRecord(Data key) {
        Record record = records.remove(key);
        if (record != null) {
            record.invalidate();
        }
    }

    public Map getReadonlyRecordMap() {
        return Collections.unmodifiableMap(records);
    }

    public void clear() {
        final LockService lockService = mapService.getNodeEngine().getSharedService(LockService.SERVICE_NAME);
        if (lockService != null) {
            lockService.clearLockStore(partitionId, new DefaultObjectNamespace(MapService.SERVICE_NAME, name));
        }
        final IndexService indexService = mapContainer.getIndexService();
        if (indexService.hasIndex()) {
            for (Data key : records.keySet()) {
                indexService.removeEntryIndex(key);
            }
        }
        cancelAssociatedSchedulers(records.keySet());
        clearRecordsMap(Collections.emptyMap());
        resetSizeEstimator();

    }

    private void clearRecordsMap(Map excludeRecords) {
        InMemoryFormat inMemoryFormat = recordFactory.getStorageFormat();
        switch (inMemoryFormat) {
            case BINARY:
            case OBJECT:
                records.clear();
                if (excludeRecords != null && !excludeRecords.isEmpty()) {
                    records.putAll(excludeRecords);
                }
                return;

            case OFFHEAP:
                Iterator iter = records.values().iterator();
                while (iter.hasNext()) {
                    Record record = iter.next();
                    if (excludeRecords == null || !excludeRecords.containsKey(record.getKey())) {
                        record.invalidate();
                        iter.remove();
                    }
                }
                return;

            default:
                throw new IllegalArgumentException("Unknown storage format: " + inMemoryFormat);
        }
    }

    public int size() {
        // do not add checkIfLoaded(), size() is also used internally
        return records.size();
    }

    public boolean isEmpty() {
        checkIfLoaded();
        return records.isEmpty();
    }

    public boolean containsValue(Object value) {
        checkIfLoaded();
        for (Record record : records.values()) {
            if (mapService.compare(name, value, record.getValue()))
                return true;
        }
        return false;
    }

    public boolean lock(Data key, String caller, int threadId, long ttl) {
        checkIfLoaded();
        return lockStore != null && lockStore.lock(key, caller, threadId, ttl);
    }

    public boolean txnLock(Data key, String caller, int threadId, long ttl) {
        checkIfLoaded();
        return lockStore != null && lockStore.txnLock(key, caller, threadId, ttl);
    }

    public boolean extendLock(Data key, String caller, int threadId, long ttl) {
        checkIfLoaded();
        return lockStore != null && lockStore.extendLeaseTime(key, caller, threadId, ttl);
    }

    public boolean unlock(Data key, String caller, int threadId) {
        checkIfLoaded();
        return lockStore != null && lockStore.unlock(key, caller, threadId);
    }

    public boolean forceUnlock(Data dataKey) {
        return lockStore != null && lockStore.forceUnlock(dataKey);
    }

    @Override
    public long getHeapCost() {
        return getSizeEstimator().getSize();
    }

    public boolean isLocked(Data dataKey) {
        return lockStore != null && lockStore.isLocked(dataKey);
    }

    public boolean isLockedBy(Data key, String caller, int threadId) {
        return lockStore != null && lockStore.isLockedBy(key, caller, threadId);
    }

    public boolean canAcquireLock(Data key, String caller, int threadId) {
        return lockStore == null || lockStore.canAcquireLock(key, caller, threadId);
    }

    public String getLockOwnerInfo(Data key) {
        return lockStore != null ? lockStore.getOwnerInfo(key) : null;
    }

    public Set> entrySetObject() {
        checkIfLoaded();
        Map temp = new HashMap(records.size());
        for (Data key : records.keySet()) {
            temp.put(key, mapService.toObject(records.get(key).getValue()));
        }
        return temp.entrySet();
    }

    public Set> entrySetData() {
        checkIfLoaded();
        Map temp = new HashMap(records.size());
        for (Data key : records.keySet()) {
            temp.put(key, mapService.toData(records.get(key).getValue()));
        }
        return temp.entrySet();
    }

    public Map.Entry getMapEntry(Data dataKey) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        if (record == null) {
            record = getRecordInternal(dataKey, true);
        } else {
            accessRecord(record);
        }
        final Object data = record != null ? record.getValue() : null;
        return new AbstractMap.SimpleImmutableEntry(dataKey, data);
    }

    public Map.Entry getMapEntryForBackup(Data dataKey) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        if (record == null) {
            record = getRecordInternal(dataKey, false);
        } else {
            accessRecord(record);
        }
        final Object data = record != null ? record.getValue() : null;
        return new AbstractMap.SimpleImmutableEntry(dataKey, data);
    }

    private Record getRecordInternal(Data dataKey, boolean enableIndex) {
        Record record = null;
        if (mapContainer.getStore() != null) {
            final Object value = mapContainer.getStore().load(mapService.toObject(dataKey));
            if (value != null) {
                record = mapService.createRecord(name, dataKey, value, DEFAULT_TTL);
                records.put(dataKey, record);
                if (enableIndex) {
                    saveIndex(record);
                }
                updateSizeEstimator(calculateRecordSize(record));
            }
        }
        return record;
    }

    public Set keySet() {
        checkIfLoaded();
        Set keySet = new HashSet(records.size());
        for (Data data : records.keySet()) {
            keySet.add(data);
        }
        return keySet;
    }

    public Collection valuesObject() {
        checkIfLoaded();
        Collection values = new ArrayList(records.size());
        for (Record record : records.values()) {
            values.add(mapService.toObject(record.getValue()));
        }
        return values;
    }

    public Collection valuesData() {
        checkIfLoaded();
        Collection values = new ArrayList(records.size());
        for (Record record : records.values()) {
            values.add(mapService.toData(record.getValue()));
        }
        return values;
    }

    public void removeAll() {
        checkIfLoaded();
        resetSizeEstimator();
        final Collection lockedKeys = lockStore != null ? lockStore.getLockedKeys() : Collections.emptySet();
        final Map lockedRecords = new HashMap(lockedKeys.size());
        // Locked records should not be removed!
        for (Data key : lockedKeys) {
            Record record = records.get(key);
            if (record != null) {
                lockedRecords.put(key, record);
                updateSizeEstimator(calculateRecordSize(record));
            }
        }
        Set keysToDelete = records.keySet();
        keysToDelete.removeAll(lockedRecords.keySet());

        final MapStoreWrapper store = mapContainer.getStore();
        Collection keysObject = new ArrayList();
        for (Data key : keysToDelete) {
            // todo ea have a clear(Keys) method for optimizations
            removeIndex(key);
            keysObject.add(mapService.toObject(key));
        }

        if (store != null) {
            store.deleteAll(keysObject);
            toBeRemovedKeys.removeAll(keysToDelete);
        }

        clearRecordsMap(lockedRecords);
        cancelAllSchedulers();
    }

    public void reset() {
        checkIfLoaded();
        clearRecordsMap(Collections.emptyMap());
        resetSizeEstimator();
        cancelAllSchedulers();
    }

    public Object remove(Data dataKey) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object oldValue = null;
        if (record == null) {
            if (mapContainer.getStore() != null) {
                oldValue = mapContainer.getStore().load(mapService.toObject(dataKey));
                if (oldValue != null) {
                    removeIndex(dataKey);
                    mapStoreDelete(null, dataKey);
                }
            }
        } else {
            oldValue = record.getValue();
            oldValue = mapService.interceptRemove(name, oldValue);
            if (oldValue != null) {
                removeIndex(dataKey);
                mapStoreDelete(record, dataKey);
            }
            // reduce size
            updateSizeEstimator(-calculateRecordSize(record));
            deleteRecord(dataKey);
            cancelAssociatedSchedulers(dataKey);
        }
        return oldValue;
    }

    private void removeIndex(Data key) {
        final IndexService indexService = mapContainer.getIndexService();
        if (indexService.hasIndex()) {
            indexService.removeEntryIndex(key);
        }
    }

    public Object evict(Data dataKey) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object oldValue = null;
        if (record != null) {
            flush(dataKey);
            mapService.interceptRemove(name, record.getValue());
            oldValue = record.getValue();
            // reduce size
            updateSizeEstimator(-calculateRecordSize(record));
            deleteRecord(dataKey);
            removeIndex(dataKey);
            cancelAssociatedSchedulers(dataKey);
        }
        return oldValue;
    }

    public boolean remove(Data dataKey, Object testValue) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object oldValue = null;
        boolean removed = false;
        if (record == null) {
            if (mapContainer.getStore() != null) {
                oldValue = mapContainer.getStore().load(mapService.toObject(dataKey));
            }
            if (oldValue == null)
                return false;
        } else {
            oldValue = record.getValue();
        }
        if (mapService.compare(name, testValue, oldValue)) {
            mapService.interceptRemove(name, oldValue);
            removeIndex(dataKey);
            mapStoreDelete(record, dataKey);
            // reduce size
            updateSizeEstimator(-calculateRecordSize(record));
            deleteRecord(dataKey);
            cancelAssociatedSchedulers(dataKey);
            removed = true;
        }
        return removed;
    }

    public Object get(Data dataKey) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object value = null;
        if (record == null) {
            if (mapContainer.getStore() != null) {
                value = mapContainer.getStore().load(mapService.toObject(dataKey));
                if (value != null) {
                    record = mapService.createRecord(name, dataKey, value, DEFAULT_TTL);
                    records.put(dataKey, record);
                    saveIndex(record);
                    updateSizeEstimator(calculateRecordSize(record));
                }
            }

        } else {
            accessRecord(record);
            value = record.getValue();
        }
        value = mapService.interceptGet(name, value);

        return value;
    }

    public MapEntrySet getAll(Set keySet) {
        checkIfLoaded();
        final MapEntrySet mapEntrySet = new MapEntrySet();
        Map keyMapForLoader = null;
        if (mapContainer.getStore() != null) {
            keyMapForLoader = new HashMap();
        }
        for (Data dataKey : keySet) {
            Record record = records.get(dataKey);
            if (record == null) {
                if (mapContainer.getStore() != null) {
                    keyMapForLoader.put(mapService.toObject(dataKey), dataKey);
                }
            } else {
                accessRecord(record);
                Object value = record.getValue();
                value = mapService.interceptGet(name, value);
                if (value != null) {
                    mapEntrySet.add(new AbstractMap.SimpleImmutableEntry(dataKey, mapService.toData(value)));
                }
            }
        }
        if (mapContainer.getStore() == null || keyMapForLoader.size() == 0) {
            return mapEntrySet;
        }
        final Map loadedKeys = mapContainer.getStore().loadAll(keyMapForLoader.keySet());
        for (Map.Entry entry : loadedKeys.entrySet()) {
            final Object objectKey = entry.getKey();
            Object value = entry.getValue();
            Data dataKey = keyMapForLoader.get(objectKey);
            if (value != null) {
                Record record = mapService.createRecord(name, dataKey, value, DEFAULT_TTL);
                records.put(dataKey, record);
                saveIndex(record);
                updateSizeEstimator(calculateRecordSize(record));
            }
            value = mapService.interceptGet(name, value);
            if (value != null) {
                mapEntrySet.add(new AbstractMap.SimpleImmutableEntry(dataKey, mapService.toData(value)));
            }
        }
        return mapEntrySet;
    }

    public boolean containsKey(Data dataKey) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        if (record == null) {
            if (mapContainer.getStore() != null) {
                Object value = mapContainer.getStore().load(mapService.toObject(dataKey));
                if (value != null) {
                    record = mapService.createRecord(name, dataKey, value, DEFAULT_TTL);
                    records.put(dataKey, record);
                    updateSizeEstimator(calculateRecordSize(record));
                }
            }
        }

        boolean contains = record != null;
        if (contains) {
            accessRecord(record);
        }
        return contains;
    }

    public void put(Map.Entry entry) {
        checkIfLoaded();
        Data dataKey = entry.getKey();
        Object value = entry.getValue();
        Record record = records.get(dataKey);
        if (record == null) {
            value = mapService.interceptPut(name, null, value);
            record = mapService.createRecord(name, dataKey, value, DEFAULT_TTL);
            mapStoreWrite(record, dataKey, value);
            records.put(dataKey, record);
            // increase size.
            updateSizeEstimator(calculateRecordSize(record));
            saveIndex(record);
        } else {
            final Object oldValue = record.getValue();
            value = mapService.interceptPut(name, oldValue, value);
            mapStoreWrite(record, dataKey, value);
            // if key exists before, first reduce size
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            // then increase size
            updateSizeEstimator(calculateRecordSize(record));
            saveIndex(record);
        }

    }

    public Object put(Data dataKey, Object value, long ttl) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object oldValue = null;
        if (record == null) {
            if (mapContainer.getStore() != null) {
                oldValue = mapContainer.getStore().load(mapService.toObject(dataKey));
            }
            value = mapService.interceptPut(name, null, value);
            record = mapService.createRecord(name, dataKey, value, ttl);
            mapStoreWrite(record, dataKey, value);
            records.put(dataKey, record);
            updateSizeEstimator(calculateRecordSize(record));
            saveIndex(record);
        } else {
            oldValue = record.getValue();
            value = mapService.interceptPut(name, oldValue, value);
            mapStoreWrite(record, dataKey, value);
            // if key exists before, first reduce size
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            // then increase size.
            updateSizeEstimator(calculateRecordSize(record));
            updateTtl(record, ttl);
            saveIndex(record);
        }
        return oldValue;
    }

    public boolean set(Data dataKey, Object value, long ttl) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        boolean newRecord = false;
        if (record == null) {
            value = mapService.interceptPut(name, null, value);
            record = mapService.createRecord(name, dataKey, value, ttl);
            mapStoreWrite(record, dataKey, value);
            records.put(dataKey, record);
            updateSizeEstimator(calculateRecordSize(record));
            newRecord = true;
        } else {
            value = mapService.interceptPut(name, record.getValue(), value);
            mapStoreWrite(record, dataKey, value);
            // if key exists before, first reduce size
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            // then increase size.
            updateSizeEstimator(calculateRecordSize(record));
            updateTtl(record, ttl);
        }
        saveIndex(record);

        return newRecord;
    }

    public boolean merge(Data dataKey, EntryView mergingEntry, MapMergePolicy mergePolicy) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object newValue = null;
        if (record == null) {
            newValue = mergingEntry.getValue();
            record = mapService.createRecord(name, dataKey, newValue, DEFAULT_TTL);
            mapStoreWrite(record, dataKey, newValue);
            records.put(dataKey, record);
            updateSizeEstimator(calculateRecordSize(record));
        } else {
            Object oldValue = record.getValue();
            EntryView existingEntry = new SimpleEntryView(mapService.toObject(record.getKey()), mapService.toObject(record.getValue()),
                    record.getStatistics(), record.getCost(), record.getVersion());
            newValue = mergePolicy.merge(name, mergingEntry, existingEntry);
            if (newValue == null) { // existing entry will be removed
                deleteRecord(dataKey);
                removeIndex(dataKey);
                mapStoreDelete(record, dataKey);
                // reduce size.
                updateSizeEstimator(-calculateRecordSize(record));
                return true;
            }
            // same with the existing entry so no need to mapstore etc operations.
            if (mapService.compare(name, newValue, oldValue)) {
                return true;
            }
            mapStoreWrite(record, dataKey, newValue);
            updateSizeEstimator(-calculateRecordSize(record));
            recordFactory.setValue(record, newValue);
            updateSizeEstimator(calculateRecordSize(record));
        }
        saveIndex(record);
        return newValue != null;
    }

    public Object replace(Data dataKey, Object value) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object oldValue = null;
        if (record != null && record.getValue() != null) {
            oldValue = record.getValue();
            value = mapService.interceptPut(name, oldValue, value);
            mapStoreWrite(record, dataKey, value);
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            updateSizeEstimator(calculateRecordSize(record));
        } else {
            return null;
        }
        saveIndex(record);

        return oldValue;
    }

    public boolean replace(Data dataKey, Object testValue, Object newValue) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        if (record == null)
            return false;
        if (mapService.compare(name, record.getValue(), testValue)) {
            newValue = mapService.interceptPut(name, record.getValue(), newValue);
            mapStoreWrite(record, dataKey, newValue);
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, newValue);
            updateSizeEstimator(calculateRecordSize(record));
        } else {
            return false;
        }
        saveIndex(record);

        return true;
    }

    public void putTransient(Data dataKey, Object value, long ttl) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        if (record == null) {
            value = mapService.interceptPut(name, null, value);
            record = mapService.createRecord(name, dataKey, value, ttl);
            records.put(dataKey, record);
            updateSizeEstimator(calculateRecordSize(record));
        } else {
            value = mapService.interceptPut(name, record.getValue(), value);
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            updateSizeEstimator(calculateRecordSize(record));
            updateTtl(record, ttl);
        }
        saveIndex(record);
    }

    public void putFromLoad(Data dataKey, Object value, long ttl) {
        Record record = records.get(dataKey);
        if (record == null) {
            value = mapService.interceptPut(name, null, value);
            record = mapService.createRecord(name, dataKey, value, ttl);
            records.put(dataKey, record);
            updateSizeEstimator(calculateRecordSize(record));
        } else {
            value = mapService.interceptPut(name, record.getValue(), value);
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            updateSizeEstimator(calculateRecordSize(record));
            updateTtl(record, ttl);
        }
        saveIndex(record);
    }

    public boolean tryPut(Data dataKey, Object value, long ttl) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        if (record == null) {
            value = mapService.interceptPut(name, null, value);
            record = mapService.createRecord(name, dataKey, value, ttl);
            mapStoreWrite(record, dataKey, value);
            records.put(dataKey, record);
            updateSizeEstimator(calculateRecordSize(record));
        } else {
            value = mapService.interceptPut(name, record.getValue(), value);
            mapStoreWrite(record, dataKey, value);
            updateSizeEstimator(-calculateRecordSize(record));
            setRecordValue(record, value);
            updateSizeEstimator(calculateRecordSize(record));
            updateTtl(record, ttl);
        }
        saveIndex(record);

        return true;
    }

    public Object putIfAbsent(Data dataKey, Object value, long ttl) {
        checkIfLoaded();
        Record record = records.get(dataKey);
        Object oldValue = null;
        if (record == null) {
            if (mapContainer.getStore() != null) {
                oldValue = mapContainer.getStore().load(mapService.toObject(dataKey));
                if (oldValue != null) {
                    record = mapService.createRecord(name, dataKey, oldValue, DEFAULT_TTL);
                    records.put(dataKey, record);
                    updateSizeEstimator(calculateRecordSize(record));
                }
            }
        } else {
            accessRecord(record);
            oldValue = record.getValue();
        }
        if (oldValue == null) {
            value = mapService.interceptPut(name, null, value);
            record = mapService.createRecord(name, dataKey, value, ttl);
            mapStoreWrite(record, dataKey, value);
            records.put(dataKey, record);
            updateSizeEstimator(calculateRecordSize(record));
            updateTtl(record, ttl);
        }
        saveIndex(record);

        return oldValue;
    }

    private void accessRecord(Record record) {
        record.onAccess();
        final int maxIdleSeconds = mapContainer.getMapConfig().getMaxIdleSeconds();
        if (maxIdleSeconds > 0) {
            mapService.scheduleIdleEviction(name, record.getKey(), TimeUnit.SECONDS.toMillis(maxIdleSeconds));
        }
    }

    private void saveIndex(Record record) {
        Data dataKey = record.getKey();
        final IndexService indexService = mapContainer.getIndexService();
        if (indexService.hasIndex()) {
            SerializationService ss = mapService.getSerializationService();
            QueryableEntry queryableEntry = new QueryEntry(ss, dataKey, dataKey, record.getValue());
            indexService.saveEntryIndex(queryableEntry);
        }
    }

    private void mapStoreWrite(Record record, Data key, Object value) {
        final MapStoreWrapper store = mapContainer.getStore();
        if (store != null) {
            long writeDelayMillis = mapContainer.getWriteDelayMillis();
            if (writeDelayMillis <= 0) {
                store.store(mapService.toObject(key), mapService.toObject(value));
                if (record != null)
                    record.onStore();
            } else {
                mapService.scheduleMapStoreWrite(name, key, value, writeDelayMillis);
            }
        }
    }

    private void mapStoreDelete(Record record, Data key) {
        final MapStoreWrapper store = mapContainer.getStore();
        if (store != null) {
            long writeDelayMillis = mapContainer.getWriteDelayMillis();
            if (writeDelayMillis == 0) {
                store.delete(mapService.toObject(key));
                // todo ea record will be deleted then why calling onStore
                if (record != null)
                    record.onStore();
            } else {
                mapService.scheduleMapStoreDelete(name, key, writeDelayMillis);
                toBeRemovedKeys.add(key);
            }
        }
    }

    public SizeEstimator getSizeEstimator() {
        return sizeEstimator;
    }

    private void updateTtl(Record record, long ttl) {
        if (ttl > 0) {
            mapService.scheduleTtlEviction(name, record, ttl);
        } else if (ttl == 0) {
            mapContainer.getTtlEvictionScheduler().cancel(record.getKey());
        }
    }

    private void updateSizeEstimator(long recordSize) {
        sizeEstimator.add(recordSize);
    }

    private long calculateRecordSize(Record record) {
        return sizeEstimator.getCost(record);
    }

    private void resetSizeEstimator() {
        sizeEstimator.reset();
    }

    private void setRecordValue(Record record, Object value) {
        accessRecord(record);
        record.onUpdate();
        recordFactory.setValue(record, value);
    }

    private void cancelAssociatedSchedulers(Data key) {
        mapContainer.getIdleEvictionScheduler().cancel(key);
        mapContainer.getTtlEvictionScheduler().cancel(key);
    }

    private void cancelAssociatedSchedulers(Set keySet) {
        if (keySet == null || keySet.isEmpty()) return;

        for (Data key : keySet) {
            cancelAssociatedSchedulers(key);
        }
    }

    private void cancelAllSchedulers() {
        mapContainer.getIdleEvictionScheduler().cancelAll();
        mapContainer.getTtlEvictionScheduler().cancelAll();
    }

    private class MapLoadAllTask implements Runnable {
        private Map keys;
        private AtomicInteger checkIfMapLoaded;

        private MapLoadAllTask(Map keys, AtomicInteger checkIfMapLoaded) {
            this.keys = keys;
            this.checkIfMapLoaded = checkIfMapLoaded;
        }

        public void run() {
            final NodeEngine nodeEngine = mapService.getNodeEngine();

            try {
                Map values = mapContainer.getStore().loadAll(keys.values());
                if (values == null || values.isEmpty()) {
                    if (checkIfMapLoaded.decrementAndGet() == 0) {
                        loaded.set(true);
                    }
                    return;
                }

                MapEntrySet entrySet = new MapEntrySet();
                for (Data dataKey : keys.keySet()) {
                    Object key = keys.get(dataKey);
                    Object value = values.get(key);
                    if (value != null) {
                        Data dataValue = mapService.toData(value);
                        entrySet.add(dataKey, dataValue);
                    }
                }
                PutAllOperation operation = new PutAllOperation(name, entrySet, true);
                operation.setNodeEngine(nodeEngine);
                operation.setResponseHandler(new ResponseHandler() {
                    @Override
                    public void sendResponse(Object obj) {
                        if (checkIfMapLoaded.decrementAndGet() == 0) {
                            loaded.set(true);
                        }
                    }

                    public boolean isLocal() {
                        return true;
                    }
                });
                operation.setPartitionId(partitionId);
                OperationAccessor.setCallerAddress(operation, nodeEngine.getThisAddress());
                operation.setCallerUuid(nodeEngine.getLocalMember().getUuid());
                operation.setServiceName(MapService.SERVICE_NAME);
                nodeEngine.getOperationService().executeOperation(operation);
            } catch (Exception e) {
                logger.warning("Exception while load all task:" + e.toString());
            }
        }
    }
}