/*
* 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.tx;
import com.hazelcast.core.PartitioningStrategy;
import com.hazelcast.map.impl.MapContainer;
import com.hazelcast.map.impl.MapService;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.nearcache.MapNearCacheManager;
import com.hazelcast.map.impl.operation.MapOperation;
import com.hazelcast.map.impl.operation.MapOperationProvider;
import com.hazelcast.map.impl.record.RecordFactory;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.OperationFactory;
import com.hazelcast.spi.OperationService;
import com.hazelcast.spi.TransactionalDistributedObject;
import com.hazelcast.spi.partition.IPartitionService;
import com.hazelcast.transaction.TransactionNotActiveException;
import com.hazelcast.transaction.TransactionOptions.TransactionType;
import com.hazelcast.transaction.TransactionTimedOutException;
import com.hazelcast.transaction.impl.Transaction;
import com.hazelcast.util.ThreadUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static com.hazelcast.internal.nearcache.NearCache.CACHED_AS_NULL;
import static com.hazelcast.internal.nearcache.NearCache.NOT_CACHED;
import static com.hazelcast.map.impl.MapService.SERVICE_NAME;
import static com.hazelcast.util.ExceptionUtil.rethrow;
/**
* Base class contains proxy helper methods for {@link com.hazelcast.map.impl.tx.TransactionalMapProxy}
*/
public abstract class TransactionalMapProxySupport extends TransactionalDistributedObject<MapService> {
protected final Map<Data, VersionedValue> valueMap = new HashMap<Data, VersionedValue>();
protected final String name;
protected final MapServiceContext mapServiceContext;
protected final MapNearCacheManager mapNearCacheManager;
protected final MapOperationProvider operationProvider;
protected final PartitioningStrategy partitionStrategy;
protected final IPartitionService partitionService;
protected final OperationService operationService;
private final RecordFactory recordFactory;
private final boolean nearCacheEnabled;
TransactionalMapProxySupport(String name, MapService mapService, NodeEngine nodeEngine, Transaction transaction) {
super(nodeEngine, mapService, transaction);
this.name = name;
this.mapServiceContext = mapService.getMapServiceContext();
this.mapNearCacheManager = mapServiceContext.getMapNearCacheManager();
this.operationProvider = mapServiceContext.getMapOperationProvider(name);
MapContainer mapContainer = mapServiceContext.getMapContainer(name);
this.partitionStrategy = mapContainer.getPartitioningStrategy();
this.partitionService = nodeEngine.getPartitionService();
this.operationService = nodeEngine.getOperationService();
this.recordFactory = mapContainer.getRecordFactoryConstructor().createNew(null);
this.nearCacheEnabled = mapContainer.getMapConfig().isNearCacheEnabled();
}
@Override
public String getName() {
return name;
}
@Override
public final String getServiceName() {
return SERVICE_NAME;
}
boolean isEquals(Object value1, Object value2) {
return recordFactory.isEquals(value1, value2);
}
void checkTransactionState() {
if (!tx.getState().equals(Transaction.State.ACTIVE)) {
throw new TransactionNotActiveException("Transaction is not active!");
}
}
boolean containsKeyInternal(Data key) {
MapOperation operation = operationProvider.createContainsKeyOperation(name, key);
operation.setThreadId(ThreadUtil.getThreadId());
int partitionId = partitionService.getPartitionId(key);
try {
Future future = operationService.invokeOnPartition(SERVICE_NAME, operation, partitionId);
return (Boolean) future.get();
} catch (Throwable t) {
throw rethrow(t);
}
}
Object getInternal(Data key) {
if (nearCacheEnabled) {
Object value = getCachedValue(key, true);
if (value != NOT_CACHED) {
return value;
}
}
MapOperation operation = operationProvider.createGetOperation(name, key);
operation.setThreadId(ThreadUtil.getThreadId());
int partitionId = partitionService.getPartitionId(key);
try {
Future future = operationService.invokeOnPartition(SERVICE_NAME, operation, partitionId);
return future.get();
} catch (Throwable t) {
throw rethrow(t);
}
}
private Object getCachedValue(Data key, boolean deserializeValue) {
Object value = mapNearCacheManager.getFromNearCache(name, key);
if (value == null) {
return NOT_CACHED;
}
if (value == CACHED_AS_NULL) {
return null;
}
return deserializeValue ? getNodeEngine().getSerializationService().toObject(value) : value;
}
Object getForUpdateInternal(Data key) {
VersionedValue versionedValue = lockAndGet(key, tx.getTimeoutMillis(), true);
addUnlockTransactionRecord(key, versionedValue.version);
return versionedValue.value;
}
int sizeInternal() {
try {
OperationFactory sizeOperationFactory = operationProvider.createMapSizeOperationFactory(name);
Map<Integer, Object> results = operationService.invokeOnAllPartitions(SERVICE_NAME, sizeOperationFactory);
int total = 0;
for (Object result : results.values()) {
Integer size = getNodeEngine().toObject(result);
total += size;
}
return total;
} catch (Throwable t) {
throw rethrow(t);
}
}
Data putInternal(Data key, Data value, long ttl, TimeUnit timeUnit) {
VersionedValue versionedValue = lockAndGet(key, tx.getTimeoutMillis());
long timeInMillis = getTimeInMillis(ttl, timeUnit);
MapOperation operation = operationProvider.createTxnSetOperation(name, key, value, versionedValue.version, timeInMillis);
tx.add(new MapTransactionLogRecord(name, key, getPartitionId(key), operation, versionedValue.version, tx.getOwnerUuid()));
return versionedValue.value;
}
Data putIfAbsentInternal(Data key, Data value) {
boolean unlockImmediately = !valueMap.containsKey(key);
VersionedValue versionedValue = lockAndGet(key, tx.getTimeoutMillis());
if (versionedValue.value != null) {
if (unlockImmediately) {
unlock(key, versionedValue);
return versionedValue.value;
}
addUnlockTransactionRecord(key, versionedValue.version);
return versionedValue.value;
}
MapOperation operation = operationProvider.createTxnSetOperation(name, key, value, versionedValue.version, -1);
tx.add(new MapTransactionLogRecord(name, key, getPartitionId(key), operation, versionedValue.version, tx.getOwnerUuid()));
return versionedValue.value;
}
Data replaceInternal(Data key, Data value) {
boolean unlockImmediately = !valueMap.containsKey(key);
VersionedValue versionedValue = lockAndGet(key, tx.getTimeoutMillis());
if (versionedValue.value == null) {
if (unlockImmediately) {
unlock(key, versionedValue);
return null;
}
addUnlockTransactionRecord(key, versionedValue.version);
return null;
}
MapOperation operation = operationProvider.createTxnSetOperation(name, key, value, versionedValue.version, -1);
tx.add(new MapTransactionLogRecord(name, key, getPartitionId(key), operation, versionedValue.version, tx.getOwnerUuid()));
return versionedValue.value;
}
boolean replaceIfSameInternal(Data key, Object oldValue, Data newValue) {
boolean unlockImmediately = !valueMap.containsKey(key);
VersionedValue versionedValue = lockAndGet(key, tx.getTimeoutMillis());
if (!isEquals(oldValue, versionedValue.value)) {
if (unlockImmediately) {
unlock(key, versionedValue);
return false;
}
addUnlockTransactionRecord(key, versionedValue.version);
return false;
}
MapOperation operation = operationProvider.createTxnSetOperation(name, key, newValue, versionedValue.version, -1);
tx.add(new MapTransactionLogRecord(name, key, getPartitionId(key), operation, versionedValue.version, tx.getOwnerUuid()));
return true;
}
Data removeInternal(Data key) {
VersionedValue versionedValue = lockAndGet(key, tx.getTimeoutMillis());
tx.add(new MapTransactionLogRecord(name, key, getPartitionId(key),
operationProvider.createTxnDeleteOperation(name, key, versionedValue.version),
versionedValue.version, tx.getOwnerUuid()));
return versionedValue.value;
}
boolean removeIfSameInternal(Data key, Object value) {
boolean unlockImmediately = !valueMap.containsKey(key);
VersionedValue versionedValue = lockAndGet(key, tx.getTimeoutMillis());
if (!isEquals(versionedValue.value, value)) {
if (unlockImmediately) {
unlock(key, versionedValue);
return false;
}
addUnlockTransactionRecord(key, versionedValue.version);
return false;
}
tx.add(new MapTransactionLogRecord(name, key, getPartitionId(key),
operationProvider.createTxnDeleteOperation(name, key, versionedValue.version),
versionedValue.version, tx.getOwnerUuid()));
return true;
}
private void unlock(Data key, VersionedValue versionedValue) {
try {
TxnUnlockOperation unlockOperation = new TxnUnlockOperation(name, key, versionedValue.version);
unlockOperation.setThreadId(ThreadUtil.getThreadId());
unlockOperation.setOwnerUuid(tx.getOwnerUuid());
int partitionId = partitionService.getPartitionId(key);
Future<VersionedValue> future = operationService.invokeOnPartition(SERVICE_NAME, unlockOperation, partitionId);
future.get();
valueMap.remove(key);
} catch (Throwable t) {
throw rethrow(t);
}
}
private void addUnlockTransactionRecord(Data key, long version) {
TxnUnlockOperation operation = new TxnUnlockOperation(name, key, version);
tx.add(new MapTransactionLogRecord(name, key, getPartitionId(key), operation, version, tx.getOwnerUuid()));
}
/**
* Locks the key on the partition owner and returns the value with the version. Does not invokes maploader if
* the key is missing in memory
*
* @param key serialized key
* @param timeout timeout in millis
* @return VersionedValue wrapper for value/version pair.
*/
private VersionedValue lockAndGet(Data key, long timeout) {
return lockAndGet(key, timeout, false);
}
private VersionedValue lockAndGet(Data key, long timeout, boolean shouldLoad) {
VersionedValue versionedValue = valueMap.get(key);
if (versionedValue != null) {
return versionedValue;
}
boolean blockReads = tx.getTransactionType() == TransactionType.ONE_PHASE;
MapOperation operation = operationProvider.createTxnLockAndGetOperation(name, key, timeout, timeout,
tx.getOwnerUuid(), shouldLoad, blockReads);
operation.setThreadId(ThreadUtil.getThreadId());
try {
int partitionId = partitionService.getPartitionId(key);
Future<VersionedValue> future = operationService.invokeOnPartition(SERVICE_NAME, operation, partitionId);
versionedValue = future.get();
if (versionedValue == null) {
throw new TransactionTimedOutException("Transaction couldn't obtain lock for the key: " + toObjectIfNeeded(key));
}
valueMap.put(key, versionedValue);
return versionedValue;
} catch (Throwable t) {
throw rethrow(t);
}
}
private static long getTimeInMillis(long time, TimeUnit timeunit) {
return timeunit != null ? timeunit.toMillis(time) : time;
}
}