/* * Copyright (c) 2008-2012, Hazel Bilisim Ltd. 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.impl; import com.hazelcast.core.Instance; import com.hazelcast.core.Transaction; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.Data; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import static com.hazelcast.nio.IOUtil.toObject; public class TransactionImpl implements Transaction { public static final long DEFAULT_TXN_TIMEOUT = 30 * 1000; private final long id; private final FactoryImpl factory; private final List<TransactionRecord> transactionRecords = new CopyOnWriteArrayList<TransactionRecord>(); private int status = TXN_STATUS_NO_TXN; private final ILogger logger; public TransactionImpl(FactoryImpl factory, long txnId) { this.id = txnId; this.factory = factory; this.logger = factory.getLoggingService().getLogger(this.getClass().getName()); } public Data attachPutOp(String name, Object key, Data value, boolean newRecord) { return attachPutOp(name, key, value, 0, -1, newRecord); } public void attachPutMultiOp(String name, Object key, Data value) { transactionRecords.add(new TransactionRecord(name, key, value, true)); } public Data attachPutOp(String name, Object key, Data value, long timeout, boolean newRecord) { return attachPutOp(name, key, value, timeout, -1, newRecord); } public Data attachPutOp(String name, Object key, Data value, long timeout, long ttl, boolean newRecord) { Instance.InstanceType instanceType = ConcurrentMapManager.getInstanceType(name); Object matchValue = (instanceType.isMultiMap()) ? toObject(value) : null; TransactionRecord rec = findTransactionRecord(name, key, matchValue); if (rec == null) { rec = new TransactionRecord(name, key, value, newRecord); rec.timeout = timeout; rec.ttl = ttl; transactionRecords.add(rec); return null; } else { Data old = rec.value; rec.value = value; rec.removed = false; return old; } } public Data attachRemoveOp(String name, Object key, Data value, boolean newRecord) { return attachRemoveOp(name, key, value, newRecord, 1); } public Data attachRemoveOp(String name, Object key, Data value, boolean newRecord, int valueCount) { Instance.InstanceType instanceType = ConcurrentMapManager.getInstanceType(name); Object matchValue = (instanceType.isMultiMap()) ? toObject(value) : null; TransactionRecord rec = findTransactionRecord(name, key, matchValue); Data oldValue = null; if (rec == null) { rec = new TransactionRecord(name, key, value, newRecord); transactionRecords.add(rec); } else { oldValue = rec.value; rec.value = value; } rec.valueCount = valueCount; rec.removed = true; return oldValue; } public void begin() throws IllegalStateException { if (status == TXN_STATUS_ACTIVE) throw new IllegalStateException("Transaction is already active"); status = TXN_STATUS_ACTIVE; } public void commit() throws IllegalStateException { if (status != TXN_STATUS_ACTIVE) throw new IllegalStateException("Transaction is not active"); status = TXN_STATUS_COMMITTING; try { ThreadContext.get().setCurrentFactory(factory); for (TransactionRecord transactionRecord : transactionRecords) { transactionRecord.commit(); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } finally { finalizeTxn(); status = TXN_STATUS_COMMITTED; } } public void rollback() throws IllegalStateException { if (status == TXN_STATUS_NO_TXN || status == TXN_STATUS_UNKNOWN || status == TXN_STATUS_COMMITTED || status == TXN_STATUS_ROLLED_BACK) throw new IllegalStateException("Transaction is not ready to rollback. Status= " + status); status = TXN_STATUS_ROLLING_BACK; try { ThreadContext.get().setCurrentFactory(factory); final int size = transactionRecords.size(); ListIterator<TransactionRecord> iter = transactionRecords.listIterator(size); while (iter.hasPrevious()) { iter.previous().rollback(); } } catch (Exception e) { logger.log(Level.WARNING, e.getMessage(), e); } finally { finalizeTxn(); status = TXN_STATUS_ROLLED_BACK; } } public boolean containsValue(String name, Object value) { for (TransactionRecord transactionRecord : transactionRecords) { if (transactionRecord.name.equals(name)) { if (!transactionRecord.removed) { if (value.equals(toObject(transactionRecord.value))) return true; } } } return false; } public boolean containsEntry(String name, Object key, Object value) { TransactionRecord transactionRecord = findTransactionRecord(name, key, value); return transactionRecord != null && !transactionRecord.removed; } private TransactionRecord findTransactionRecord(String name, Object key) { for (TransactionRecord transactionRecord : transactionRecords) { if (transactionRecord.name.equals(name)) { if (transactionRecord.key != null) { if (transactionRecord.key.equals(key)) { return transactionRecord; } } } } return null; } private TransactionRecord findTransactionRecord(String name, Object key, Object value) { for (TransactionRecord transactionRecord : transactionRecords) { if (transactionRecord.name.equals(name)) { if (transactionRecord.key != null) { if (transactionRecord.key.equals(key)) { final Object txValue = toObject(transactionRecord.value); if (transactionRecord.instanceType.isMultiMap()) { if (value == null && txValue == null) { return transactionRecord; } else if (value != null && value.equals(txValue)) { return transactionRecord; } } else { if (value == null) { return transactionRecord; } else if (value.equals(txValue)) { return transactionRecord; } } } } } } return null; } public Data get(String name, Object key) { TransactionRecord rec = findTransactionRecord(name, key); if (rec == null) return null; if (rec.removed) return null; rec.lastAccess = System.currentTimeMillis(); return rec.value; } public long getId() { return id; } public int getStatus() { return status; } public boolean has(String name, Object key) { return findTransactionRecord(name, key) != null; } public boolean has(String name, Object key, Object value) { return findTransactionRecord(name, key, value) != null; } public boolean isNew(String name, Object key) { TransactionRecord rec = findTransactionRecord(name, key); return (rec != null && !rec.removed && rec.newRecord); } public boolean isRemoved(String name, Object key) { TransactionRecord rec = findTransactionRecord(name, key); return (rec != null && rec.removed); } public int size(String name) { int size = 0; for (TransactionRecord transactionRecord : transactionRecords) { if (transactionRecord.name.equals(name)) { if (transactionRecord.removed) { if (transactionRecord.instanceType.isSet()) { size--; } else if (!transactionRecord.newRecord) { if (!transactionRecord.instanceType.isQueue()) { size -= transactionRecord.valueCount; } } } else if (transactionRecord.newRecord) { if (transactionRecord.instanceType.isList()) { size += (Integer) toObject(transactionRecord.value); } else { size++; } } } } return size; } public List<Map.Entry> newEntries(String name) { List<Map.Entry> lsEntries = null; for (TransactionRecord transactionRecord : transactionRecords) { if (transactionRecord.name.equals(name)) { if (!transactionRecord.removed) { if (transactionRecord.value != null) { if (transactionRecord.newRecord) { if (lsEntries == null) { lsEntries = new ArrayList<Map.Entry>(2); } lsEntries.add(BaseManager.createSimpleMapEntry(factory, name, transactionRecord.key, transactionRecord.value)); } } } } } return lsEntries; } public void getMulti(String name, Object key, Collection col) { for (TransactionRecord transactionRecord : transactionRecords) { if (transactionRecord.name.equals(name)) { if (key.equals(transactionRecord.key)) { if (!transactionRecord.removed && transactionRecord.newRecord) { col.add(toObject(transactionRecord.value)); } else if (transactionRecord.removed) { if (transactionRecord.value == null) { col.clear(); return; } else { col.remove(toObject(transactionRecord.value)); } } } } } } public Map newKeys(String name) { Map newEntries = null; for (TransactionRecord transactionRecord : transactionRecords) { if (transactionRecord.name.equals(name)) { if (!transactionRecord.removed) { if (transactionRecord.value != null) { if (transactionRecord.newRecord) { if (newEntries == null) { newEntries = new HashMap(); } newEntries.put(transactionRecord.key, transactionRecord.value); } } } } } return newEntries; } @Override public String toString() { return "TransactionImpl [" + id + "] status: " + status; } private void finalizeTxn() { transactionRecords.clear(); status = TXN_STATUS_NO_TXN; ThreadContext.get().finalizeTxn(); } private class TransactionRecord { public String name; public Object key; public Data value; public boolean removed = false; public boolean newRecord = false; public Instance.InstanceType instanceType = null; public long lastAccess = -1; public int valueCount = 1; public long timeout = 0; // for commit public long ttl = -1; public TransactionRecord(String name, Object key, Data value, boolean newRecord) { this.name = name; this.key = key; this.value = value; this.newRecord = newRecord; instanceType = ConcurrentMapManager.getInstanceType(name); } public void commit() { if (instanceType == Instance.InstanceType.QUEUE) commitQueue(); else commitMap(); } public void commitMap() { if (removed) { if (instanceType.isSet()) { ConcurrentMapManager.MRemoveItem mRemoveItem = factory.node.concurrentMapManager.new MRemoveItem(); mRemoveItem.removeItem(name, key); } else if (!newRecord) { if (instanceType.isMap()) { factory.node.concurrentMapManager.new MRemove().remove(name, key, -1); } else if (instanceType.isMultiMap()) { if (value == null) { factory.node.concurrentMapManager.new MRemove().remove(name, key, -1); } else { factory.node.concurrentMapManager.new MRemoveMulti().remove(name, key, value); } } } else { factory.node.concurrentMapManager.new MLock().unlock(name, key, -1); } } else { if (instanceType.isMultiMap()) { factory.node.concurrentMapManager.new MPutMulti().put(name, key, value); } else { factory.node.concurrentMapManager.new MPut().put(name, key, value, -1, ttl, id); } } } public void commitQueue() { if (!removed) { offerAgain(false); } } public void rollback() { if (instanceType == Instance.InstanceType.QUEUE) rollbackQueue(); else rollbackMap(); } public void rollbackMap() { MProxy mapProxy = null; Object proxy = factory.getOrCreateProxyByName(name); if (proxy instanceof MProxy) { mapProxy = (MProxy) proxy; } if (mapProxy != null) mapProxy.unlock(key); } public void rollbackQueue() { if (removed) { factory.node.blockingQueueManager.rollbackPoll(name, key, value); } } private void offerAgain(boolean first) { factory.node.blockingQueueManager.offerCommit(name, key, value); } @Override public String toString() { return "TransactionRecord{" + "instanceType=" + instanceType + ", name='" + name + '\'' + ", key=" + key + ", value=" + value + ", removed=" + removed + ", newRecord=" + newRecord + ", lastAccess=" + lastAccess + ", valueCount=" + valueCount + '}'; } } }