/*
* 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.replicatedmap.impl.record;
import com.hazelcast.cluster.memberselector.MemberSelectors;
import com.hazelcast.core.Member;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.replicatedmap.impl.ReplicatedMapEventPublishingService;
import com.hazelcast.replicatedmap.impl.ReplicatedMapService;
import com.hazelcast.replicatedmap.impl.operation.ReplicateUpdateOperation;
import com.hazelcast.replicatedmap.impl.operation.VersionResponsePair;
import com.hazelcast.replicatedmap.merge.ReplicatedMapMergePolicy;
import com.hazelcast.spi.OperationService;
import com.hazelcast.util.Clock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static com.hazelcast.core.EntryEventType.EVICTED;
import static com.hazelcast.replicatedmap.impl.ReplicatedMapService.SERVICE_NAME;
import static com.hazelcast.util.Preconditions.isNotNull;
/**
* This is the base class for all {@link ReplicatedRecordStore} implementations
*
* @param <K> key type
* @param <V> value type
*/
public abstract class AbstractReplicatedRecordStore<K, V> extends AbstractBaseReplicatedRecordStore<K, V> {
public AbstractReplicatedRecordStore(String name, ReplicatedMapService replicatedMapService, int partitionId) {
super(name, replicatedMapService, partitionId);
}
@Override
public Object remove(Object key) {
final InternalReplicatedMapStorage<K, V> storage = getStorage();
final Object old = remove(storage, key);
storage.incrementVersion();
return old;
}
@Override
public Object removeWithVersion(Object key, long version) {
final InternalReplicatedMapStorage<K, V> storage = getStorage();
final Object old = remove(storage, key);
storage.setVersion(version);
return old;
}
private Object remove(InternalReplicatedMapStorage<K, V> storage, Object key) {
isNotNull(key, "key");
long time = Clock.currentTimeMillis();
V oldValue;
K marshalledKey = (K) marshall(key);
final ReplicatedRecord current = storage.get(marshalledKey);
if (current == null) {
oldValue = null;
} else {
oldValue = (V) current.getValueInternal();
storage.remove(marshalledKey, current);
}
Object unmarshalledOldValue = unmarshall(oldValue);
if (replicatedMapConfig.isStatisticsEnabled()) {
getStats().incrementRemoves(Clock.currentTimeMillis() - time);
}
return unmarshalledOldValue;
}
@Override
public void evict(Object key) {
isNotNull(key, "key");
long time = Clock.currentTimeMillis();
V oldValue;
K marshalledKey = (K) marshall(key);
InternalReplicatedMapStorage<K, V> storage = getStorage();
ReplicatedRecord current = storage.get(marshalledKey);
if (current == null) {
oldValue = null;
} else {
oldValue = (V) current.getValueInternal();
storage.remove(marshalledKey, current);
}
Data dataKey = nodeEngine.toData(key);
Data dataOldValue = nodeEngine.toData(oldValue);
ReplicatedMapEventPublishingService eventPublishingService = replicatedMapService.getEventPublishingService();
eventPublishingService.fireEntryListenerEvent(dataKey, dataOldValue, null, EVICTED, name, nodeEngine.getThisAddress());
if (replicatedMapConfig.isStatisticsEnabled()) {
getStats().incrementRemoves(Clock.currentTimeMillis() - time);
}
}
@Override
public Object get(Object key) {
isNotNull(key, "key");
long time = Clock.currentTimeMillis();
ReplicatedRecord replicatedRecord = getStorage().get(marshall(key));
// Force return null on ttl expiration (but before cleanup thread run)
long ttlMillis = replicatedRecord == null ? 0 : replicatedRecord.getTtlMillis();
if (ttlMillis > 0 && Clock.currentTimeMillis() - replicatedRecord.getUpdateTime() >= ttlMillis) {
replicatedRecord = null;
}
Object value = replicatedRecord == null ? null : unmarshall(replicatedRecord.getValue());
if (replicatedMapConfig.isStatisticsEnabled()) {
getStats().incrementGets(Clock.currentTimeMillis() - time);
}
return value;
}
@Override
public Object put(Object key, Object value) {
isNotNull(key, "key");
isNotNull(value, "value");
return put(key, value, 0, TimeUnit.MILLISECONDS, true);
}
@Override
public Object put(Object key, Object value, long ttl, TimeUnit timeUnit, boolean incrementHits) {
final InternalReplicatedMapStorage<K, V> storage = getStorage();
final Object old = put(storage, key, value, ttl, timeUnit, incrementHits);
storage.incrementVersion();
return old;
}
@Override
public Object putWithVersion(Object key, Object value, long ttl, TimeUnit timeUnit, boolean incrementHits, long version) {
final InternalReplicatedMapStorage<K, V> storage = getStorage();
final Object old = put(storage, key, value, ttl, timeUnit, incrementHits);
storage.setVersion(version);
return old;
}
private Object put(InternalReplicatedMapStorage<K, V> storage, Object key, Object value,
long ttl, TimeUnit timeUnit, boolean incrementHits) {
isNotNull(key, "key");
isNotNull(value, "value");
isNotNull(timeUnit, "timeUnit");
if (ttl < 0) {
throw new IllegalArgumentException("ttl must be a positive integer");
}
long time = Clock.currentTimeMillis();
V oldValue = null;
K marshalledKey = (K) marshall(key);
V marshalledValue = (V) marshall(value);
final long ttlMillis = ttl == 0 ? 0 : timeUnit.toMillis(ttl);
final ReplicatedRecord<K, V> old = storage.get(marshalledKey);
ReplicatedRecord<K, V> record;
if (old == null) {
record = buildReplicatedRecord(marshalledKey, marshalledValue, ttlMillis);
storage.put(marshalledKey, record);
} else {
oldValue = old.getValueInternal();
if (incrementHits) {
old.setValue(marshalledValue, ttlMillis);
} else {
old.setValueInternal(marshalledValue, ttlMillis);
}
storage.put(marshalledKey, old);
}
if (ttlMillis > 0) {
scheduleTtlEntry(ttlMillis, marshalledKey, marshalledValue);
} else {
cancelTtlEntry(marshalledKey);
}
if (replicatedMapConfig.isStatisticsEnabled()) {
getStats().incrementPuts(Clock.currentTimeMillis() - time);
}
return oldValue;
}
@Override
public boolean containsKey(Object key) {
isNotNull(key, "key");
getStats().incrementOtherOperations();
return containsKeyAndValue(key);
}
// IMPORTANT >> Increments hit counter
private boolean containsKeyAndValue(Object key) {
ReplicatedRecord replicatedRecord = getStorage().get(marshall(key));
return replicatedRecord != null && replicatedRecord.getValue() != null;
}
@Override
public boolean containsValue(Object value) {
isNotNull(value, "value");
getStats().incrementOtherOperations();
Object v = unmarshall(value);
for (Map.Entry<K, ReplicatedRecord<K, V>> entry : getStorage().entrySet()) {
V entryValue = entry.getValue().getValue();
if (v == entryValue || (entryValue != null && unmarshall(entryValue).equals(v))) {
return true;
}
}
return false;
}
@Override
public Set keySet(boolean lazy) {
getStats().incrementOtherOperations();
if (lazy) {
// Lazy evaluation to prevent to much copying
return new LazySet<K, V, K>(new KeySetIteratorFactory<K, V>(this), getStorage());
}
return getStorage().keySet();
}
@Override
public Collection values(boolean lazy) {
getStats().incrementOtherOperations();
if (lazy) {
// Lazy evaluation to prevent to much copying
return new LazyCollection<K, V>(new ValuesIteratorFactory<K, V>(this), getStorage());
}
return getStorage().values();
}
@Override
public Collection values(Comparator comparator) {
InternalReplicatedMapStorage<K, V> storage = getStorage();
List values = new ArrayList(storage.size());
for (ReplicatedRecord record : storage.values()) {
values.add(unmarshall(record.getValue()));
}
getStats().incrementOtherOperations();
return values;
}
@Override
public Set entrySet(boolean lazy) {
getStats().incrementOtherOperations();
if (lazy) {
// Lazy evaluation to prevent to much copying
return new LazySet<K, V, Map.Entry<K, V>>(new EntrySetIteratorFactory<K, V>(this), getStorage());
}
return getStorage().entrySet();
}
@Override
public ReplicatedRecord getReplicatedRecord(Object key) {
isNotNull(key, "key");
return getStorage().get(marshall(key));
}
@Override
public boolean isEmpty() {
getStats().incrementOtherOperations();
return getStorage().isEmpty();
}
@Override
public int size() {
getStats().incrementOtherOperations();
return getStorage().size();
}
@Override
public void clear() {
InternalReplicatedMapStorage<K, V> storage = getStorage();
storage.clear();
storage.incrementVersion();
getStats().incrementOtherOperations();
}
@Override
public void clearWithVersion(long version) {
InternalReplicatedMapStorage<K, V> storage = getStorage();
storage.clear();
storage.setVersion(version);
getStats().incrementOtherOperations();
}
@Override
public void reset() {
destroy();
}
@Override
public Iterator recordIterator() {
return new RecordIterator(getStorage().entrySet().iterator());
}
public void putRecords(Collection<RecordMigrationInfo> records, long version) {
final InternalReplicatedMapStorage<K, V> storage = getStorage();
for (RecordMigrationInfo record : records) {
putRecord(storage, record);
}
storage.syncVersion(version);
}
private void putRecord(InternalReplicatedMapStorage<K, V> storage, RecordMigrationInfo record) {
K key = (K) marshall(record.getKey());
V value = (V) marshall(record.getValue());
ReplicatedRecord newRecord = buildReplicatedRecord(key, value, record.getTtl());
newRecord.setHits(record.getHits());
newRecord.setCreationTime(record.getCreationTime());
newRecord.setLastAccessTime(record.getLastAccessTime());
newRecord.setUpdateTime(record.getLastUpdateTime());
storage.put(key, newRecord);
if (record.getTtl() > 0) {
scheduleTtlEntry(record.getTtl(), key, value);
}
}
private ReplicatedRecord<K, V> buildReplicatedRecord(K key, V value, long ttlMillis) {
return new ReplicatedRecord<K, V>(key, value, ttlMillis);
}
@Override
public boolean merge(Object key, ReplicatedMapEntryView mergingEntry, ReplicatedMapMergePolicy policy) {
Object marshalledKey = marshall(key);
InternalReplicatedMapStorage<K, V> storage = getStorage();
ReplicatedRecord<K, V> record = storage.get(marshalledKey);
Object newValue;
if (record == null) {
ReplicatedMapEntryView nullEntryView = new ReplicatedMapEntryView(unmarshall(key), null);
newValue = policy.merge(getName(), mergingEntry, nullEntryView);
if (newValue == null) {
return false;
}
record = buildReplicatedRecord((K) marshalledKey, (V) newValue, 0);
storage.put((K) marshalledKey, record);
storage.incrementVersion();
Data dataKey = serializationService.toData(marshalledKey);
Data dataValue = serializationService.toData(newValue);
VersionResponsePair responsePair = new VersionResponsePair(mergingEntry.getValue(), getVersion());
sendReplicationOperation(false, getName(), dataKey, dataValue, record.getTtlMillis(), responsePair);
} else {
Object oldValue = record.getValueInternal();
ReplicatedMapEntryView existingEntry = new ReplicatedMapEntryView(unmarshall(key), unmarshall(oldValue));
existingEntry.setCreationTime(record.getCreationTime());
existingEntry.setLastUpdateTime(record.getUpdateTime());
existingEntry.setLastAccessTime(record.getLastAccessTime());
existingEntry.setHits(record.getHits());
existingEntry.setTtl(record.getTtlMillis());
newValue = policy.merge(getName(), mergingEntry, existingEntry);
if (newValue == null) {
storage.remove((K) marshalledKey, record);
storage.incrementVersion();
Data dataKey = serializationService.toData(marshalledKey);
VersionResponsePair responsePair = new VersionResponsePair(mergingEntry.getValue(), getVersion());
sendReplicationOperation(true, getName(), dataKey, null, record.getTtlMillis(), responsePair);
return false;
}
record.setValueInternal((V) newValue, record.getTtlMillis());
storage.incrementVersion();
Data dataKey = serializationService.toData(marshalledKey);
Data dataValue = serializationService.toData(newValue);
VersionResponsePair responsePair = new VersionResponsePair(mergingEntry.getValue(), getVersion());
sendReplicationOperation(false, getName(), dataKey, dataValue, record.getTtlMillis(), responsePair);
}
return true;
}
protected void sendReplicationOperation(final boolean isRemove, String name, Data key, Data value, long ttl,
VersionResponsePair response) {
Collection<Member> members = nodeEngine.getClusterService().getMembers(MemberSelectors.DATA_MEMBER_SELECTOR);
for (Member member : members) {
invoke(isRemove, member.getAddress(), name, key, value, ttl, response);
}
}
private void invoke(boolean isRemove, Address address, String name, Data key, Data value, long ttl,
VersionResponsePair response) {
OperationService operationService = nodeEngine.getOperationService();
ReplicateUpdateOperation updateOperation = new ReplicateUpdateOperation(name, key, value, ttl,
response, isRemove, nodeEngine.getThisAddress());
updateOperation.setPartitionId(partitionId);
updateOperation.setValidateTarget(false);
operationService.invokeOnTarget(SERVICE_NAME, updateOperation, address);
}
private final class RecordIterator implements Iterator<ReplicatedRecord<K, V>> {
private final Iterator<Map.Entry<K, ReplicatedRecord<K, V>>> iterator;
private Map.Entry<K, ReplicatedRecord<K, V>> entry;
private RecordIterator(Iterator<Map.Entry<K, ReplicatedRecord<K, V>>> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
while (iterator.hasNext()) {
entry = iterator.next();
if (testEntry(entry)) {
return true;
}
}
return false;
}
@Override
public ReplicatedRecord<K, V> next() {
Map.Entry<K, ReplicatedRecord<K, V>> entry = this.entry;
ReplicatedRecord<K, V> record = entry != null ? entry.getValue() : null;
while (entry == null) {
entry = findNextEntry();
Object key = entry.getKey();
record = entry.getValue();
Object value = record != null ? record.getValue() : null;
if (key != null && value != null) {
break;
}
}
this.entry = null;
return record;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Lazy structures are not modifiable");
}
private boolean testEntry(Map.Entry<K, ReplicatedRecord<K, V>> entry) {
return entry.getKey() != null && entry.getValue() != null && !entry.getValue().isTombstone();
}
private Map.Entry<K, ReplicatedRecord<K, V>> findNextEntry() {
Map.Entry<K, ReplicatedRecord<K, V>> entry = null;
while (iterator.hasNext()) {
entry = iterator.next();
if (testEntry(entry)) {
break;
}
entry = null;
}
if (entry == null) {
throw new NoSuchElementException();
}
return entry;
}
}
}