/*
* 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.recordstore;
import com.hazelcast.concurrent.lock.LockService;
import com.hazelcast.concurrent.lock.LockStore;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.config.MapConfig;
import com.hazelcast.map.impl.EntryCostEstimator;
import com.hazelcast.map.impl.MapContainer;
import com.hazelcast.map.impl.MapService;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.mapstore.MapDataStore;
import com.hazelcast.map.impl.mapstore.MapStoreContext;
import com.hazelcast.map.impl.mapstore.MapStoreManager;
import com.hazelcast.map.impl.record.Record;
import com.hazelcast.map.impl.record.RecordFactory;
import com.hazelcast.map.impl.record.Records;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.query.impl.Indexes;
import com.hazelcast.query.impl.QueryableEntry;
import com.hazelcast.spi.DistributedObjectNamespace;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.serialization.SerializationService;
import com.hazelcast.util.Clock;
import java.util.Collection;
import static com.hazelcast.config.InMemoryFormat.NATIVE;
import static com.hazelcast.internal.nearcache.impl.invalidation.ToHeapDataConverter.toHeapData;
import static com.hazelcast.map.impl.ExpirationTimeSetter.calculateMaxIdleMillis;
import static com.hazelcast.map.impl.ExpirationTimeSetter.calculateTTLMillis;
import static com.hazelcast.map.impl.ExpirationTimeSetter.pickTTL;
import static com.hazelcast.map.impl.ExpirationTimeSetter.setExpirationTime;
/**
* Contains record store common parts.
*/
abstract class AbstractRecordStore implements RecordStore<Record> {
protected final String name;
protected final LockStore lockStore;
protected final RecordFactory recordFactory;
protected final MapContainer mapContainer;
protected final MapServiceContext mapServiceContext;
protected final SerializationService serializationService;
protected final MapDataStore<Data, Object> mapDataStore;
protected final MapStoreContext mapStoreContext;
protected final InMemoryFormat inMemoryFormat;
protected final int partitionId;
protected Storage<Data, Record> storage;
private long hits;
private long lastAccess;
private long lastUpdate;
protected AbstractRecordStore(MapContainer mapContainer, int partitionId) {
this.mapContainer = mapContainer;
this.partitionId = partitionId;
this.mapServiceContext = mapContainer.getMapServiceContext();
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
this.serializationService = nodeEngine.getSerializationService();
this.name = mapContainer.getName();
this.recordFactory = mapContainer.getRecordFactoryConstructor().createNew(null);
this.inMemoryFormat = mapContainer.getMapConfig().getInMemoryFormat();
this.mapStoreContext = mapContainer.getMapStoreContext();
MapStoreManager mapStoreManager = mapStoreContext.getMapStoreManager();
this.mapDataStore = mapStoreManager.getMapDataStore(name, partitionId);
this.lockStore = createLockStore();
}
@Override
public void init() {
this.storage = createStorage(recordFactory, inMemoryFormat);
}
@Override
public Record createRecord(Object value, long ttlMillis, long now) {
MapConfig mapConfig = mapContainer.getMapConfig();
Record record = recordFactory.newRecord(value);
record.setCreationTime(now);
record.setLastUpdateTime(now);
final long ttlMillisFromConfig = calculateTTLMillis(mapConfig);
final long ttl = pickTTL(ttlMillis, ttlMillisFromConfig);
record.setTtl(ttl);
final long maxIdleMillis = calculateMaxIdleMillis(mapConfig);
setExpirationTime(record, maxIdleMillis);
updateStatsOnPut(true, now);
return record;
}
@Override
public Storage createStorage(RecordFactory recordFactory, InMemoryFormat memoryFormat) {
return new StorageImpl(recordFactory, memoryFormat, serializationService);
}
@Override
public String getName() {
return name;
}
@Override
public MapContainer getMapContainer() {
return mapContainer;
}
@Override
public long getOwnedEntryCost() {
return storage.getEntryCostEstimator().getEstimate();
}
protected long getNow() {
return Clock.currentTimeMillis();
}
protected void updateRecord(Data key, Record record, Object value, long now) {
updateStatsOnPut(false, now);
record.onUpdate(now);
storage.updateRecordValue(key, record, value);
}
@Override
public int getPartitionId() {
return partitionId;
}
protected void saveIndex(Record record, Object oldValue) {
Data dataKey = record.getKey();
final Indexes indexes = mapContainer.getIndexes();
if (indexes.hasIndex()) {
Object value = Records.getValueOrCachedValue(record, serializationService);
// When using format InMemoryFormat.NATIVE, just copy key & value to heap.
if (NATIVE == inMemoryFormat) {
dataKey = (Data) copyToHeap(dataKey);
value = copyToHeap(value);
oldValue = copyToHeap(oldValue);
}
QueryableEntry queryableEntry = mapContainer.newQueryEntry(dataKey, value);
indexes.saveEntryIndex(queryableEntry, oldValue);
}
}
protected void removeIndex(Record record) {
Indexes indexes = mapContainer.getIndexes();
if (indexes.hasIndex()) {
Data key = record.getKey();
Object value = Records.getValueOrCachedValue(record, serializationService);
if (NATIVE == inMemoryFormat) {
key = (Data) copyToHeap(key);
value = copyToHeap(value);
}
indexes.removeEntryIndex(key, value);
}
}
protected Object copyToHeap(Object object) {
if (object instanceof Data) {
return toHeapData(((Data) object));
} else {
return object;
}
}
protected void removeIndex(Collection<Record> records) {
Indexes indexes = mapContainer.getIndexes();
if (!indexes.hasIndex()) {
return;
}
for (Record record : records) {
removeIndex(record);
}
}
protected LockStore createLockStore() {
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
final LockService lockService = nodeEngine.getSharedService(LockService.SERVICE_NAME);
if (lockService == null) {
return null;
}
return lockService.createLockStore(partitionId, new DistributedObjectNamespace(MapService.SERVICE_NAME, name));
}
public int getLockedEntryCount() {
return lockStore.getLockedEntryCount();
}
protected RecordStoreLoader createRecordStoreLoader(MapStoreContext mapStoreContext) {
return mapStoreContext.getMapStoreWrapper() == null
? RecordStoreLoader.EMPTY_LOADER : new BasicRecordStoreLoader(this);
}
protected Data toData(Object value) {
return mapServiceContext.toData(value);
}
public void setSizeEstimator(EntryCostEstimator entryCostEstimator) {
this.storage.setEntryCostEstimator(entryCostEstimator);
}
@Override
public void disposeDeferredBlocks() {
storage.disposeDeferredBlocks();
}
public Storage<Data, ? extends Record> getStorage() {
return storage;
}
@Override
public long getHits() {
return hits;
}
@Override
public long getLastAccessTime() {
return lastAccess;
}
@Override
public long getLastUpdateTime() {
return lastUpdate;
}
@Override
public void increaseHits() {
this.hits++;
}
@Override
public void increaseHits(long hits) {
this.hits += hits;
}
@Override
public void decreaseHits(long hits) {
this.hits -= hits;
}
@Override
public void setLastAccessTime(long time) {
this.lastAccess = Math.max(this.lastAccess, time);
}
@Override
public void setLastUpdateTime(long time) {
this.lastUpdate = Math.max(this.lastUpdate, time);
}
protected void updateStatsOnPut(boolean newRecord, long now) {
setLastUpdateTime(now);
if (!newRecord) {
updateStatsOnGet(now);
}
}
protected void updateStatsOnPut(long hits) {
increaseHits(hits);
}
protected void updateStatsOnGet(long now) {
setLastAccessTime(now);
increaseHits();
}
protected void updateStatsOnRemove(long hits) {
decreaseHits(hits);
}
protected void resetStats() {
this.hits = 0;
this.lastAccess = 0;
this.lastUpdate = 0;
}
}