Package kr.co.vcnc.haeinsa

Source Code of kr.co.vcnc.haeinsa.HaeinsaTable$ClientScanner

/**
* Copyright (C) 2013-2014 VCNC Inc.
*
* 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 kr.co.vcnc.haeinsa;

import static kr.co.vcnc.haeinsa.HaeinsaConstants.LOCK_FAMILY;
import static kr.co.vcnc.haeinsa.HaeinsaConstants.LOCK_QUALIFIER;
import static kr.co.vcnc.haeinsa.HaeinsaConstants.RECOVER_MAX_RETRY_COUNT;
import static kr.co.vcnc.haeinsa.HaeinsaConstants.ROW_LOCK_VERSION;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;

import javax.annotation.Nullable;

import kr.co.vcnc.haeinsa.exception.ConflictException;
import kr.co.vcnc.haeinsa.exception.NotExpiredYetException;
import kr.co.vcnc.haeinsa.exception.RecoverableConflictException;
import kr.co.vcnc.haeinsa.thrift.TRowLocks;
import kr.co.vcnc.haeinsa.thrift.generated.TCellKey;
import kr.co.vcnc.haeinsa.thrift.generated.TKeyValue;
import kr.co.vcnc.haeinsa.thrift.generated.TMutation;
import kr.co.vcnc.haeinsa.thrift.generated.TRowKey;
import kr.co.vcnc.haeinsa.thrift.generated.TRowLock;
import kr.co.vcnc.haeinsa.thrift.generated.TRowLockState;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.KeyValue.Type;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.ColumnRangeFilter;
import org.apache.hadoop.hbase.util.Bytes;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Implementation of {@link HaeinsaTableIface}. It works with
* {@link HaeinsaTransaction} to provide transaction on HBase.
*/
public class HaeinsaTable implements HaeinsaTableIfaceInternal {
    private static final Logger LOGGER = LoggerFactory.getLogger(HaeinsaTable.class);
    private final HTableInterface table;

    public HaeinsaTable(HTableInterface table) {
        this.table = table;
    }

    @Override
    public byte[] getTableName() {
        return table.getTableName();
    }

    @Override
    public Configuration getConfiguration() {
        return table.getConfiguration();
    }

    @Override
    public HTableDescriptor getTableDescriptor() throws IOException {
        return table.getTableDescriptor();
    }

    /**
     * Get data from HBase without transaction.
     * {@link HaeinsaTransaction#commit()} to check or mutate lock column of the row scanned by this method.
     * This method can be used when read performance is important or strict consistency of the result is not matter.
     *
     * @param get
     * @return
     * @throws IOException
     */
    private HaeinsaResult getWithoutTx(HaeinsaGet get) throws IOException {
        Get hGet = new Get(get.getRow());
        for (Entry<byte[], NavigableSet<byte[]>> entry : get.getFamilyMap().entrySet()) {
            if (entry.getValue() == null) {
                hGet.addFamily(entry.getKey());
            } else {
                for (byte[] qualifier : entry.getValue()) {
                    hGet.addColumn(entry.getKey(), qualifier);
                }
            }
        }
        Result result = table.get(hGet);
        return new HaeinsaResult(result);
    }

    @Override
    public HaeinsaResult get(@Nullable HaeinsaTransaction tx, HaeinsaGet get) throws IOException {
        Preconditions.checkNotNull(get);
        if (tx == null) {
            return getWithoutTx(get);
        }

        byte[] row = get.getRow();
        HaeinsaTableTransaction tableState = tx.createOrGetTableState(this.table.getTableName());
        HaeinsaRowTransaction rowState = tableState.getRowStates().get(row);
        boolean lockInclusive = false;
        Get hGet = new Get(get.getRow());
        hGet.setCacheBlocks(get.getCacheBlocks());

        for (Entry<byte[], NavigableSet<byte[]>> entry : get.getFamilyMap().entrySet()) {
            if (entry.getValue() == null) {
                hGet.addFamily(entry.getKey());
            } else {
                for (byte[] qualifier : entry.getValue()) {
                    hGet.addColumn(entry.getKey(), qualifier);
                }
            }
        }

        if (rowState == null) {
            if (hGet.hasFamilies()) {
                hGet.addColumn(LOCK_FAMILY, LOCK_QUALIFIER);
            }
            lockInclusive = true;
        }

        Result result = table.get(hGet);
        List<HaeinsaKeyValueScanner> scanners = Lists.newArrayList();
        if (rowState != null) {
            scanners.addAll(rowState.getScanners());
        }
        scanners.add(new HBaseGetScanner(result, Long.MAX_VALUE));

        HaeinsaResult hResult = null;
        // Scanners at this moment is:
        // union( muationScanners from RowTransaction, Scanner of get)
        try (ClientScanner scanner = new ClientScanner(tx, scanners, get.getFamilyMap(), lockInclusive)) {
            hResult = scanner.next();
        }
        if (hResult == null) {
            /*
             * if specific row is empty and there was no puts at all, initialize ClientScanner make empty scanners
             * variable.
             * There will be no rowState associated to the row, and transaction will not operate normally.
             * Therefore, create rowState if there was no HBase operation accessed to the row before.
             */
            rowState = tableState.createOrGetRowState(row);
            if (rowState.getCurrent() == null) {
                rowState.setCurrent(TRowLocks.deserialize(null));
            }

            List<HaeinsaKeyValue> emptyList = Collections.emptyList();
            hResult = new HaeinsaResult(emptyList);
        }
        return hResult;
    }

    @Override
    public HaeinsaResultScanner getScanner(@Nullable HaeinsaTransaction tx, byte[] family) throws IOException {
        Preconditions.checkNotNull(family);

        HaeinsaScan scan = new HaeinsaScan();
        scan.addFamily(family);
        return getScanner(tx, scan);
    }

    @Override
    public HaeinsaResultScanner getScanner(@Nullable HaeinsaTransaction tx, byte[] family, byte[] qualifier)
            throws IOException {
        Preconditions.checkNotNull(family);
        Preconditions.checkNotNull(qualifier);

        HaeinsaScan scan = new HaeinsaScan();
        scan.addColumn(family, qualifier);
        return getScanner(tx, scan);
    }

    /**
     * Haeinsa implementation of {@link Scan}.
     * Scan range of row inside defined by {@link HaeinsaScan} in the context of transaction(tx).
     * Return {@link #ClientScanner} which related to {@link HaeinsaTable} and {@link HaeinsaTransaction}.
     * <p>
     * Return {@link SimpleClientScanner} which do not support transaction if tx is null.
     * User can use this feature if specific scan operation does not require strong consistency
     * or high performance is crucial because scan too wide range of rows.
     */
    @Override
    public HaeinsaResultScanner getScanner(@Nullable HaeinsaTransaction tx, HaeinsaScan scan) throws IOException {
        Preconditions.checkNotNull(scan);
        if (tx == null) {
            return getScannerWithoutTx(scan);
        }

        Scan hScan = new Scan(scan.getStartRow(), scan.getStopRow());
        hScan.setCaching(scan.getCaching());
        hScan.setCacheBlocks(scan.getCacheBlocks());

        for (Entry<byte[], NavigableSet<byte[]>> entry : scan.getFamilyMap().entrySet()) {
            if (entry.getValue() == null) {
                hScan.addFamily(entry.getKey());
            } else {
                for (byte[] qualifier : entry.getValue()) {
                    hScan.addColumn(entry.getKey(), qualifier);
                }
            }
        }
        if (hScan.hasFamilies()) {
            hScan.addColumn(LOCK_FAMILY, LOCK_QUALIFIER);
        }

        HaeinsaTableTransaction tableState = tx.createOrGetTableState(getTableName());
        NavigableMap<byte[], HaeinsaRowTransaction> rows;

        if (Bytes.equals(scan.getStartRow(), HConstants.EMPTY_START_ROW)) {
            if (Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)) {
                // null, null
                rows = tableState.getRowStates();
            } else {
                // null, StopRow
                rows = tableState.getRowStates().headMap(scan.getStopRow(), false);
            }
        } else {
            if (Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)) {
                // StartRow, null
                rows = tableState.getRowStates().tailMap(scan.getStartRow(), true);
            } else {
                // StartRow, StopRow
                rows = tableState.getRowStates().subMap(scan.getStartRow(), true, scan.getStopRow(), false);
            }
        }

        List<HaeinsaKeyValueScanner> scanners = Lists.newArrayList();

        for (HaeinsaRowTransaction rowTx : rows.values()) {
            scanners.addAll(rowTx.getScanners());
        }
        scanners.add(new HBaseScanScanner(table.getScanner(hScan)));

        // Scanners at this moment is:
        // union( muationScanners from all RowTransactions, Scanner of scan )
        return new ClientScanner(tx, scanners, scan.getFamilyMap(), true);
    }

    /**
     * Scan data from HBase without transaction.
     * {@link HaeinsaTransaction#commit()} to check or mutate lock column of the row scanned by this method.
     * This method can be used when read performance is important or strict consistency of the result is not matter.
     *
     * @param scan
     * @return
     * @throws IOException IOException from HBase.
     */
    private HaeinsaResultScanner getScannerWithoutTx(HaeinsaScan scan) throws IOException {
        Scan hScan = new Scan(scan.getStartRow(), scan.getStopRow());
        hScan.setCaching(scan.getCaching());
        hScan.setCacheBlocks(scan.getCacheBlocks());

        for (Entry<byte[], NavigableSet<byte[]>> entry : scan.getFamilyMap().entrySet()) {
            if (entry.getValue() == null) {
                hScan.addFamily(entry.getKey());
            } else {
                for (byte[] qualifier : entry.getValue()) {
                    hScan.addColumn(entry.getKey(), qualifier);
                }
            }
        }
        final ResultScanner scanner = table.getScanner(hScan);
        return new SimpleClientScanner(scanner);
    }

    /**
     * Haeinsa implementation of {@link ColumnRangeFilter}.
     * Scan range of column inside single row defined by {@link HaeinsaIntraScan} in the context of transaction(tx).
     * Return {@link #ClientScanner} which related to {@link HaeinsaTable} and {@link HaeinsaTransaction}.
     * <p>
     * Return {@link SimpleClientScanner} which do not support transaction if tx is null.
     * User can use this feature if specific intra-scan operation does not require strong consistency.
     * <p>
     * Haeinsa does not support column-range scan over multiple row in this version.
     */
    @Override
    public HaeinsaResultScanner getScanner(@Nullable HaeinsaTransaction tx, HaeinsaIntraScan intraScan)
            throws IOException {
        Preconditions.checkNotNull(intraScan);
        if (tx == null) {
            return getScannerWithoutTx(intraScan);
        }

        // scan from startRow ( inclusive ) to startRow + 0x00 ( exclusive )
        Scan hScan = new Scan(intraScan.getRow(), Bytes.add(intraScan.getRow(), new byte[] { 0x00 }));
        hScan.setBatch(intraScan.getBatch());
        hScan.setCacheBlocks(intraScan.getCacheBlocks());

        for (byte[] family : intraScan.getFamilies()) {
            hScan.addFamily(family);
        }

        ColumnRangeFilter rangeFilter = new ColumnRangeFilter(
                intraScan.getMinColumn(), intraScan.isMinColumnInclusive(),
                intraScan.getMaxColumn(), intraScan.isMaxColumnInclusive());
        hScan.setFilter(rangeFilter);

        HaeinsaTableTransaction tableState = tx.createOrGetTableState(getTableName());
        HaeinsaRowTransaction rowState = tableState.getRowStates().get(intraScan.getRow());
        if (rowState == null) {
            rowState = checkOrRecoverLock(tx, intraScan.getRow(), tableState, rowState);
        }

        List<HaeinsaKeyValueScanner> scanners = Lists.newArrayList();
        if (rowState != null) {
            scanners.addAll(rowState.getScanners());
        }
        scanners.add(new HBaseScanScanner(table.getScanner(hScan)));

        // scanners at this moment is:
        // union( muationScanners from RowTransaction, Scanner of intraScan )
        return new ClientScanner(tx, scanners, hScan.getFamilyMap(), intraScan, false);
    }

    /**
     * Scan data inside single row (intraScan) without transaction.
     * {@link HaeinsaTransaction#commit()} to check or mutate lock column of the row scanned by this method.
     * This method can be used when read performance is important or strict consistency of the result is not matter.
     *
     * @param intraScan
     * @return
     * @throws IOException IOException from HBase.
     */
    private HaeinsaResultScanner getScannerWithoutTx(HaeinsaIntraScan intraScan) throws IOException {
        Scan hScan = new Scan(intraScan.getRow(), Bytes.add(intraScan.getRow(), new byte[] { 0x00 }));
        hScan.setBatch(intraScan.getBatch());

        for (byte[] family : intraScan.getFamilies()) {
            hScan.addFamily(family);
        }

        ColumnRangeFilter rangeFilter = new ColumnRangeFilter(
                intraScan.getMinColumn(), intraScan.isMinColumnInclusive(),
                intraScan.getMaxColumn(), intraScan.isMaxColumnInclusive());
        hScan.setFilter(rangeFilter);

        final ResultScanner scanner = table.getScanner(hScan);
        return new SimpleClientScanner(scanner);
    }

    @Override
    public void put(HaeinsaTransaction tx, HaeinsaPut put) throws IOException {
        Preconditions.checkNotNull(tx);
        Preconditions.checkNotNull(put);

        byte[] row = put.getRow();
        HaeinsaTableTransaction tableState = tx.createOrGetTableState(this.table.getTableName());
        HaeinsaRowTransaction rowState = tableState.getRowStates().get(row);
        if (rowState == null) {
            // TODO(improvement) : Should consider to get lock when commit() called.
            rowState = checkOrRecoverLock(tx, row, tableState, rowState);
        }
        rowState.addMutation(put);
    }

    /**
     * Check rowState and whether it contains {@link TRowLock} already, create one if not.
     * <p>
     * Get {@link TRowLock} of the row from HBase if rowState does not contains it.
     * If lock is not in stable state, try to recover it first by {@link HaeinsaTrasaction#recover()}.
     * <p>
     * By calling this method proper time, {@link RowTransaction} inside {@link HaeinsaTransaction} can have
     * {@link TRowLock} of the row when this method was called first time in the context of the transaction.
     *
     * @param tx
     * @param row
     * @param tableState
     * @param rowState
     * @return
     * @throws IOException ConflictException, HBase IOException
     */
    private HaeinsaRowTransaction checkOrRecoverLock(HaeinsaTransaction tx,
            byte[] row, HaeinsaTableTransaction tableState,
            @Nullable HaeinsaRowTransaction rowState) throws IOException {
        if (rowState != null && rowState.getCurrent() != null) {
            // return rowState itself if rowState already exist and contains TRowLock
            return rowState;
        }
        int recoverCount = 0;
        while (true) {
            if (recoverCount > RECOVER_MAX_RETRY_COUNT) {
                throw new ConflictException("recover retry count is exceeded.");
            }
            TRowLock currentRowLock = getRowLock(row);
            try {
                if (checkAndIsShouldRecover(currentRowLock)) {
                    recover(tx, row);
                    recoverCount++;
                } else {
                    rowState = tableState.createOrGetRowState(row);
                    rowState.setCurrent(currentRowLock);
                    break;
                }
            } catch (NotExpiredYetException e) {
                recoverCount++;
                if (recoverCount > RECOVER_MAX_RETRY_COUNT) {
                    throw e;
                }
            }
        }
        return rowState;
    }

    /**
     * Check whether specific row need recover by checking {@link TRowLock}.
     * Return true only if rowLock is NOT in {@link TRowLockState#STABLE} state and lock is expired.
     * Return false if rowLock is in {@link TRowLockState#STABLE} state.
     * Throw {@link ConflictException} if rowLock is not in stable state and not expired yet.
     *
     * @param rowLock
     * @return true - when lock is established but expired. / false - when there
     *         is no lock ( {@link TRowLockState#STABLE} )
     * @throws IOException {@link NotExpiredYetException} if lock is established and
     *         not expired.
     */
    private boolean checkAndIsShouldRecover(TRowLock rowLock) throws IOException {
        if (rowLock.getState() != TRowLockState.STABLE) {
            if (rowLock.isSetExpiry() && rowLock.getExpiry() < System.currentTimeMillis()) {
                return true;
            }
            throw new NotExpiredYetException("this row is unstable and not expired yet.");
        }
        return false;
    }

    /**
     * Call {@link HaeinsaTransaction#recover()}.
     * Abort or recover when there is failed transaction on the row,
     * throw {@link ConflictException} when there is ongoing transaction.
     *
     * @param tx
     * @param row
     * @throws IOException ConflictException, HBase IOException
     */
    private void recover(HaeinsaTransaction tx, byte[] row) throws IOException {
        HaeinsaTransaction previousTx = tx.getManager().getTransaction(getTableName(), row);
        if (previousTx != null) {
            try {
                // there is an unstable transaction in this row.
                previousTx.recover(false);
            } catch (RecoverableConflictException e) {
                LOGGER.warn(e.getMessage(), e);
            }
        }
    }

    @Override
    public void put(HaeinsaTransaction tx, List<HaeinsaPut> puts) throws IOException {
        Preconditions.checkNotNull(tx);
        Preconditions.checkNotNull(puts);

        for (HaeinsaPut put : puts) {
            put(tx, put);
        }
    }

    @Override
    public void delete(HaeinsaTransaction tx, HaeinsaDelete delete) throws IOException {
        Preconditions.checkNotNull(tx);
        Preconditions.checkNotNull(delete);

        byte[] row = delete.getRow();
        // Can't delete entire row in Haeinsa because of lock column. Please specify column families when needed.
        Preconditions.checkArgument(delete.getFamilyMap().size() > 0, "can't delete an entire row.");
        HaeinsaTableTransaction tableState = tx.createOrGetTableState(this.table.getTableName());
        HaeinsaRowTransaction rowState = tableState.getRowStates().get(row);
        if (rowState == null) {
            // TODO(improvement) : Should consider to get lock when commit() called.
            rowState = checkOrRecoverLock(tx, row, tableState, rowState);
        }
        rowState.addMutation(delete);
    }

    @Override
    public void delete(HaeinsaTransaction tx, List<HaeinsaDelete> deletes) throws IOException {
        Preconditions.checkNotNull(tx);
        Preconditions.checkNotNull(deletes);

        for (HaeinsaDelete delete : deletes) {
            delete(tx, delete);
        }
    }

    @Override
    public void close() throws IOException {
        table.close();
    }

    @Override
    public void commitSingleRowPutOnly(HaeinsaRowTransaction rowState, byte[] row) throws IOException {
        HaeinsaTransaction tx = rowState.getTableTransaction().getTransaction();
        Put put = new Put(row);
        HaeinsaPut haeinsaPut = (HaeinsaPut) rowState.getMutations().remove(0);
        for (HaeinsaKeyValue kv : Iterables.concat(haeinsaPut.getFamilyMap().values())) {
            put.add(kv.getFamily(), kv.getQualifier(), tx.getCommitTimestamp(), kv.getValue());
        }
        TRowLock newRowLock = new TRowLock(ROW_LOCK_VERSION, TRowLockState.STABLE, tx.getCommitTimestamp());
        put.add(LOCK_FAMILY, LOCK_QUALIFIER, tx.getCommitTimestamp(), TRowLocks.serialize(newRowLock));

        byte[] currentRowLockBytes = TRowLocks.serialize(rowState.getCurrent());
        if (!table.checkAndPut(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, put)) {
            throw new ConflictException("can't acquire row's lock, commitSingleRowPutOnly failed");
        } else {
            rowState.setCurrent(newRowLock);
        }
    }

    /**
     * Read {@link TRowLock} from HBase and compare that lock with prevRowLock.
     * If TRowLock is changed, it means transaction is failed, so throw
     * {@link ConflictException}.
     *
     * @param prevRowLock
     * @param row
     * @throws IOException ConflictException, HBase IOException.
     * @throws NullPointException if oldLock is null (haven't read lock from
     *         HBase)
     */
    @Override
    public void checkSingleRowLock(HaeinsaRowTransaction rowState, byte[] row) throws IOException {
        TRowLock currentRowLock = getRowLock(row);
        if (!rowState.getCurrent().equals(currentRowLock)) {
            HaeinsaTransaction tx = rowState.getTableTransaction().getTransaction();
            HaeinsaTransaction currentTx = tx.getManager().getTransaction(tx.getPrimary().getTableName(), tx.getPrimary().getRow());
            if (currentTx != null) {
                currentTx.recover(true);
            }
            throw new ConflictException("this row is modified, checkSingleRow failed");
        }
    }

    @Override
    public void prewrite(HaeinsaRowTransaction rowState, byte[] row, boolean isPrimary) throws IOException {
        Put put = new Put(row);
        Set<TCellKey> prewritten = Sets.newTreeSet();
        // order of remaining as TRemove, TPut, TRemove, TPut, ...
        List<TMutation> remaining = Lists.newArrayList();
        HaeinsaTransaction tx = rowState.getTableTransaction().getTransaction();
        if (rowState.getMutations().size() > 0) {
            if (rowState.getMutations().get(0) instanceof HaeinsaPut) {
                HaeinsaPut haeinsaPut = (HaeinsaPut) rowState.getMutations().remove(0);
                for (HaeinsaKeyValue kv : Iterables.concat(haeinsaPut.getFamilyMap().values())) {
                    put.add(kv.getFamily(), kv.getQualifier(), tx.getPrewriteTimestamp(), kv.getValue());
                    TCellKey cellKey = new TCellKey();
                    cellKey.setFamily(kv.getFamily());
                    cellKey.setQualifier(kv.getQualifier());
                    prewritten.add(cellKey);
                }
            }
            for (HaeinsaMutation mutation : rowState.getMutations()) {
                remaining.add(mutation.toTMutation());
            }
        }

        TRowLock newRowLock = new TRowLock(ROW_LOCK_VERSION, TRowLockState.PREWRITTEN,
                tx.getCommitTimestamp()).setCurrentTimestamp(tx.getPrewriteTimestamp());
        if (isPrimary) {
            // for primary row
            for (Entry<TRowKey, HaeinsaRowTransaction> rowStateEntry : tx.getMutationRowStates().entrySet()) {
                TRowKey rowKey = rowStateEntry.getKey();
                if (Bytes.equals(rowKey.getTableName(), getTableName()) && Bytes.equals(rowKey.getRow(), row)) {
                    // if this is primaryRow
                    continue;
                }
                newRowLock.addToSecondaries(new TRowKey().setTableName(rowKey.getTableName()).setRow(rowKey.getRow()));
            }
        } else {
            // for secondary rows
            newRowLock.setPrimary(tx.getPrimary());
        }

        newRowLock.setPrewriteTimestamp(tx.getPrewriteTimestamp());
        newRowLock.setPrewritten(Lists.newArrayList(prewritten));
        newRowLock.setMutations(remaining);
        newRowLock.setExpiry(tx.getExpiry());
        put.add(LOCK_FAMILY, LOCK_QUALIFIER, tx.getPrewriteTimestamp(), TRowLocks.serialize(newRowLock));

        byte[] currentRowLockBytes = TRowLocks.serialize(rowState.getCurrent());

        if (!table.checkAndPut(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, put)) {
            // Consider as conflict because another transaction might acquire lock of this row.
            HaeinsaTransaction currentTx = tx.getManager().getTransaction(tx.getPrimary().getTableName(), tx.getPrimary().getRow());
            if (currentTx != null) {
                currentTx.recover(true);
            }
            throw new ConflictException("can't acquire row's lock");
        } else {
            rowState.setCurrent(newRowLock);
        }
    }

    @Override
    public void applyMutations(HaeinsaRowTransaction rowTxState, byte[] row) throws IOException {
        if (rowTxState.getCurrent().getMutationsSize() == 0) {
            // If this row does not have any left mutations to apply.
            return;
        }

        List<TMutation> remaining = Lists.newArrayList(rowTxState.getCurrent().getMutations());
        long currentTimestamp = rowTxState.getCurrent().getCurrentTimestamp();
        final HaeinsaTransaction tx = rowTxState.getTableTransaction().getTransaction();

        for (int i = 0; i < remaining.size(); i++) {
            byte[] currentRowLockBytes = TRowLocks.serialize(rowTxState.getCurrent());
            int mutationOffset = i + 1;
            long mutationTimestamp = currentTimestamp + mutationOffset;

            TMutation mutation = remaining.get(i);
            switch (mutation.getType()) {
            case PUT: {
                TRowLock newRowLock = rowTxState.getCurrent().deepCopy();
                newRowLock.setCurrentTimestamp(mutationTimestamp);
                newRowLock.setMutations(remaining.subList(mutationOffset, remaining.size()));
                // Maintain prewritten state and extend lock by ROW_LOCK_TIMEOUT
                newRowLock.setExpiry(tx.getExpiry());
                Put put = new Put(row);
                put.add(LOCK_FAMILY, LOCK_QUALIFIER, newRowLock.getCurrentTimestamp(), TRowLocks.serialize(newRowLock));
                for (TKeyValue kv : mutation.getPut().getValues()) {
                    put.add(kv.getKey().getFamily(), kv.getKey().getQualifier(), newRowLock.getCurrentTimestamp(), kv.getValue());
                }
                if (!table.checkAndPut(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, put)) {
                    // Consider as conflict because another transaction might acquire lock of this row.
                    throw new ConflictException("can't acquire row's lock");
                } else {
                    rowTxState.setCurrent(newRowLock);
                }
                break;
            }
            case REMOVE: {
                Delete delete = new Delete(row);
                if (mutation.getRemove().getRemoveFamiliesSize() > 0) {
                    for (ByteBuffer removeFamily : mutation.getRemove().getRemoveFamilies()) {
                        delete.deleteFamily(removeFamily.array(), mutationTimestamp);
                    }
                }
                if (mutation.getRemove().getRemoveCellsSize() > 0) {
                    for (TCellKey removeCell : mutation.getRemove().getRemoveCells()) {
                        delete.deleteColumns(removeCell.getFamily(), removeCell.getQualifier(), mutationTimestamp);
                    }
                }
                if (!table.checkAndDelete(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, delete)) {
                    // Consider as conflict because another transaction might acquire lock of this row.
                    throw new ConflictException("can't acquire row's lock");
                }
                break;
            }
            default:
                break;
            }
        }
    }

    @Override
    public void makeStable(HaeinsaRowTransaction rowTxState, byte[] row) throws IOException {
        byte[] currentRowLockBytes = TRowLocks.serialize(rowTxState.getCurrent());
        HaeinsaTransaction transaction = rowTxState.getTableTransaction().getTransaction();
        long commitTimestamp = transaction.getCommitTimestamp();
        TRowLock newRowLock = new TRowLock(ROW_LOCK_VERSION, TRowLockState.STABLE, commitTimestamp);
        byte[] newRowLockBytes = TRowLocks.serialize(newRowLock);
        Put put = new Put(row);
        put.add(LOCK_FAMILY, LOCK_QUALIFIER, commitTimestamp, newRowLockBytes);

        if (!table.checkAndPut(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, put)) {
            // Consider as success because another transaction might already stabilize this row.
            throw new RecoverableConflictException("can't make stable");
        } else {
            rowTxState.setCurrent(newRowLock);
        }
    }

    @Override
    public void commitPrimary(HaeinsaRowTransaction rowTxState, byte[] row) throws IOException {
        byte[] currentRowLockBytes = TRowLocks.serialize(rowTxState.getCurrent());
        HaeinsaTransaction transaction = rowTxState.getTableTransaction().getTransaction();
        long commitTimestamp = transaction.getCommitTimestamp();
        TRowLock newRowLock = rowTxState.getCurrent().deepCopy();
        newRowLock.setCommitTimestamp(commitTimestamp);
        newRowLock.setState(TRowLockState.COMMITTED);
        // if tx is already committed, we can't change current timestamp.
        if (rowTxState.getCurrent().getState() != TRowLockState.COMMITTED) {
            newRowLock.setCurrentTimestamp(newRowLock.getCurrentTimestamp() + 1);
        }
        // make prewritten to null
        newRowLock.setPrewrittenIsSet(false);
        // extend expiry by ROW_LOCK_TIMEOUT
        newRowLock.setExpiry(transaction.getExpiry());

        byte[] newRowLockBytes = TRowLocks.serialize(newRowLock);
        Put put = new Put(row);
        put.add(LOCK_FAMILY, LOCK_QUALIFIER, newRowLock.getCurrentTimestamp(), newRowLockBytes);

        if (!table.checkAndPut(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, put)) {
            // We don't need abort current transaction. Because the transaction is already aborted.
            // Consider as conflict because another transaction might acquire lock of primary row.
            throw new ConflictException("can't acquire primary row's lock");
        } else {
            rowTxState.setCurrent(newRowLock);
        }
    }

    @Override
    public TRowLock getRowLock(byte[] row) throws IOException {
        Get get = new Get(row);
        get.addColumn(LOCK_FAMILY, LOCK_QUALIFIER);
        Result result = table.get(get);
        if (result.isEmpty()) {
            return TRowLocks.deserialize(null);
        } else {
            byte[] rowLockBytes = result.getValue(LOCK_FAMILY, LOCK_QUALIFIER);
            return TRowLocks.deserialize(rowLockBytes);
        }
    }

    @Override
    public void abortPrimary(HaeinsaRowTransaction rowTxState, byte[] row) throws IOException {
        byte[] currentRowLockBytes = TRowLocks.serialize(rowTxState.getCurrent());
        HaeinsaTransaction transaction = rowTxState.getTableTransaction().getTransaction();
        long commitTimestamp = transaction.getCommitTimestamp();
        TRowLock newRowLock = rowTxState.getCurrent().deepCopy();
        newRowLock.setCommitTimestamp(commitTimestamp);
        // if tx is already aborted, we can't change current timestamp.
        if (rowTxState.getCurrent().getState() != TRowLockState.ABORTED) {
            newRowLock.setCurrentTimestamp(newRowLock.getCurrentTimestamp() + 1);
        }
        newRowLock.setState(TRowLockState.ABORTED);
        newRowLock.setMutationsIsSet(false);
        newRowLock.setExpiry(transaction.getExpiry());

        byte[] newRowLockBytes = TRowLocks.serialize(newRowLock);
        Put put = new Put(row);
        put.add(LOCK_FAMILY, LOCK_QUALIFIER, newRowLock.getCurrentTimestamp(), newRowLockBytes);

        if (!table.checkAndPut(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, put)) {
            // Consider as conflict because another transaction might acquire lock of primary row.
            throw new ConflictException("can't acquire primary row's lock");
        } else {
            rowTxState.setCurrent(newRowLock);
        }
    }

    @Override
    public void deletePrewritten(HaeinsaRowTransaction rowTxState, byte[] row) throws IOException {
        if (rowTxState.getCurrent().getPrewrittenSize() == 0) {
            // nothing to do
            return;
        }
        byte[] currentRowLockBytes = TRowLocks.serialize(rowTxState.getCurrent());
        long prewriteTimestamp = rowTxState.getCurrent().isSetPrewriteTimestamp() ?
                rowTxState.getCurrent().getPrewriteTimestamp() : rowTxState.getCurrent().getCurrentTimestamp();
        Delete delete = new Delete(row);
        for (TCellKey cellKey : rowTxState.getCurrent().getPrewritten()) {
            delete.deleteColumn(cellKey.getFamily(), cellKey.getQualifier(), prewriteTimestamp);
        }
        if (!table.checkAndDelete(row, LOCK_FAMILY, LOCK_QUALIFIER, currentRowLockBytes, delete)) {
            // Consider as conflict because another transaction might acquire lock of this row.
            throw new ConflictException("can't acquire primary row's lock");
        }
    }

    protected HTableInterface getHTable() {
        return table;
    }

    /**
     * Implementation of {@link HaeinsaReulstScanner} which is used when scan without transaction.
     */
    private class SimpleClientScanner implements HaeinsaResultScanner {
        private final ResultScanner scanner;

        public SimpleClientScanner(ResultScanner scanner) {
            this.scanner = scanner;
        }

        @Override
        public Iterator<HaeinsaResult> iterator() {
            return Iterators.transform(scanner.iterator(), new Function<Result, HaeinsaResult>() {
                @Override
                public HaeinsaResult apply(@Nullable Result result) {
                    return new HaeinsaResult(result);
                }
            });
        }

        @Override
        public HaeinsaResult[] next(int nbRows) throws IOException {
            Result[] resultArray = scanner.next(nbRows);
            HaeinsaResult[] transformed = new HaeinsaResult[resultArray.length];
            for (int i = 0; i < resultArray.length; i++) {
                transformed[i] = new HaeinsaResult(resultArray[i]);
            }
            return transformed;
        }

        @Override
        public HaeinsaResult next() throws IOException {
            Result result = scanner.next();
            if (result != null) {
                return new HaeinsaResult(result);
            } else {
                return null;
            }
        }

        @Override
        public void close() {
            scanner.close();
        }
    }

    /**
     * Contains scanners for single {@link HaeinsaTable} to help project puts/deletes to gets/scans in same transaction.
     * <p>
     * This projection of mutations is crucial to proper execution of transaction.
     * For instance, consider single transaction such as T = {W1(X), R2(X), R3(Y), W4(X)}.
     * Haeinsa does not write mutations on HBase until commit() is called.
     * Therefore if R(X) get data only from HBase, R2(X) on transaction T cannot read data written by W1(X)
     * and this is not expected behavior to programmers.
     * Haeinsa resolves this problem by projecting buffered mutations in client to get/scan operations executed in same transaction.
     */
    private class ClientScanner implements HaeinsaResultScanner {
        private final HaeinsaTransaction tx;
        private final HaeinsaTableTransaction tableState;
        private boolean initialized;
        private final NavigableSet<HaeinsaKeyValueScanner> scanners =
                Sets.newTreeSet(HaeinsaKeyValueScanner.COMPARATOR);
        private final List<HaeinsaKeyValueScanner> scannerList = Lists.newArrayList();
        // tracking delete of one specific row.
        private final HaeinsaDeleteTracker deleteTracker = new HaeinsaDeleteTracker();
        private final HaeinsaColumnTracker columnTracker;

        /**
         * true if TRowLock inside scanners, false if TRowLock is already
         * included inside tableState.getRowStates().get(row)
         */
        private final boolean lockInclusive;

        /**
         * -1 if not used. ( Get / Scan )
         */
        private final int batch;
        private final Map<byte[], NavigableSet<byte[]>> familyMap;
        private HaeinsaKeyValue prevKV;
        private long maxSeqID = Long.MAX_VALUE;

        /**
         *
         * @param tx
         * @param scanners
         * @param familyMap
         * @param lockInclusive - whether scanners contains {@link TRowLock} inside.
         *        If not, should bring from {@link RowTransaction} or get from HBase directly.
         */
        public ClientScanner(HaeinsaTransaction tx, Iterable<HaeinsaKeyValueScanner> scanners,
                Map<byte[], NavigableSet<byte[]>> familyMap, boolean lockInclusive) {
            this(tx, scanners, familyMap, null, lockInclusive);
        }

        /**
         *
         * @param tx
         * @param scanners
         * @param familyMap
         * @param intraScan - To support to use {@link ColumnRangeFilter}
         * @param lockInclusive - whether scanners contains {@link TRowLock} inside.
         *        If not, should bring from {@link RowTransaction} or get from HBase directly.
         */
        public ClientScanner(HaeinsaTransaction tx, Iterable<HaeinsaKeyValueScanner> scanners,
                Map<byte[], NavigableSet<byte[]>> familyMap, HaeinsaIntraScan intraScan, boolean lockInclusive) {
            this.tx = tx;
            this.tableState = tx.createOrGetTableState(getTableName());
            for (HaeinsaKeyValueScanner kvScanner : scanners) {
                scannerList.add(kvScanner);
            }
            if (intraScan == null) {
                intraScan = new HaeinsaIntraScan(null, null, false, null, false);
                intraScan.setBatch(-1);
            }
            this.columnTracker = new HaeinsaColumnTracker(familyMap,
                    intraScan.getMinColumn(), intraScan.isMinColumnInclusive(),
                    intraScan.getMaxColumn(), intraScan.isMaxColumnInclusive());
            this.batch = intraScan.getBatch();
            this.lockInclusive = lockInclusive;
            this.familyMap = familyMap;
        }

        /**
         * Method to generate {@link #scanners} from {@link #scannerList}.
         * Only can be called one time for every ClientScanner.
         * <p>
         * The reason why there are different variables for {@link #scannerList} and {@link #scanners} is that
         * the way {@link #nextScanner()} implemented is removing {@link HaeinsaKeyValueScanner} one by one form scanners.
         * {@link #close()} method needs to close every {@link ClientScanner} when called,
         * so some other variable should preserve every scanner when ClientScanner created.
         *
         * @throws IOException
         */
        private void initialize() throws IOException {
            try {
                for (HaeinsaKeyValueScanner scanner : scannerList) {
                    HaeinsaKeyValue peeked = scanner.peek();
                    if (peeked != null) {
                        scanners.add(scanner);
                    }
                }
                initialized = true;
            } catch (Exception e) {
                throw new IOException(e.getMessage(), e);
            }
        }

        /**
         * Return iterator of {@link ClientScanner}. Use {@link #next()} inside.
         */
        @Override
        public Iterator<HaeinsaResult> iterator() {
            return new Iterator<HaeinsaResult>() {
                // if current is null, whether scan is not started or next() was
                // called.
                // if hasNext() is called, next data will be ready on current.
                private HaeinsaResult current;

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }

                @Override
                public HaeinsaResult next() {
                    if (current == null) {
                        hasNext();
                    }
                    HaeinsaResult result = current;
                    current = null;
                    return result;
                }

                @Override
                public boolean hasNext() {
                    if (current != null) {
                        return true;
                    }
                    try {
                        current = ClientScanner.this.next();
                        if (current != null) {
                            return true;
                        }
                    } catch (IOException e) {
                        // because hasNext() cannot throw IOException, wrap it with RuntimeException.
                        throw new IllegalStateException(e.getMessage(), e);
                    }
                    return false;
                }
            };
        }

        /**
         * Return {@link TRowLock} for specific row from {@link IOException#scanners}.
         * Return null if there is no proper {@link TRowLock}.
         * <p>
         * If one of these {@link #scanners} has row key which is smaller than given row,
         * that {@link HaeinsaKeyValueScanner} is not peeked by this method and return null.
         * So proper operation is guaranteed only when every scanner in {@link #scanners} return
         * smaller or equal row key when {@link HaeinsaKeyValueScanner#peek()} is called.
         *
         * @param row
         * @return null if there is no TRowLock information inside scanners,
         *         return rowLock otherwise.
         * @throws IOException
         */
        private TRowLock peekLock(byte[] row) throws IOException {
            for (HaeinsaKeyValueScanner scanner : scanners) {
                HaeinsaKeyValue kv = scanner.peek();
                if (!Bytes.equals(kv.getRow(), row)) {
                    break;
                }

                TRowLock rowLock = scanner.peekLock();
                if (rowLock != null) {
                    return rowLock;
                }
            }
            return null;
        }

        @Override
        public HaeinsaResult next() throws IOException {
            if (!initialized) {
                // move scannerList -> scanners
                initialize();
            }

            final List<HaeinsaKeyValue> sortedKVs = Lists.newArrayList();
            while (true) {
                if (scanners.isEmpty()) {
                    break;
                }
                HaeinsaKeyValueScanner currentScanner = scanners.first();
                HaeinsaKeyValue currentKV = currentScanner.peek();
                if (prevKV == null) {
                    // start new row, deal with TRowLock and Recover()
                    if (lockInclusive) {
                        // HaeinsaKeyValues from HBaseScanScanner or HBaseGetScanner contains TRowLock for this row.
                        TRowLock currentRowLock = peekLock(currentKV.getRow());
                        HaeinsaRowTransaction rowState = tableState.createOrGetRowState(currentKV.getRow());
                        if (rowState.getCurrent() == null) {
                            // rowState is just created by createOrGetRowState method().
                            // So proper TRowLock value should be set.
                            if (currentRowLock == null) {
                                /*
                                 * HBase do not have TRowLock for this row.
                                 * This is the case when Haeinsa accesses to this row first time.
                                 * (This can be because of lazy-migration from HBase-only code).
                                 *
                                 * HaeinsaRowTransaction can use TRowLock(ROW_LOCK_VERSION,
                                 * TRowLockState.STABLE, Long.MIN_VALUE) as initial TRowLock value.
                                 * This initial TRowLock will be override by proper value and applied to HBase
                                 * when commit() method is called.
                                 */
                                currentRowLock = TRowLocks.deserialize(null);
                                rowState.setCurrent(currentRowLock);
                            }

                            if (checkAndIsShouldRecover(currentRowLock)) {
                                // when currentRowLock is not stable but
                                // expired.
                                rowState = checkOrRecoverLock(tx, currentKV.getRow(), tableState, rowState);
                                Get get = new Get(currentKV.getRow());
                                for (Entry<byte[], NavigableSet<byte[]>> entry : familyMap.entrySet()) {
                                    if (entry.getValue() != null) {
                                        for (byte[] qualifier : entry.getValue()) {
                                            get.addColumn(entry.getKey(), qualifier);
                                        }
                                    } else {
                                        get.addFamily(entry.getKey());
                                    }
                                }
                                Result result = table.get(get);
                                maxSeqID--;
                                HBaseGetScanner getScanner = new HBaseGetScanner(result, maxSeqID);
                                if (getScanner.peek() != null) {
                                    scanners.add(getScanner);
                                }
                                continue;
                            } else {
                                // when currentRowLock is stable
                                rowState.setCurrent(currentRowLock);
                            }
                        } else {
                            // rowState is already exist, use current variable in rowState instead of TRowLock from scan
                            // or get
                        }
                        // At this point, TRowLock of currentKV.getRow() is saved in rowState.
                    }
                    prevKV = currentKV;
                }

                if (Bytes.equals(prevKV.getRow(), currentKV.getRow())) {
                    if (currentScanner.getSequenceID() > maxSeqID) {
                        // too old data, ignore
                    } else if (Bytes.equals(currentKV.getFamily(), LOCK_FAMILY)
                            && Bytes.equals(currentKV.getQualifier(), LOCK_QUALIFIER)) {
                        // if currentKV is Lock, ignore
                    } else if (currentKV.getType() == Type.DeleteColumn || currentKV.getType() == Type.DeleteFamily) {
                        // if currentKV is delete
                        deleteTracker.add(currentKV, currentScanner.getSequenceID());
                    } else if (prevKV == currentKV
                            || !(Bytes.equals(prevKV.getRow(), currentKV.getRow())
                                    && Bytes.equals(prevKV.getFamily(), currentKV.getFamily())
                                    && Bytes.equals(prevKV.getQualifier(), currentKV.getQualifier()))) {
                        // if reference of prevKV and currentKV is same, the currentKV have new row.
                        // Ignore when prevKV and currentKV is different but row, family,
                        // qualifier of currentKv and prevKv is all same. (not likely)
                        if (!deleteTracker.isDeleted(currentKV, currentScanner.getSequenceID())
                                && columnTracker.isMatched(currentKV)) {
                            // if currentKV is not deleted and inside scan range
                            sortedKVs.add(currentKV);
                            prevKV = currentKV;
                        }
                    }
                    nextScanner(currentScanner);
                } else {
                    // currentKV is different row with prevKV, so reset
                    // deleteTracker & maxSeqID
                    deleteTracker.reset();
                    prevKV = null;
                    maxSeqID = Long.MAX_VALUE;
                    if (sortedKVs.size() > 0) {
                        // If currentKV moved to next row and there are more than one KV satisfy scan requirement,
                        // should return HaeinsaResult which aggregate HaeinsaKeyValue for previous row.
                        break;
                    }
                }
                if (batch > 0 && sortedKVs.size() >= batch) {
                    // if intraScan & sortedKVs have more elements than batch.
                    break;
                }
            }
            if (sortedKVs.size() > 0) {
                return new HaeinsaResult(sortedKVs);
            } else {
                // scanners are exhausted.
                return null;
            }
        }

        /**
         * Moving index of scanner of currentScanner by one. If there is no more
         * element at the scanner, remove currentScanner from scanners
         * (NavigableSet)
         *
         * @param currentScanner
         * @throws IOException
         */
        private void nextScanner(HaeinsaKeyValueScanner currentScanner) throws IOException {
            scanners.remove(currentScanner);
            currentScanner.next();
            HaeinsaKeyValue currentScannerNext = currentScanner.peek();
            if (currentScannerNext != null) {
                // if currentScanner is not exhausted.
                scanners.add(currentScanner);
            }
        }

        @Override
        public HaeinsaResult[] next(int nbRows) throws IOException {
            List<HaeinsaResult> result = Lists.newArrayList();
            for (int i = 0; i < nbRows; i++) {
                HaeinsaResult current = this.next();
                if (current != null) {
                    result.add(current);
                } else {
                    break;
                }
            }
            HaeinsaResult[] array = new HaeinsaResult[result.size()];
            return result.toArray(array);
        }

        @Override
        public void close() {
            for (HaeinsaKeyValueScanner scanner : scannerList) {
                scanner.close();
            }
        }
    }

    /**
     * Implementation of {@link HaeinsaKeyValueScanner} which provides scanner interface which wrap
     * {@link KeyValue}s from {@link ResultScanner} by HBase {@link Scan} to {@link HaeinsaKeyValue}s.
     * This class is used if there are {@link HaeinsaScan} or {@link HaeinsaIntraScan} inside transaction.
     * <p>
     * HBaseScanScanner can contain kev-values for single row like {@link ResultScanner},
     * or key-values of multiple rows.
     * <p>
     * HBaseScanScanner does not need sequenceID control because it will use {@link HBaseGetScanner}
     * if rows from scan do not have stable state, and put result from get to lower sequenceID.
     * Therefore, {@link #getSequenceID()} always return Long.MAX_VALUE
     */
    private static class HBaseScanScanner implements HaeinsaKeyValueScanner {
        private final ResultScanner resultScanner;

        /**
         * current is null when scan is not started or next() is called last
         * time.
         */
        private HaeinsaKeyValue current;

        /**
         * currentResult is null when there is no more elements to scan in resultScanner or scan is not started.
         * currentResult only contains result of single row.
         */
        private Result currentResult;

        /**
         * current = currentResult[resultIndex - 1]
         */
        private int resultIndex;

        public HBaseScanScanner(ResultScanner resultScanner) {
            this.resultScanner = resultScanner;
        }

        @Override
        public HaeinsaKeyValue peek() {
            try {
                if (current != null) {
                    return current;
                }
                if (currentResult == null || (currentResult != null && resultIndex >= currentResult.size())) {
                    currentResult = resultScanner.next();
                    if (currentResult != null && currentResult.isEmpty()) {
                        currentResult = null;
                    }
                    resultIndex = 0;
                }
                if (currentResult == null) {
                    // if currentResult is still null at this point, that means
                    // there is no more KV to scan.
                    return null;
                }
                // First scan or next() was called last time so move
                // resultIndex.
                current = new HaeinsaKeyValue(currentResult.raw()[resultIndex]);
                resultIndex++;

                return current;
            } catch (IOException e) {
                // because peek() cannot throw IOException, wrap it with RuntimeException.
                throw new IllegalStateException(e.getMessage(), e);
            }
        }

        @Override
        public HaeinsaKeyValue next() throws IOException {
            HaeinsaKeyValue result = peek();
            current = null;
            return result;
        }

        @Override
        public TRowLock peekLock() throws IOException {
            peek();
            if (currentResult != null) {
                byte[] lock = currentResult.getValue(LOCK_FAMILY, LOCK_QUALIFIER);
                if (lock != null) {
                    return TRowLocks.deserialize(lock);
                }
            }
            return null;
        }

        @Override
        public long getSequenceID() {
            /*
             * Scan always read data from HBase, so sequenceId should be biggest every time.
             */
            return Long.MAX_VALUE;
        }

        @Override
        public void close() {
            resultScanner.close();
        }
    }

    /**
     * Implementation of {@link HaeinsaKeyValueScanner} which provide scanner interface which wrap
     * {@link KeyValue}s from {@link ResultScanner} by HBase {@link Scan} to {@link HaeinsaKeyValue}s.
     * This class is used there is {@link HaeinsaScan} or {@link HaeinsaIntraScan} inside transaction.
     * <p>
     * HBaseScanScanner can contain kev-values for single row like {@link ResultScanner},
     * or key-values of multiple rows.
     * <p>
     * HBaseScanScanner does not need sequenceID control because it will use {@link HBaseGetScanner}
     * if rows from scan do not have stable state, and put result from get to lower sequenceID.
     * Therefore, {@link #getSequenceID()} always return Long.MAX_VALUE
     */

    /**
     * Implementation of {@link HaeinsaKeyValueScanner} which provides scanner interface which wrap
     * {@link KeyValue}s from {@link Result} by HBase {@link Get} to {@link HaeinsaKeyValue}s.
     * This class is used if there is {@link HaeinsaGet} inside transaction.
     * <p>
     * HBaseGetScanner can contain key-values for only single row link {@link Result}.
     * So {@link #peek()} method or {@link #next()} method will return value from same row.
     * <p>
     * HBaseGetScanner is now used in two cases.
     * First, inside {@link HaeinsaTable#get()}, {@link HaeinsaKeyValue}s returned by this class will
     * always have sequenceId of Long.MAX_VALUE.
     * Second is used inside {@link ClientScanner}. In this case, HBaseGetScanner recover failed transaction and
     * get recovered data from HBase.
     * Recovered {@link HaeinsaKeyValue} should have smaller sequenceId contained in ClientScanner so far
     * because it is more recent value.
     */
    private static class HBaseGetScanner implements HaeinsaKeyValueScanner {
        private final long sequenceID;
        private Result result;
        private int resultIndex;
        private HaeinsaKeyValue current;

        public HBaseGetScanner(Result result, final long sequenceID) {
            if (result != null && !result.isEmpty()) {
                this.result = result;
            } else {
                // null if result is empty
                this.result = null;
            }
            // bigger sequenceID is older one.
            this.sequenceID = sequenceID;
        }

        @Override
        public HaeinsaKeyValue peek() {
            if (current != null) {
                return current;
            }
            if (result != null && resultIndex >= result.size()) {
                result = null;
            }
            if (result == null) {
                return null;
            }
            current = new HaeinsaKeyValue(result.list().get(resultIndex));
            resultIndex++;
            return current;
        }

        @Override
        public HaeinsaKeyValue next() throws IOException {
            HaeinsaKeyValue result = peek();
            current = null;
            return result;
        }

        @Override
        public TRowLock peekLock() throws IOException {
            peek();
            if (result != null) {
                byte[] lock = result.getValue(LOCK_FAMILY, LOCK_QUALIFIER);
                if (lock != null) {
                    return TRowLocks.deserialize(lock);
                }
            }
            return null;
        }

        @Override
        public long getSequenceID() {
            return sequenceID;
        }

        @Override
        public void close() {
        }
    }
}
TOP

Related Classes of kr.co.vcnc.haeinsa.HaeinsaTable$ClientScanner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.