package io.eguan.ibs; /* * #%L * Project eguan * %% * Copyright (C) 2012 - 2017 Oodrive * %% * 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. * #L% */ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.concurrent.GuardedBy; import com.carrotsearch.hppc.IntObjectOpenHashMap; /** * Handles {@link Ibs} transactions. Stores the pending blocks in memory. The associated {@link Ibs} must consider the * transaction id <code>Integer.MIN_VALUE</code> as the commit of a key/value pair. * * @author oodrive * @author llambert * */ final class IbsMemTransaction { private static enum FakeTxOperationCode { PUT, REPLACE; } private static final class FakeTxOperation { final FakeTxOperationCode opCode; final byte[] oldKey; final byte[] newKey; final ByteBuffer data; final int offset; final int length; FakeTxOperation(final FakeTxOperationCode opCode, final byte[] oldKey, final byte[] newKey, final ByteBuffer data, final int offset, final int length) { super(); this.opCode = opCode; this.oldKey = oldKey; this.newKey = newKey; // Defensive copy of the buffer final ByteBuffer dataTmp = data.duplicate(); if (dataTmp.isDirect()) this.data = ByteBuffer.allocateDirect(dataTmp.capacity()); else this.data = ByteBuffer.allocate(dataTmp.capacity()); dataTmp.position(0).limit(dataTmp.capacity()); this.data.put(dataTmp); this.data.clear(); this.offset = offset; this.length = length; } } // TODO: handle overflow private final AtomicInteger nextTxId = new AtomicInteger(1); @GuardedBy(value = "transactions") private final IntObjectOpenHashMap<List<FakeTxOperation>> transactions = new IntObjectOpenHashMap<>(); private final Ibs ibs; IbsMemTransaction(final Ibs ibs) { super(); this.ibs = ibs; } final int createTransaction() { final int txId = nextTxId.getAndIncrement(); assert txId > 0; // New operation list synchronized (transactions) { transactions.put(txId, new ArrayList<FakeTxOperation>()); } return txId; } final void put(final int txId, final byte[] key, final ByteBuffer data, final int offset, final int length) throws IbsException, IbsIOException, IllegalArgumentException, IndexOutOfBoundsException, NullPointerException { if (txId <= 0) { throw new IllegalArgumentException("txId=" + txId); } final List<FakeTxOperation> operations; synchronized (transactions) { operations = transactions.get(txId); } if (operations == null) { throw new IbsIOException(IbsErrorCode.INVALID_TRANSACTION_ID); } // Add new operation synchronized (operations) { operations.add(new FakeTxOperation(FakeTxOperationCode.PUT, null, key, data, offset, length)); } } final void replace(final int txId, final byte[] oldKey, final byte[] newKey, final ByteBuffer data, final int offset, final int length) throws IbsException, IllegalArgumentException, IbsIOException, IndexOutOfBoundsException, NullPointerException { if (txId <= 0) { throw new IllegalArgumentException("txId=" + txId); } final List<FakeTxOperation> operations; synchronized (transactions) { operations = transactions.get(txId); } if (operations == null) { throw new IbsIOException(IbsErrorCode.INVALID_TRANSACTION_ID); } // Add new operation synchronized (operations) { operations.add(new FakeTxOperation(FakeTxOperationCode.REPLACE, oldKey, newKey, data, offset, length)); } } final void commit(final int txId) throws IbsException, IllegalArgumentException, IbsIOException { if (txId <= 0) { throw new IllegalArgumentException("txId=" + txId); } final List<FakeTxOperation> operations; synchronized (transactions) { operations = transactions.remove(txId); } if (operations == null) { throw new IbsIOException(IbsErrorCode.INVALID_TRANSACTION_ID); } // Apply changes for (final FakeTxOperation fakeTxOperation : operations) { if (fakeTxOperation.opCode == FakeTxOperationCode.PUT) { ibs.put(Integer.MIN_VALUE, fakeTxOperation.newKey, fakeTxOperation.data, fakeTxOperation.offset, fakeTxOperation.length); } else if (fakeTxOperation.opCode == FakeTxOperationCode.REPLACE) { ibs.replace(Integer.MIN_VALUE, fakeTxOperation.oldKey, fakeTxOperation.newKey, fakeTxOperation.data, fakeTxOperation.offset, fakeTxOperation.length); } else { throw new AssertionError("opcode=" + fakeTxOperation.opCode); } } } final void rollback(final int txId) throws IbsException, IllegalArgumentException, IbsIOException { if (txId <= 0) { throw new IllegalArgumentException("txId=" + txId); } final List<FakeTxOperation> operations; synchronized (transactions) { operations = transactions.remove(txId); } if (operations == null) { throw new IbsIOException(IbsErrorCode.INVALID_TRANSACTION_ID); } } /** * Roll back all the pending transactions. */ final void clear() { synchronized (transactions) { transactions.clear(); } } }