/*
* 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.operation;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.core.EntryEventType;
import com.hazelcast.core.EntryView;
import com.hazelcast.core.ReadOnly;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.map.EntryBackupProcessor;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.map.impl.LazyMapEntry;
import com.hazelcast.map.impl.LocalMapStatsProvider;
import com.hazelcast.map.impl.LockAwareLazyMapEntry;
import com.hazelcast.map.impl.MapContainer;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.event.MapEventPublisher;
import com.hazelcast.map.impl.record.Record;
import com.hazelcast.map.impl.recordstore.RecordStore;
import com.hazelcast.monitor.impl.LocalMapStatsImpl;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.TruePredicate;
import com.hazelcast.query.impl.FalsePredicate;
import com.hazelcast.query.impl.QueryableEntry;
import com.hazelcast.spi.BackupOperation;
import com.hazelcast.spi.EventService;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.partition.IPartitionService;
import com.hazelcast.util.Clock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.hazelcast.config.InMemoryFormat.OBJECT;
import static com.hazelcast.core.EntryEventType.ADDED;
import static com.hazelcast.core.EntryEventType.REMOVED;
import static com.hazelcast.core.EntryEventType.UPDATED;
import static com.hazelcast.internal.nearcache.impl.invalidation.ToHeapDataConverter.toHeapData;
import static com.hazelcast.map.impl.EntryViews.createSimpleEntryView;
import static com.hazelcast.map.impl.MapService.SERVICE_NAME;
import static com.hazelcast.map.impl.recordstore.RecordStore.DEFAULT_TTL;
/**
* Operator for single key processing logic of {@link EntryProcessor}/{@link EntryBackupProcessor} related operations.
*/
public final class EntryOperator {
private final boolean collectWanEvents;
private final boolean shouldClone;
private final boolean backup;
private final boolean readOnly;
private final boolean wanReplicationEnabled;
private final boolean hasEventRegistration;
private final int partitionId;
private final long now = Clock.currentTimeMillis();
private final String mapName;
private final RecordStore recordStore;
private final InternalSerializationService ss;
private final MapContainer mapContainer;
private final MapEventPublisher mapEventPublisher;
private final LocalMapStatsImpl stats;
private final IPartitionService partitionService;
private final Predicate predicate;
private final MapServiceContext mapServiceContext;
private final MapOperation mapOperation;
private final Address callerAddress;
private final List<WanEventHolder> wanEventList;
private final InMemoryFormat inMemoryFormat;
private EntryProcessor entryProcessor;
private EntryBackupProcessor backupProcessor;
// these fields can be used to reset this operator
private Data dataKey;
private Object oldValue;
private Object newValue;
private EntryEventType eventType;
private Data result;
@SuppressWarnings("checkstyle:executablestatementcount")
private EntryOperator(MapOperation mapOperation, Object processor, Predicate predicate, boolean collectWanEvents) {
this.backup = mapOperation instanceof BackupOperation;
setProcessor(processor);
this.mapOperation = mapOperation;
this.predicate = predicate;
this.recordStore = mapOperation.recordStore;
this.collectWanEvents = collectWanEvents;
this.readOnly = entryProcessor instanceof ReadOnly;
this.wanEventList = collectWanEvents ? new ArrayList<WanEventHolder>() : Collections.<WanEventHolder>emptyList();
this.mapContainer = recordStore.getMapContainer();
this.inMemoryFormat = mapContainer.getMapConfig().getInMemoryFormat();
this.mapName = mapContainer.getName();
this.wanReplicationEnabled = mapContainer.isWanReplicationEnabled();
this.shouldClone = mapContainer.shouldCloneOnEntryProcessing();
this.mapServiceContext = mapContainer.getMapServiceContext();
LocalMapStatsProvider localMapStatsProvider = mapServiceContext.getLocalMapStatsProvider();
this.stats = localMapStatsProvider.getLocalMapStatsImpl(mapName);
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
this.ss = ((InternalSerializationService) nodeEngine.getSerializationService());
this.partitionService = nodeEngine.getPartitionService();
EventService eventService = nodeEngine.getEventService();
this.hasEventRegistration = eventService.hasEventRegistration(SERVICE_NAME, mapName);
this.mapEventPublisher = mapServiceContext.getMapEventPublisher();
this.partitionId = recordStore.getPartitionId();
this.callerAddress = mapOperation.getCallerAddress();
}
private void setProcessor(Object processor) {
if (backup) {
backupProcessor = ((EntryBackupProcessor) processor);
entryProcessor = null;
} else {
entryProcessor = ((EntryProcessor) processor);
backupProcessor = null;
}
}
public static EntryOperator operator(MapOperation mapOperation) {
return new EntryOperator(mapOperation, null, null, false);
}
public static EntryOperator operator(MapOperation mapOperation, Object processor) {
return new EntryOperator(mapOperation, processor, null, false);
}
public static EntryOperator operator(MapOperation mapOperation, Object processor, Predicate predicate) {
return new EntryOperator(mapOperation, processor, predicate, false);
}
public static EntryOperator operator(MapOperation mapOperation, Object processor, Predicate predicate,
boolean collectWanEvents) {
return new EntryOperator(mapOperation, processor, predicate, collectWanEvents);
}
public EntryOperator init(Data dataKey, Object oldValue, Object newValue, Data result, EntryEventType eventType) {
this.dataKey = dataKey;
this.oldValue = oldValue;
this.newValue = newValue;
this.eventType = eventType;
this.result = result;
return this;
}
public EntryOperator operateOnKey(Data dataKey) {
init(dataKey, null, null, null, null);
if (belongsAnotherPartition(dataKey)) {
return this;
}
oldValue = recordStore.get(dataKey, backup);
Boolean locked = recordStore.isLocked(dataKey);
return operateOnKeyValueInternal(dataKey, clonedOrRawOldValue(), locked);
}
public EntryOperator operateOnKeyValue(Data dataKey, Object oldValue) {
return operateOnKeyValueInternal(dataKey, oldValue, null);
}
private EntryOperator operateOnKeyValueInternal(Data dataKey, Object oldValue, Boolean locked) {
init(dataKey, oldValue, null, null, null);
Map.Entry entry = createMapEntry(dataKey, oldValue, locked);
if (outOfPredicateScope(entry)) {
return this;
}
process(entry);
findModificationType(entry);
newValue = entry.getValue();
if (readOnly && entryWasModified()) {
throwModificationInReadOnlyException();
}
return this;
}
private boolean entryWasModified() {
return eventType != null;
}
public EntryEventType getEventType() {
return eventType;
}
public List<WanEventHolder> getWanEventList() {
return wanEventList;
}
public Object getNewValue() {
return newValue;
}
public Object getOldValue() {
return oldValue;
}
public Data getResult() {
return result;
}
public EntryOperator doPostOperateOps() {
if (eventType == null) {
// when event type is null, it means this is a read-only entry processor and not modified entry.
return this;
}
switch (eventType) {
case ADDED:
case UPDATED:
onAddedOrUpdated();
break;
case REMOVED:
onRemove();
break;
default:
throw new IllegalArgumentException("Unexpected event found:" + eventType);
}
if (wanReplicationEnabled) {
publishWanReplicationEvent();
}
if (!backup) {
if (hasEventRegistration) {
publishEntryEvent();
}
mapOperation.invalidateNearCache(dataKey);
}
mapOperation.evict(dataKey);
return this;
}
private Object clonedOrRawOldValue() {
return shouldClone ? ss.toObject(ss.toData(oldValue)) : oldValue;
}
// Needed for MultipleEntryOperation.
private boolean belongsAnotherPartition(Data key) {
return partitionService.getPartitionId(key) != partitionId;
}
private boolean outOfPredicateScope(Map.Entry entry) {
assert entry instanceof QueryableEntry;
if (predicate == null || predicate == TruePredicate.INSTANCE) {
return false;
}
return predicate == FalsePredicate.INSTANCE || !predicate.apply(entry);
}
private Map.Entry createMapEntry(Data key, Object value, Boolean locked) {
return new LockAwareLazyMapEntry(key, value, ss, mapContainer.getExtractors(), locked);
}
private void findModificationType(Map.Entry entry) {
LazyMapEntry lazyMapEntry = (LazyMapEntry) entry;
if (!lazyMapEntry.isModified()
|| (oldValue == null && lazyMapEntry.hasNullValue())) {
// read only
eventType = null;
return;
}
if (lazyMapEntry.hasNullValue()) {
eventType = REMOVED;
return;
}
eventType = oldValue == null ? ADDED : UPDATED;
}
private void onAddedOrUpdated() {
if (backup) {
recordStore.putBackup(dataKey, newValue);
} else {
recordStore.set(dataKey, newValue, DEFAULT_TTL);
if (mapOperation.isPostProcessing(recordStore)) {
Record record = recordStore.getRecord(dataKey);
newValue = record == null ? null : record.getValue();
}
mapServiceContext.interceptAfterPut(mapName, newValue);
stats.incrementPuts(getLatencyFrom(now));
}
}
private void onRemove() {
if (backup) {
recordStore.removeBackup(dataKey);
} else {
recordStore.delete(dataKey);
mapServiceContext.interceptAfterRemove(mapName, oldValue);
stats.incrementRemoves(getLatencyFrom(now));
}
}
private static long getLatencyFrom(long begin) {
return Clock.currentTimeMillis() - begin;
}
private void process(Map.Entry entry) {
if (backup) {
backupProcessor.processBackup(entry);
return;
}
result = ss.toData(entryProcessor.process(entry));
}
private void throwModificationInReadOnlyException() {
throw new UnsupportedOperationException("Entry Processor " + entryProcessor.getClass().getName()
+ " marked as ReadOnly tried to modify map " + mapName + ". This is not supported. Remove "
+ "the ReadOnly marker from the Entry Processor or do not modify the entry in the process "
+ "method.");
}
private void publishWanReplicationEvent() {
assert entryWasModified();
Data dataKey = toHeapData(this.dataKey);
if (eventType == REMOVED) {
if (backup) {
mapEventPublisher.publishWanReplicationRemoveBackup(mapName, dataKey, Clock.currentTimeMillis());
} else {
mapEventPublisher.publishWanReplicationRemove(mapName, dataKey, Clock.currentTimeMillis());
if (collectWanEvents) {
wanEventList.add(new WanEventHolder(dataKey, null, REMOVED));
}
}
return;
}
Record record = recordStore.getRecord(dataKey);
Data dataNewValue = toHeapData(ss.toData(newValue));
EntryView entryView = createSimpleEntryView(dataKey, dataNewValue, record);
if (backup) {
mapEventPublisher.publishWanReplicationUpdateBackup(mapName, entryView);
} else {
mapEventPublisher.publishWanReplicationUpdate(mapName, entryView);
if (collectWanEvents) {
wanEventList.add(new WanEventHolder(dataKey, dataNewValue, UPDATED));
}
}
}
private void publishEntryEvent() {
Object oldValue = getOrNullOldValue();
mapEventPublisher.publishEvent(callerAddress, mapName, eventType, toHeapData(dataKey), oldValue, newValue);
}
/**
* Nullify old value if in memory format is object and operation is not removal
* since old and new value in fired event {@link com.hazelcast.core.EntryEvent}
* may be same due to the object in memory format.
*/
private Object getOrNullOldValue() {
if (inMemoryFormat == OBJECT && eventType != REMOVED) {
return null;
} else {
return oldValue;
}
}
}