org.apache.hadoop.hbase.regionserver.transactional.TrxTransactionState.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.regionserver.transactional.TrxTransactionState.java

Source

/**
 * Copyright 2009 The Apache Software Foundation Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to you 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 org.apache.hadoop.hbase.regionserver.transactional;

import java.io.IOException;

import java.lang.Class;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.codec.binary.Hex;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.Tag;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.KeyValueUtil;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.KeyValue.Type;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.regionserver.wal.HLog;
import org.apache.hadoop.hbase.regionserver.InternalScanner;
import org.apache.hadoop.hbase.regionserver.KeyValueScanner;
import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
import org.apache.hadoop.hbase.regionserver.ScanQueryMatcher;
import org.apache.hadoop.hbase.regionserver.ScanType;
import org.apache.hadoop.hbase.regionserver.ScanInfo;
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.io.DataInputBuffer;

/**
 * Holds the state of a transaction. This includes a buffer of all writes, a record of all reads / scans, and
 * information about which other transactions we need to check against.
 */
public class TrxTransactionState extends TransactionState {

    static boolean sb_sqm_98_1;
    static boolean sb_sqm_98_4;
    static java.lang.reflect.Constructor c98_1 = null;
    static java.lang.reflect.Constructor c98_4 = null;

    static {
        sb_sqm_98_1 = true;
        try {
            NavigableSet<byte[]> lv_nvg = (NavigableSet<byte[]>) null;
            c98_1 = ScanQueryMatcher.class.getConstructor(new Class[] { Scan.class, ScanInfo.class,
                    java.util.NavigableSet.class, ScanType.class, long.class, long.class, long.class });
        } catch (NoSuchMethodException exc_nsm) {
            sb_sqm_98_1 = false;
            sb_sqm_98_4 = true;
            try {
                c98_4 = ScanQueryMatcher.class
                        .getConstructor(new Class[] { Scan.class, ScanInfo.class, java.util.NavigableSet.class,
                                ScanType.class, long.class, long.class, long.class, RegionCoprocessorHost.class });
            } catch (NoSuchMethodException exc_nsm2) {
                sb_sqm_98_4 = false;
            }
        }

        if (sb_sqm_98_1) {
            LOG.info("Got info of Class ScanQueryMatcher for HBase 98.1");
        }
        if (sb_sqm_98_4) {
            LOG.info("Got info of Class ScanQueryMatcher for HBase 98.4");
        }
    }

    /**
     * Simple container of the range of the scanners we've opened. Used to check for conflicting writes.
     */
    private static class ScanRange {

        protected byte[] startRow;
        protected byte[] endRow;

        public ScanRange(final byte[] startRow, final byte[] endRow) {
            this.startRow = startRow == HConstants.EMPTY_START_ROW ? null : startRow;
            this.endRow = endRow == HConstants.EMPTY_END_ROW ? null : endRow;
        }

        /**
         * Check if this scan range contains the given key.
         * 
         * @param rowKey
         * @return boolean
         */
        public boolean contains(final byte[] rowKey) {
            if (startRow != null && Bytes.compareTo(rowKey, startRow) < 0) {
                return false;
            }
            if (endRow != null && Bytes.compareTo(endRow, rowKey) < 0) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            return "startRow: " + (startRow == null ? "null" : Bytes.toStringBinary(startRow)) + ", endRow: "
                    + (endRow == null ? "null" : Bytes.toStringBinary(endRow));
        }
    }

    private List<ScanRange> scans = Collections.synchronizedList(new LinkedList<ScanRange>());
    private List<Delete> deletes = Collections.synchronizedList(new LinkedList<Delete>());
    private List<WriteAction> writeOrdering = Collections.synchronizedList(new LinkedList<WriteAction>());
    private Set<TrxTransactionState> transactionsToCheck = Collections
            .synchronizedSet(new HashSet<TrxTransactionState>());
    private WALEdit e;

    public TrxTransactionState(final long transactionId, final long rLogStartSequenceId, AtomicLong hlogSeqId,
            final HRegionInfo regionInfo, HTableDescriptor htd, HLog hLog, boolean logging) {
        super(transactionId, rLogStartSequenceId, hlogSeqId, regionInfo, htd, hLog, logging);
        this.e = new WALEdit();
    }

    public synchronized void addRead(final byte[] rowKey) {
        scans.add(new ScanRange(rowKey, rowKey));
    }

    public synchronized void addWrite(final Put write) {
        if (LOG.isTraceEnabled())
            LOG.trace("addWrite -- ENTRY: write: " + write.toString());
        WriteAction waction;
        KeyValue kv;
        WALEdit e1 = new WALEdit();
        updateLatestTimestamp(write.getFamilyCellMap().values(), EnvironmentEdgeManager.currentTimeMillis());
        // Adding read scan on a write action
        addRead(new WriteAction(write).getRow());

        ListIterator<WriteAction> writeOrderIter = writeOrdering.listIterator();
        writeOrderIter.add(waction = new WriteAction(write));

        if (this.earlyLogging) { // immeditaely write edit out to HLOG during DML (actve transaction state)
            for (Cell value : waction.getCells()) {
                //KeyValue kv = KeyValueUtil.ensureKeyValue(value);
                kv = KeyValue.cloneAndAddTags(value, tagList);
                //if (LOG.isTraceEnabled()) LOG.trace("KV hex dump " + Hex.encodeHexString(kv.getValueArray() /*kv.getBuffer()*/));
                e1.add(kv);
                e.add(kv);
            }
            try {
                long txid = this.tHLog.appendNoSync(this.regionInfo, this.regionInfo.getTable(), e1,
                        new ArrayList<UUID>(), EnvironmentEdgeManager.currentTimeMillis(), this.tabledescriptor,
                        this.logSeqId, false, HConstants.NO_NONCE, HConstants.NO_NONCE);
                //if (LOG.isTraceEnabled()) LOG.trace("Trafodion Recovery: Y11 write edit to HLOG during put with txid " + txid + " ts flush id " + this.flushTxId);
                if (txid > this.flushTxId)
                    this.flushTxId = txid; // save the log txid into TS object, later sync on largestSeqid during phase 1
            } catch (IOException exp1) {
                LOG.info(
                        "TrxRegionEndpoint coprocessor addWrite writing to HLOG for early logging: Threw an exception");
                //throw exp1;
            }
        } else { // edits are buffered in ts and written out to HLOG in phase 1
            for (Cell value : waction.getCells()) {
                kv = KeyValue.cloneAndAddTags(value, tagList);
                e.add(kv);
            }
        }
        if (LOG.isTraceEnabled())
            LOG.trace("addWrite -- EXIT");
    }

    public boolean hasWrite() {
        return writeOrdering.size() > 0;
    }

    public synchronized void addDelete(final Delete delete) {
        WriteAction waction;
        WALEdit e1 = new WALEdit();
        long now = EnvironmentEdgeManager.currentTimeMillis();
        updateLatestTimestamp(delete.getFamilyCellMap().values(), now);
        if (delete.getTimeStamp() == HConstants.LATEST_TIMESTAMP) {
            delete.setTimestamp(now);
        }
        deletes.add(delete);

        ListIterator<WriteAction> writeOrderIter = writeOrdering.listIterator();
        writeOrderIter.add(waction = new WriteAction(delete));

        if (this.earlyLogging) {
            for (Cell value : waction.getCells()) {
                KeyValue kv = KeyValue.cloneAndAddTags(value, tagList);
                e1.add(kv);
                e.add(kv);
            }
            try {
                long txid = this.tHLog.appendNoSync(this.regionInfo, this.regionInfo.getTable(), e1,
                        new ArrayList<UUID>(), EnvironmentEdgeManager.currentTimeMillis(), this.tabledescriptor,
                        this.logSeqId, false, HConstants.NO_NONCE, HConstants.NO_NONCE);
                //if (LOG.isTraceEnabled()) LOG.trace("Trafodion Recovery: Y00 write edit to HLOG during delete with txid " + txid + " ts flush id " + this.flushTxId);
                if (txid > this.flushTxId)
                    this.flushTxId = txid; // save the log txid into TS object, later sync on largestSeqid during phase 1
            } catch (IOException exp1) {
                LOG.info(
                        "TrxRegionEndpoint coprocessor addDelete writing to HLOG for early logging: Threw an exception");
            }
        } else {
            for (Cell value : waction.getCells()) {
                KeyValue kv = KeyValue.cloneAndAddTags(value, tagList);
                e.add(kv);
            }
        }

        if (LOG.isTraceEnabled())
            LOG.trace("addDelete -- EXIT");
    }

    public synchronized void applyDeletes(final List<Cell> input, final long minTime, final long maxTime) {
        if (deletes.isEmpty()) {
            return;
        }
        for (ListIterator<Cell> itr = input.listIterator(); itr.hasNext();) {
            Cell included = applyDeletes(itr.next(), minTime, maxTime);
            if (null == included) {
                itr.remove();
            }
        }
    }

    public synchronized Cell applyDeletes(final Cell kv, final long minTime, final long maxTime) {
        if (deletes.isEmpty()) {
            return kv;
        }

        ListIterator<Delete> deletesIter = null;

        for (deletesIter = deletes.listIterator(); deletesIter.hasNext();) {
            Delete delete = deletesIter.next();

            // Skip if delete should not apply
            if (!Bytes.equals(kv.getRow(), delete.getRow()) || kv.getTimestamp() > delete.getTimeStamp()
                    || delete.getTimeStamp() > maxTime || delete.getTimeStamp() < minTime) {
                continue;
            }

            // Whole-row delete
            if (delete.isEmpty()) {
                return null;
            }

            for (Entry<byte[], List<Cell>> deleteEntry : delete.getFamilyCellMap().entrySet()) {
                byte[] family = deleteEntry.getKey();
                if (!Bytes.equals(kv.getFamilyArray(), family)) {
                    continue;
                }
                List<Cell> familyDeletes = deleteEntry.getValue();
                if (familyDeletes == null) {
                    return null;
                }
                for (Cell keyDeletes : familyDeletes) {
                    byte[] deleteQualifier = keyDeletes.getQualifierArray();
                    byte[] kvQualifier = kv.getQualifierArray();
                    if (keyDeletes.getTimestamp() > kv.getTimestamp()
                            && Bytes.equals(deleteQualifier, kvQualifier)) {
                        return null;
                    }
                }
            }
        }

        return kv;
    }

    public void clearState() {

        clearTransactionsToCheck();
        clearWriteOrdering();
        clearScanRange();
        clearDeletes();
        clearTags();
        clearWALEdit();
    }

    public void clearTransactionsToCheck() {
        transactionsToCheck.clear();
    }

    public void clearWriteOrdering() {
        writeOrdering.clear();
    }

    public void clearScanRange() {
        scans.clear();
    }

    public void clearDeletes() {
        deletes.clear();
    }

    public void clearTags() {
        tagList.clear();
    }

    public void clearWALEdit() {
        if (e.size() > 0) {

            DataInputBuffer in = new DataInputBuffer();
            try {
                e.readFields(in);
                e.getKeyValues().clear();
            } catch (java.io.EOFException eeof) {
                // DataInputBuffer was empty, successfully emptied kvs
            } catch (Exception e) {
                if (LOG.isTraceEnabled())
                    LOG.trace(
                            "TrxTransactionState clearWALEdit:  Clearing WALEdit caught an exception for transaction "
                                    + this.transactionId + ", regionInfo is [" + regionInfo.getRegionNameAsString()
                                    + "]" + ": exception " + e.toString());
            } finally {
                try {
                    in.close();
                } catch (IOException io) {
                }
            }
        }
        if (e.size() > 0)
            if (LOG.isTraceEnabled())
                LOG.trace(
                        "TrxTransactionState clearWALEdit:  Possible leak with kvs entries in WALEDIT, for transaction "
                                + this.transactionId + ", regionInfo is [" + regionInfo.getRegionNameAsString()
                                + "], e is " + e.toString());
    }

    public void addTransactionToCheck(final TrxTransactionState transaction) {
        transactionsToCheck.add(transaction);
    }

    public synchronized boolean hasConflict() {

        for (TrxTransactionState transactionState : transactionsToCheck) {
            try {
                if (hasConflict(transactionState)) {
                    if (LOG.isTraceEnabled())
                        LOG.trace(
                                "TrxTransactionState hasConflict: Returning true for " + transactionState.toString()
                                        + ", regionInfo is [" + regionInfo.getRegionNameAsString() + "]");
                    return true;
                }
            } catch (Exception e) {
                // We are unable to ascertain if we have had a conflict with 
                // the rows we are trying to modify.  We will return true, 
                // indicating that we can not allow the pending changes 
                // for this transaction to commit.
                LOG.error("TrxTransactionState hasConflict: Returning true. Caught exception for transaction "
                        + transactionState.toString() + ", regionInfo is [" + regionInfo.getRegionNameAsString()
                        + "], exception is " + e.toString());
                return true;
            }
        }
        return false;
    }

    private boolean hasConflict(final TrxTransactionState checkAgainst) throws Exception {
        if (checkAgainst.getStatus().equals(TransactionState.Status.ABORTED)) {
            return false; // Cannot conflict with aborted transactions
        }

        ListIterator<WriteAction> writeOrderIter = null;

        for (writeOrderIter = checkAgainst.writeOrdering.listIterator(); writeOrderIter.hasNext();) {
            WriteAction otherUpdate = writeOrderIter.next();

            try {
                byte[] row = otherUpdate.getRow();
                if (row == null) {
                    LOG.warn("TrxTransactionState hasConflict: row is null - this Transaction [" + this.toString()
                            + "] checkAgainst Transaction [" + checkAgainst.toString() + "] ");
                }
                if (this.getTransactionId() == checkAgainst.getTransactionId()) {
                    if (LOG.isTraceEnabled())
                        LOG.trace("TrxTransactionState hasConflict: Continuing - this Transaction ["
                                + this.toString() + "] is the same as the against Transaction ["
                                + checkAgainst.toString() + "]");
                    continue;
                }
                if (this.scans != null && !this.scans.isEmpty()) {
                    ListIterator<ScanRange> scansIter = null;

                    for (scansIter = this.scans.listIterator(); scansIter.hasNext();) {
                        ScanRange scanRange = scansIter.next();

                        if (scanRange == null)
                            if (LOG.isTraceEnabled())
                                LOG.trace("Transaction [" + this.toString() + "] scansRange is null");
                        if (scanRange != null && scanRange.contains(row)) {
                            LOG.warn("Transaction [" + this.toString() + "] has scan which conflicts with ["
                                    + checkAgainst.toString() + "]: region [" + regionInfo.getRegionNameAsString()
                                    + "], scanRange[" + scanRange.toString() + "] ,row[" + Bytes.toStringBinary(row)
                                    + "]");
                            return true;
                        }
                        //else {
                        //    LOG.trace("Transaction [" + this.toString() + "] has scanRange checked against ["
                        //            + checkAgainst.toString() + "]: region [" + regionInfo.getRegionNameAsString()
                        //            + "], scanRange[" + scanRange.toString() + "] ,row[" + Bytes.toStringBinary(row) + "]");
                        //}
                    }
                } else {
                    if (this.scans == null)
                        LOG.trace("Transaction [" + this.toString() + "] scans was equal to null");
                    else
                        LOG.trace("Transaction [" + this.toString() + "] scans was empty ");
                }
            } catch (Exception e) {
                LOG.warn("TrxTransactionState hasConflict: Unable to get row - this Transaction [" + this.toString()
                        + "] checkAgainst Transaction [" + checkAgainst.toString() + "] " + " Exception: " + e);
                throw e;
            }
        }
        return false;
    }

    public WALEdit getEdit() {
        return e;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("[transactionId: ");
        result.append(transactionId);
        result.append(" status: ");
        result.append(status.name());
        result.append(" scan Size: ");
        result.append(scans.size());
        result.append(" write Size: ");
        result.append(getWriteOrdering().size());
        result.append(" startSQ: ");
        result.append(startSequenceNumber);
        if (sequenceNumber != null) {
            result.append(" commitedSQ:");
            result.append(sequenceNumber);
        }
        result.append("]");

        return result.toString();
    }

    public synchronized void addScan(final Scan scan) {
        ScanRange scanRange = new ScanRange(scan.getStartRow(), scan.getStopRow());
        if (LOG.isTraceEnabled())
            LOG.trace(String.format("Adding scan for transaction [%s], from startRow [%s] to endRow [%s]",
                    transactionId, scanRange.startRow == null ? "null" : Bytes.toStringBinary(scanRange.startRow),
                    scanRange.endRow == null ? "null" : Bytes.toStringBinary(scanRange.endRow)));
        scans.add(scanRange);
    }

    /**
     * Get deletes.
     * 
     * @return deletes
     */
    public synchronized List<Delete> getDeletes() {
        return deletes;
    }

    /**
     * Get a scanner to go through the puts and deletes from this transaction. Used to weave together the local trx puts
     * with the global state.
     * 
     * @return scanner
     */
    public KeyValueScanner getScanner(final Scan scan) {
        return new TransactionScanner(scan);
    }

    private synchronized Cell[] getAllCells(final Scan scan) {
        //if (LOG.isTraceEnabled()) LOG.trace("getAllCells -- ENTRY");
        List<Cell> kvList = new ArrayList<Cell>();

        ListIterator<WriteAction> writeOrderIter = null;

        for (writeOrderIter = writeOrdering.listIterator(); writeOrderIter.hasNext();) {
            WriteAction action = writeOrderIter.next();
            byte[] row = action.getRow();
            List<Cell> kvs = action.getCells();

            if (scan.getStartRow() != null && !Bytes.equals(scan.getStartRow(), HConstants.EMPTY_START_ROW)
                    && Bytes.compareTo(row, scan.getStartRow()) < 0) {
                continue;
            }
            if (scan.getStopRow() != null && !Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)
                    && Bytes.compareTo(row, scan.getStopRow()) > 0) {
                continue;
            }

            if (!scan.hasFamilies()) {
                kvList.addAll(kvs);
                continue;
            }
            // Pick only the Cell's that match the 'scan' specifications
            for (Cell lv_kv : kvs) {
                byte[] lv_kv_family = lv_kv.getFamilyArray();
                Map<byte[], NavigableSet<byte[]>> lv_familyMap = scan.getFamilyMap();
                NavigableSet<byte[]> set = lv_familyMap.get(lv_kv_family);
                if (set == null || set.size() == 0) {
                    kvList.add(lv_kv);
                    continue;
                }
                if (set.contains(lv_kv.getQualifierArray())) {
                    kvList.add(lv_kv);
                }
            }
        }

        if (LOG.isTraceEnabled())
            LOG.trace("getAllCells -- EXIT kvList size = " + kvList.size());
        return kvList.toArray(new Cell[kvList.size()]);
    }

    private synchronized KeyValue[] getAllKVs(final Scan scan) {
        //if (LOG.isTraceEnabled()) LOG.trace("getAllKVs -- ENTRY");
        List<KeyValue> kvList = new ArrayList<KeyValue>();

        ListIterator<WriteAction> writeOrderIter = null;

        for (writeOrderIter = writeOrdering.listIterator(); writeOrderIter.hasNext();) {
            WriteAction action = writeOrderIter.next();
            byte[] row = action.getRow();
            List<KeyValue> kvs = action.getKeyValues();

            if (scan.getStartRow() != null && !Bytes.equals(scan.getStartRow(), HConstants.EMPTY_START_ROW)
                    && Bytes.compareTo(row, scan.getStartRow()) < 0) {
                continue;
            }
            if (scan.getStopRow() != null && !Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)
                    && Bytes.compareTo(row, scan.getStopRow()) > 0) {
                continue;
            }

            if (!scan.hasFamilies()) {
                kvList.addAll(kvs);
                continue;
            }

            // Pick only the Cell's that match the 'scan' specifications
            Map<byte[], NavigableSet<byte[]>> lv_familyMap = scan.getFamilyMap();
            for (KeyValue lv_kv : kvs) {
                byte[] lv_kv_family = lv_kv.getFamily();
                NavigableSet<byte[]> set = lv_familyMap.get(lv_kv_family);
                if (set == null || set.size() == 0) {
                    kvList.add(lv_kv);
                    continue;
                }
                if (set.contains(lv_kv.getQualifier())) {
                    kvList.add(lv_kv);
                }
            }
        }

        if (LOG.isTraceEnabled())
            LOG.trace("getAllKVs -- EXIT kvList size = " + kvList.size());
        return kvList.toArray(new KeyValue[kvList.size()]);
    }

    private synchronized int getTransactionSequenceIndex(final Cell kv) {
        ListIterator<WriteAction> writeOrderIter = null;
        int i = 0;

        for (writeOrderIter = writeOrdering.listIterator(); writeOrderIter.hasNext();) {
            i++;
            WriteAction action = writeOrderIter.next();
            if (isKvInPut(kv, action.getPut())) {
                return i;
            }
            if (isKvInDelete(kv, action.getDelete())) {
                return i;
            }
        }
        throw new IllegalStateException("Can not find kv in transaction writes");
    }

    /**
     * Scanner of the puts and deletes that occur during this transaction.
     * 
     * @author clint.morgan
     */
    public class TransactionScanner extends KeyValueListScanner implements InternalScanner {

        private ScanQueryMatcher matcher;

        TransactionScanner(final Scan scan) {
            super(new KeyValue.KVComparator() {
                @Override
                public int compare(final Cell left, final Cell right) {
                    int result = super.compare(left, right);
                    if (result != 0) {
                        return result;
                    }
                    if (left == right) {
                        return 0;
                    }
                    int put1Number = getTransactionSequenceIndex(left);
                    int put2Number = getTransactionSequenceIndex(right);
                    return put2Number - put1Number;
                }
            }, getAllKVs(scan));

            // We want transaction scanner to always take priority over store
            // scanners.
            super.setSequenceID(Long.MAX_VALUE);

            //Store.ScanInfo scaninfo = new Store.ScanInfo(null, 0, 1, HConstants.FOREVER, false, 0, Cell.COMPARATOR);
            ScanInfo scaninfo = new ScanInfo(null, 0, 1, HConstants.FOREVER, false, 0, KeyValue.COMPARATOR);

            try {
                if (sb_sqm_98_1) {
                    try {
                        matcher = (ScanQueryMatcher) c98_1.newInstance(scan, scaninfo, null, ScanType.USER_SCAN,
                                Long.MAX_VALUE, HConstants.LATEST_TIMESTAMP, 0);
                        if (LOG.isTraceEnabled())
                            LOG.trace("Created matcher using reflection for HBase 98.1");
                    } catch (InstantiationException exc_ins) {
                        LOG.error("InstantiationException: " + exc_ins);
                    } catch (IllegalAccessException exc_ill_acc) {
                        LOG.error("IllegalAccessException: " + exc_ill_acc);
                    } catch (InvocationTargetException exc_inv_tgt) {
                        LOG.error("InvocationTargetException: " + exc_inv_tgt);
                    }

                } else {
                    try {
                        matcher = (ScanQueryMatcher) c98_4.newInstance(scan, scaninfo, null, ScanType.USER_SCAN,
                                Long.MAX_VALUE, HConstants.LATEST_TIMESTAMP, (long) 0, null);
                        if (LOG.isTraceEnabled())
                            LOG.trace("Created matcher using reflection for HBase 98.4");
                    } catch (InstantiationException exc_ins) {
                        LOG.error("InstantiationException: " + exc_ins);
                    } catch (IllegalAccessException exc_ill_acc) {
                        LOG.error("IllegalAccessException: " + exc_ill_acc);
                    } catch (InvocationTargetException exc_inv_tgt) {
                        LOG.error("InvocationTargetException: " + exc_inv_tgt);
                    }

                }
            } catch (Exception e) {
                LOG.error("error while instantiating the ScanQueryMatcher()" + e);
            }

        }

        /**
         * Get the next row of values from this transaction.
         * 
         * @param outResult
         * @param limit
         * @return true if there are more rows, false if scanner is done
         */
        @Override
        public synchronized boolean next(final List<Cell> outResult, final int limit) throws IOException {
            Cell peeked = this.peek();
            if (peeked == null) {
                close();
                return false;
            }

            matcher.setRow(peeked.getRowArray(), peeked.getRowOffset(), peeked.getRowLength());

            KeyValue kv;
            List<Cell> results = new ArrayList<Cell>();
            LOOP: while ((kv = this.peek()) != null) {
                ScanQueryMatcher.MatchCode qcode = matcher.match(kv);
                switch (qcode) {
                case INCLUDE:
                    Cell next = this.next();
                    results.add(next);
                    if (limit > 0 && results.size() == limit) {
                        break LOOP;
                    }
                    continue;

                case DONE:
                    // copy jazz
                    outResult.addAll(0, results);
                    return true;

                case DONE_SCAN:
                    close();

                    // copy jazz
                    outResult.addAll(0, results);

                    return false;

                case SEEK_NEXT_ROW:
                    this.next();
                    break;

                case SEEK_NEXT_COL:
                    this.next();
                    break;

                case SKIP:
                    this.next();
                    break;

                default:
                    throw new RuntimeException("UNEXPECTED");
                }
            }

            if (!results.isEmpty()) {
                // copy jazz
                outResult.addAll(0, results);
                return true;
            }

            // No more keys
            close();
            return false;
        }

        @Override
        /* Commenting out for HBase 0.98
        public boolean next(final List<KeyValue> results) throws IOException {
        return next(results, -1);
        }
        // May need to use metric value
        @Override
        public boolean next(List<KeyValue> results, String metric) throws IOException{          
          return next(results, -1);
        }
            
        // May need to use metric value
        @Override
        public boolean next(List<Cell> results, int limit, String metric) throws IOException {
              
          return next(results,limit);
        }
            
        */

        public synchronized boolean next(final List<Cell> results) throws IOException {
            return next(results, -1);
        }

    }

    private synchronized boolean isKvInPut(final Cell kv, final Put put) {
        if (null != put) {
            for (List<Cell> putKVs : put.getFamilyCellMap().values()) {
                for (Cell putKV : putKVs) {
                    if (putKV == kv) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private synchronized boolean isKvInDelete(final Cell kv, final Delete delete) {
        if (null != delete) {
            for (List<Cell> putKVs : delete.getFamilyCellMap().values()) {
                for (Cell deleteKv : putKVs) {
                    if (deleteKv == kv) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Get the puts and deletes in transaction order.
     * 
     * @return Return the writeOrdering.
     */
    public List<WriteAction> getWriteOrdering() {
        return writeOrdering;
    }

    /*
     * Get the puts and deletes in transaction order.
     * 
     * @return Return the writeOrdering as an iterator.
     */
    public ListIterator<WriteAction> getWriteOrderingIter() {
        return writeOrdering.listIterator();
    }

    /**
     * Simple wrapper for Put and Delete since they don't have a common enough interface.
     */
    public class WriteAction {

        private Put put;
        private Delete delete;

        public WriteAction(final Put put) {
            if (null == put) {
                throw new IllegalArgumentException("WriteAction requires a Put or a Delete.");
            }
            this.put = put;
        }

        public WriteAction(final Delete delete) {
            if (null == delete) {
                throw new IllegalArgumentException("WriteAction requires a Put or a Delete.");
            }
            this.delete = delete;
        }

        public Put getPut() {
            return put;
        }

        public Delete getDelete() {
            return delete;
        }

        public synchronized byte[] getRow() {
            if (put != null) {
                return put.getRow();
            } else if (delete != null) {
                return delete.getRow();
            }
            throw new IllegalStateException("WriteAction is invalid");
        }

        synchronized List<Cell> getCells() {
            List<Cell> edits = new ArrayList<Cell>();
            Collection<List<Cell>> kvsList;

            if (put != null) {
                kvsList = put.getFamilyCellMap().values();
            } else if (delete != null) {
                if (delete.getFamilyCellMap().isEmpty()) {
                    // If whole-row delete then we need to expand for each
                    // family
                    kvsList = new ArrayList<List<Cell>>(1);
                    for (byte[] family : tabledescriptor.getFamiliesKeys()) {
                        Cell familyDelete = new KeyValue(delete.getRow(), family, null, delete.getTimeStamp(),
                                KeyValue.Type.DeleteFamily);
                        kvsList.add(Collections.singletonList(familyDelete));
                    }
                } else {
                    kvsList = delete.getFamilyCellMap().values();
                }
            } else {
                throw new IllegalStateException("WriteAction is invalid");
            }

            for (List<Cell> kvs : kvsList) {
                for (Cell kv : kvs) {
                    edits.add(kv);
                    //if (LOG.isDebugEnabled()) LOG.debug("Trafodion Recovery:   " + regionInfo.getRegionNameAsString() + " create edits for transaction: "
                    //               + transactionId + " with Op " + kv.getType());
                }
            }
            return edits;
        }

        synchronized List<KeyValue> getKeyValues() {
            List<KeyValue> edits = new ArrayList<KeyValue>();
            Collection<List<KeyValue>> kvsList = null;

            if (put != null) {
                if (!put.getFamilyMap().isEmpty()) {
                    kvsList = put.getFamilyMap().values();
                }
            } else if (delete != null) {
                if (delete.getFamilyCellMap().isEmpty()) {
                    // If whole-row delete then we need to expand for each
                    // family
                    kvsList = new ArrayList<List<KeyValue>>(1);
                    for (byte[] family : tabledescriptor.getFamiliesKeys()) {
                        KeyValue familyDelete = new KeyValue(delete.getRow(), family, null, delete.getTimeStamp(),
                                KeyValue.Type.DeleteFamily);
                        kvsList.add(Collections.singletonList(familyDelete));
                    }
                } else {
                    kvsList = delete.getFamilyMap().values();
                }
            } else {
                throw new IllegalStateException("WriteAction is invalid");
            }

            if (kvsList != null) {
                for (List<KeyValue> kvs : kvsList) {
                    for (KeyValue kv : kvs) {
                        edits.add(kv);
                        //if (LOG.isDebugEnabled()) LOG.debug("Trafodion getKeyValues:   " + regionInfo.getRegionNameAsString() + " create edits for transaction: "
                        //              + transactionId + " with Op " + kv.getType());
                    }
                }
            } else if (LOG.isTraceEnabled())
                LOG.trace("Trafodion getKeyValues:   " + regionInfo.getRegionNameAsString() + " kvsList was null");
            return edits;
        }
    }

}