org.commonvox.hbase_column_manager.Repository.java Source code

Java tutorial

Introduction

Here is the source code for org.commonvox.hbase_column_manager.Repository.java

Source

/*
 * Copyright (C) 2016 Daniel Vimont
 *
 * 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 org.commonvox.hbase_column_manager;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
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 java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLStreamException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.NamespaceNotFoundException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Append;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Increment;
import org.apache.hadoop.hbase.client.Mutation;
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.Row;
import org.apache.hadoop.hbase.client.RowMutations;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.KeyOnlyFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.io.TimeRange;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.util.ToolRunner;
import org.apache.log4j.Logger;

/**
 * Center of all CRUD operations for maintenance and retrieval of HBase schema stored in the
 * ColumnManager Repository table, including metadata pertaining to all
 * <i>Columns</i> actively stored in each <i>Column Family</i> for each included <i>Table</i>.
 *
 * @author Daniel Vimont
 */
class Repository {

    private final Logger logger;
    private static final Logger staticLogger = Logger.getLogger(Repository.class.getName());
    private final byte[] javaUsername;
    private final boolean columnManagerIsActivated;
    private Set<String> includedNamespaces;
    private Set<String> includedEntireNamespaces;
    private Set<TableName> includedTables;
    private Set<String> excludedNamespaces;
    private Set<String> excludedEntireNamespaces;
    private Set<TableName> excludedTables;
    private final Connection hbaseConnection;
    private final Admin standardAdmin;
    private final Table repositoryTable;
    private final Table aliasTable;
    private static final byte[] ALIAS_INCREMENTOR_COLUMN = Bytes.toBytes("#$$#_aliasIncrementor");
    private static final int INVALID_ALIAS_INT = -1;
    private static final byte[] INVALID_ALIAS = Bytes.toBytes(INVALID_ALIAS_INT);
    private static final NavigableSet<byte[]> NULL_NAVIGABLE_SET = null;

    static final String PRODUCT_NAME = "ColumnManagerAPI";
    static final byte[] JAVA_USERNAME_PROPERTY_KEY = Bytes.toBytes("user.name");
    private static final String REPOSITORY_NOT_ACTIVATED_MSG = PRODUCT_NAME + " Repository is NOT ACTIVATED.";

    // The following HBASE_CONFIG_PARM* keys & values used in hbase-site.xml
    //   or hbase-column-manager.xml to activate ColumnManager, include Tables for processing, etc.
    static final String HBASE_CONFIG_PARM_KEY_PREFIX = "column_manager.";
    static final String HBASE_CONFIG_PARM_KEY_COLMANAGER_ACTIVATED = HBASE_CONFIG_PARM_KEY_PREFIX + "activated";
    private static final String HBASE_CONFIG_PARM_VALUE_COLMANAGER_DEACTIVATED = "false"; // default
    private static final String HBASE_CONFIG_PARM_VALUE_COLMANAGER_ACTIVATED = "true";
    static final String HBASE_CONFIG_PARM_KEY_COLMANAGER_INCLUDED_TABLES = HBASE_CONFIG_PARM_KEY_PREFIX
            + "includedTables";
    static final String HBASE_CONFIG_PARM_KEY_COLMANAGER_EXCLUDED_TABLES = HBASE_CONFIG_PARM_KEY_PREFIX
            + "excludedTables";
    static final String ALL_TABLES_WILDCARD_INDICATOR = ":*";

    private static final int UNIQUE_FOREIGN_KEY_LENGTH = 16;
    private static final NamespaceDescriptor HBASE_SYSTEM_NAMESPACE_DESCRIPTOR = NamespaceDescriptor.create("hbase")
            .build();
    static final NamespaceDescriptor REPOSITORY_NAMESPACE_DESCRIPTOR = NamespaceDescriptor
            .create("__column_manager_repository_namespace").build();
    static final TableName REPOSITORY_TABLENAME = TableName.valueOf(REPOSITORY_NAMESPACE_DESCRIPTOR.getName(),
            "column_manager_repository_table");
    static final byte[] REPOSITORY_CF = Bytes.toBytes("se"); // ("se"="SchemaEntities")
    static final int DEFAULT_REPOSITORY_MAX_VERSIONS = 50; // should this be set higher?

    static final TableName ALIAS_DIRECTORY_TABLENAME = TableName.valueOf(REPOSITORY_NAMESPACE_DESCRIPTOR.getName(),
            "column_manager_alias_directory_table");
    static final byte[] ALIAS_CF = Bytes.toBytes("ca"); // ("ca"="ColumnAliases")

    static final byte[] NAMESPACE_PARENT_FOREIGN_KEY = { '-' };
    static final byte[] HBASE_DEFAULT_NAMESPACE = Bytes.toBytes("default");
    private static final Map<ImmutableBytesWritable, ImmutableBytesWritable> EMPTY_VALUES = new HashMap<>();
    private static final String CONFIG_COLUMN_PREFIX = "Configuration__";
    private static final byte[] CONFIG_COLUMN_PREFIX_BYTES = Bytes.toBytes(CONFIG_COLUMN_PREFIX);
    private static final String VALUE_COLUMN_PREFIX = "Value__";
    private static final byte[] VALUE_COLUMN_PREFIX_BYTES = Bytes.toBytes(VALUE_COLUMN_PREFIX);
    static final String COUNTER_COLUMN_PREFIX = "Counter__";
    static final byte[] COUNTER_COLUMN_PREFIX_BYTES = Bytes.toBytes(COUNTER_COLUMN_PREFIX);
    static final byte[] COL_COUNTER_QUALIFIER = Bytes.toBytes(COUNTER_COLUMN_PREFIX + "column");
    static final byte[] CELL_COUNTER_QUALIFIER = Bytes.toBytes(COUNTER_COLUMN_PREFIX + "cell");
    static final String TIMESTAMP_KEY_PREFIX = "Timestamp__";
    static final byte[] TIMESTAMP_KEY_PREFIX_BYTES = Bytes.toBytes(TIMESTAMP_KEY_PREFIX);
    static final byte[] COL_COUNTER_TIMESTAMP_KEY = Bytes.toBytes(TIMESTAMP_KEY_PREFIX + "column_counter");
    static final byte[] CELL_COUNTER_TIMESTAMP_KEY = Bytes.toBytes(TIMESTAMP_KEY_PREFIX + "cell_counter");
    static final byte[] MAX_VALUE_QUALIFIER = ByteBuffer
            .allocate(VALUE_COLUMN_PREFIX.length() + ColumnAuditor.MAX_VALUE_LENGTH_KEY.length())
            .put(VALUE_COLUMN_PREFIX_BYTES).put(Bytes.toBytes(ColumnAuditor.MAX_VALUE_LENGTH_KEY)).array();

    static final String JOB_NAME_CONF_KEY = "mapreduce.job.name";
    static final String MAP_SPECULATIVE_CONF_KEY = "mapreduce.map.speculative";
    static final String COLMANAGER_MAP_CONF_KEY_PREFIX = "colmanager.map.";
    static final String TABLE_NAME_CONF_KEY = COLMANAGER_MAP_CONF_KEY_PREFIX + "source.table";
    static final String COLFAMILY_CONF_KEY = COLMANAGER_MAP_CONF_KEY_PREFIX + "source.colfamily";
    static final String ARG_KEY_PREFIX = "--";
    static final String ARG_DELIMITER = "=";
    static final String TABLE_NAME_ARG_KEY = ARG_KEY_PREFIX + TABLE_NAME_CONF_KEY + ARG_DELIMITER;
    static final String COLFAMILY_ARG_KEY = ARG_KEY_PREFIX + COLFAMILY_CONF_KEY + ARG_DELIMITER;
    static final String INCLUDE_ALL_CELLS_CONF_KEY = "include_all_cells";
    static final String INCLUDE_ALL_CELLS_ARG_KEY = ARG_KEY_PREFIX + INCLUDE_ALL_CELLS_CONF_KEY + ARG_DELIMITER;

    private static final String SYNC_ERROR_MSG = "SYNCHRONIZATION ERROR FOUND IN " + PRODUCT_NAME + " REPOSITORY. ";
    private static final String SCHEMA_ENTITY_NOT_FOUND_SYNC_ERROR_MSG = SYNC_ERROR_MSG
            + "%s in Repository NOT FOUND in HBase: ";
    static final String NAMESPACE_NOT_FOUND_SYNC_ERROR_MSG = String.format(SCHEMA_ENTITY_NOT_FOUND_SYNC_ERROR_MSG,
            NamespaceDescriptor.class.getSimpleName());
    static final String TABLE_NOT_FOUND_SYNC_ERROR_MSG = String.format(SCHEMA_ENTITY_NOT_FOUND_SYNC_ERROR_MSG,
            HTableDescriptor.class.getSimpleName());
    static final String COLDESCRIPTOR_NOT_FOUND_SYNC_ERROR_MSG = String
            .format(SCHEMA_ENTITY_NOT_FOUND_SYNC_ERROR_MSG, HColumnDescriptor.class.getSimpleName());
    private static final String SCHEMA_ENTITY_ATTRIBUTE_SYNC_ERROR_MSG = SYNC_ERROR_MSG
            + "%s in Repository has attribute-value(s) differing from that in HBase: ";
    static final String NAMESPACE_ATTRIBUTE_SYNC_ERROR_MSG = String.format(SCHEMA_ENTITY_ATTRIBUTE_SYNC_ERROR_MSG,
            NamespaceDescriptor.class.getSimpleName());
    static final String TABLE_ATTRIBUTE_SYNC_ERROR_MSG = String.format(SCHEMA_ENTITY_ATTRIBUTE_SYNC_ERROR_MSG,
            HTableDescriptor.class.getSimpleName());
    static final String COLDESCRIPTOR_ATTRIBUTE_SYNC_ERROR_MSG = String
            .format(SCHEMA_ENTITY_ATTRIBUTE_SYNC_ERROR_MSG, HColumnDescriptor.class.getSimpleName());
    private static final byte[] ENTITY_STATUS_COLUMN = Bytes.toBytes("_Status");
    private static final byte[] ACTIVE_STATUS = Bytes.toBytes("A");
    private static final byte[] DELETED_STATUS = Bytes.toBytes("D");
    static final byte[] FOREIGN_KEY_COLUMN = Bytes.toBytes("_ForeignKey");
    private static final byte[] HEX_00_ARRAY = new byte[16];
    private static final byte[] HEX_FF_ARRAY = new byte[16];

    static {
        Arrays.fill(HEX_FF_ARRAY, (byte) 0xff);
    }

    Repository(Connection hBaseConnection, Object originatingObject) throws IOException {
        logger = Logger.getLogger(this.getClass().getName());
        javaUsername = Bytes.toBytes(System.getProperty(Bytes.toString(JAVA_USERNAME_PROPERTY_KEY)));
        this.hbaseConnection = hBaseConnection;
        this.standardAdmin = getNewAdmin(this.hbaseConnection);
        Configuration conf = hbaseConnection.getConfiguration();
        // Configuration.dumpConfiguration(conf, new PrintWriter(System.out));
        String columnManagerActivatedStatus = conf.get(HBASE_CONFIG_PARM_KEY_COLMANAGER_ACTIVATED,
                HBASE_CONFIG_PARM_VALUE_COLMANAGER_DEACTIVATED);
        logger.info(PRODUCT_NAME + " Repository instance being instantiated by object of "
                + originatingObject.getClass().getSimpleName() + " class.");
        logger.info(PRODUCT_NAME + " config parameter: " + HBASE_CONFIG_PARM_KEY_COLMANAGER_ACTIVATED + " = "
                + columnManagerActivatedStatus);
        if (columnManagerActivatedStatus.equalsIgnoreCase(HBASE_CONFIG_PARM_VALUE_COLMANAGER_ACTIVATED)) {
            columnManagerIsActivated = true;
            logger.info(PRODUCT_NAME + " Repository is ACTIVATED.");
            buildIncludedAndExcludedTablesSets(conf);
            boolean newInstallation = !standardAdmin.tableExists(REPOSITORY_TABLENAME);
            initializeRepositoryNamespace(standardAdmin);
            repositoryTable = initializeRepositoryTable(standardAdmin);
            aliasTable = initializeAliasTable(standardAdmin);
            doSyncCheck();
            if (newInstallation) {
                discoverSchema(false, false, false);
            }
        } else {
            //      throw new ColumnManagerIOException(PRODUCT_NAME + " Repository is NOT ACTIVATED.") {};
            columnManagerIsActivated = false;
            repositoryTable = null;
            aliasTable = null;
            logger.info(PRODUCT_NAME + " Repository is NOT ACTIVATED.");
        }
    }

    private void buildIncludedAndExcludedTablesSets(Configuration conf) {
        String[] includedTablesArray = conf.getStrings(HBASE_CONFIG_PARM_KEY_COLMANAGER_INCLUDED_TABLES);
        String[] excludedTablesArray = conf.getStrings(HBASE_CONFIG_PARM_KEY_COLMANAGER_EXCLUDED_TABLES);
        if (includedTablesArray != null && excludedTablesArray != null) {
            logger.warn(PRODUCT_NAME + " " + HBASE_CONFIG_PARM_KEY_COLMANAGER_EXCLUDED_TABLES
                    + " parameter will be ignored; overridden by "
                    + HBASE_CONFIG_PARM_KEY_COLMANAGER_INCLUDED_TABLES + " parameter.");
        }
        if (includedTablesArray == null) {
            includedTables = null;
            includedNamespaces = null;
            includedEntireNamespaces = null;
            if (excludedTablesArray == null) {
                excludedTables = null;
                excludedNamespaces = null;
                excludedEntireNamespaces = null;
                logger.info(PRODUCT_NAME + " Repository activated for ALL user tables.");
            } else {
                excludedTables = new TreeSet<>();
                excludedNamespaces = new TreeSet<>();
                excludedEntireNamespaces = new TreeSet<>();
                for (String excludedTableString : new TreeSet<>(Arrays.asList(excludedTablesArray))) {
                    try {
                        TableName excludedTableName = TableName.valueOf(excludedTableString);
                        excludedTables.add(excludedTableName);
                    } catch (IllegalArgumentException e) {
                        if (excludedTableString.endsWith(ALL_TABLES_WILDCARD_INDICATOR)) {
                            String excludedNamespaceString = excludedTableString.substring(0,
                                    excludedTableString.length() - 2);
                            // #isLegalNamespaceName throws IllegalArgumentException if not legal Namespace
                            TableName.isLegalNamespaceName(Bytes.toBytes(excludedNamespaceString));
                            excludedNamespaces.add(excludedNamespaceString);
                            excludedEntireNamespaces.add(excludedNamespaceString);
                        } else {
                            throw e;
                        }
                    }
                }
                logger.info(PRODUCT_NAME + " Repository activated for all EXCEPT the following user tables: "
                        + conf.get(HBASE_CONFIG_PARM_KEY_COLMANAGER_EXCLUDED_TABLES));
            }
        } else {
            excludedTables = null;
            excludedNamespaces = null;
            excludedEntireNamespaces = null;
            includedTables = new TreeSet<>();
            includedNamespaces = new TreeSet<>();
            includedEntireNamespaces = new TreeSet<>();
            for (String includedTableString : new TreeSet<>(Arrays.asList(includedTablesArray))) {
                try {
                    TableName includedTableName = TableName.valueOf(includedTableString);
                    includedTables.add(includedTableName);
                    includedNamespaces.add(includedTableName.getNamespaceAsString());
                } catch (IllegalArgumentException e) {
                    if (includedTableString.endsWith(ALL_TABLES_WILDCARD_INDICATOR)) {
                        String includedNamespaceString = includedTableString.substring(0,
                                includedTableString.length() - 2);
                        // #isLegalNamespaceName throws IllegalArgumentException if not legal Namespace
                        TableName.isLegalNamespaceName(Bytes.toBytes(includedNamespaceString));
                        includedNamespaces.add(includedNamespaceString);
                        includedEntireNamespaces.add(includedNamespaceString);
                    } else {
                        throw e;
                    }
                }
            }
            logger.info(PRODUCT_NAME + " Repository activated for ONLY the following user tables: "
                    + conf.get(HBASE_CONFIG_PARM_KEY_COLMANAGER_INCLUDED_TABLES));
        }
    }

    Table getRepositoryTable() {
        return repositoryTable;
    }

    static boolean repositoryTableExists(Admin standardAdmin) throws IOException {
        return standardAdmin.tableExists(REPOSITORY_TABLENAME);
    }

    /**
     * Creates repository namespace if it does not already exist.
     *
     * @param hbaseAdmin an Admin object
     * @throws IOException if a remote or network exception occurs
     */
    static void initializeRepositoryNamespace(Admin hbaseAdmin) throws IOException {
        Admin standardAdmin = getStandardAdmin(hbaseAdmin);

        if (namespaceExists(standardAdmin, REPOSITORY_NAMESPACE_DESCRIPTOR)) {
            staticLogger
                    .info("ColumnManager Repository Namespace found: " + REPOSITORY_NAMESPACE_DESCRIPTOR.getName());
        } else {
            standardAdmin.createNamespace(REPOSITORY_NAMESPACE_DESCRIPTOR);
            staticLogger.info("ColumnManager Repository Namespace has been created (did not already exist): "
                    + REPOSITORY_NAMESPACE_DESCRIPTOR.getName());
        }
    }

    /**
     * Creates repository table if it does not already exist; in any case, the repository table is
     * returned.
     *
     * @param hbaseAdmin an Admin object
     * @return repository table
     * @throws IOException if a remote or network exception occurs
     */
    static Table initializeRepositoryTable(Admin hbaseAdmin) throws IOException {
        Connection standardConnection = getStandardConnection(hbaseAdmin.getConnection());
        Admin standardAdmin = getStandardAdmin(hbaseAdmin);

        try (Table existingRepositoryTable = standardConnection.getTable(REPOSITORY_TABLENAME)) {
            if (standardAdmin.tableExists(existingRepositoryTable.getName())) {
                staticLogger
                        .info("ColumnManager Repository Table found: " + REPOSITORY_TABLENAME.getNameAsString());
                return existingRepositoryTable;
            }
        }

        // Create new repositoryTable, since it doesn't already exist
        standardAdmin.createTable(
                new HTableDescriptor(REPOSITORY_TABLENAME).addFamily(new HColumnDescriptor(REPOSITORY_CF)
                        .setMaxVersions(DEFAULT_REPOSITORY_MAX_VERSIONS).setInMemory(true)));
        try (Table newRepositoryTable = standardConnection.getTable(REPOSITORY_TABLENAME)) {
            staticLogger.info("ColumnManager Repository Table has been created (did not already exist): "
                    + REPOSITORY_TABLENAME.getNameAsString());
            return newRepositoryTable;
        }
    }

    /**
     * Creates aliasTable if it does not already exist; in any case, the aliasTable is returned.
     *
     * @param hbaseAdmin an Admin object
     * @return aliasDirectory table
     * @throws IOException if a remote or network exception occurs
     */
    static Table initializeAliasTable(Admin hbaseAdmin) throws IOException {
        Connection standardConnection = getStandardConnection(hbaseAdmin.getConnection());
        Admin standardAdmin = getStandardAdmin(hbaseAdmin);

        try (Table existingAliasDirectoryTable = standardConnection.getTable(ALIAS_DIRECTORY_TABLENAME)) {
            if (standardAdmin.tableExists(existingAliasDirectoryTable.getName())) {
                staticLogger.info(
                        "ColumnManager AliasDirectory Table found: " + ALIAS_DIRECTORY_TABLENAME.getNameAsString());
                return existingAliasDirectoryTable;
            }
        }

        // Create new AliasDirectory Table, since it doesn't already exist
        standardAdmin.createTable(new HTableDescriptor(ALIAS_DIRECTORY_TABLENAME)
                .addFamily(new HColumnDescriptor(ALIAS_CF).setInMemory(true)));
        try (Table newAliasDirectoryTable = standardConnection.getTable(ALIAS_DIRECTORY_TABLENAME)) {
            staticLogger.info("ColumnManager AliasDirectory Table has been created (did not already exist): "
                    + ALIAS_DIRECTORY_TABLENAME.getNameAsString());
            return newAliasDirectoryTable;
        }
    }

    static void setRepositoryMaxVersions(Admin hbaseAdmin, int maxVersions) throws IOException {
        HColumnDescriptor repositoryHcd = getStandardAdmin(hbaseAdmin).getTableDescriptor(REPOSITORY_TABLENAME)
                .getFamily(REPOSITORY_CF);
        int oldMaxVersions = repositoryHcd.getMaxVersions();
        if (oldMaxVersions != maxVersions) {
            repositoryHcd.setMaxVersions(maxVersions);
            hbaseAdmin.modifyColumn(REPOSITORY_TABLENAME, repositoryHcd);
            staticLogger.info("ColumnManager Repository Table column-family's <maxVersions> setting has "
                    + "been changed from <" + oldMaxVersions + "> to <" + maxVersions + ">.");
        }
    }

    static int getRepositoryMaxVersions(Admin hbaseAdmin) throws IOException {
        HColumnDescriptor repositoryHcd = getStandardAdmin(hbaseAdmin).getTableDescriptor(REPOSITORY_TABLENAME)
                .getFamily(REPOSITORY_CF);
        return repositoryHcd.getMaxVersions();
    }

    private static Connection getStandardConnection(Connection connection) {
        if (MConnection.class.isAssignableFrom(connection.getClass())) {
            return ((MConnection) connection).getStandardConnection();
        } else {
            return connection;
        }
    }

    private static Admin getStandardAdmin(Admin admin) {
        if (MAdmin.class.isAssignableFrom(admin.getClass())) {
            return ((MAdmin) admin).getWrappedAdmin();
        } else {
            return admin;
        }
    }

    private static Admin getNewAdmin(Connection hbaseConnection) throws IOException {
        try (Admin admin = getStandardConnection(hbaseConnection).getAdmin()) {
            return admin;
        }
    }

    Admin getAdmin() {
        return this.standardAdmin;
    }

    boolean isActivated() {
        return columnManagerIsActivated;
    }

    private boolean doSyncCheck() throws IOException {
        boolean syncErrorFound = false;
        for (MNamespaceDescriptor mnd : getMNamespaceDescriptors()) {
            try {
                NamespaceDescriptor nd = standardAdmin.getNamespaceDescriptor(mnd.getNameAsString());
                if (!schemaEntityAttributesInSync(nd.getName(), NAMESPACE_ATTRIBUTE_SYNC_ERROR_MSG,
                        mnd.getConfiguration(), nd.getConfiguration(), null, null)) {
                    syncErrorFound = true;
                }
            } catch (NamespaceNotFoundException e) {
                logger.warn(NAMESPACE_NOT_FOUND_SYNC_ERROR_MSG + mnd.getNameAsString());
                syncErrorFound = true;
                continue;
            }
            for (MTableDescriptor mtd : getMTableDescriptors(mnd.getForeignKey())) {
                if (!standardAdmin.tableExists(mtd.getTableName())) {
                    logger.warn(TABLE_NOT_FOUND_SYNC_ERROR_MSG + mtd.getNameAsString());
                    syncErrorFound = true;
                    continue;
                }
                HTableDescriptor htd = standardAdmin.getTableDescriptor(mtd.getTableName());
                if (!schemaEntityAttributesInSync(mtd.getTableName().getNameAsString(),
                        TABLE_ATTRIBUTE_SYNC_ERROR_MSG, mtd.getConfiguration(), htd.getConfiguration(),
                        mtd.getValues(), htd.getValues())) {
                    syncErrorFound = true;
                }
                Collection<HColumnDescriptor> hcdCollection = htd.getFamilies();
                Set<String> hcdNames = new TreeSet<>();
                for (HColumnDescriptor hcd : hcdCollection) {
                    hcdNames.add(hcd.getNameAsString());
                }
                for (MColumnDescriptor mcd : mtd.getMColumnDescriptors()) {
                    if (!hcdNames.contains(mcd.getNameAsString())) {
                        logger.warn(COLDESCRIPTOR_NOT_FOUND_SYNC_ERROR_MSG + mcd.getNameAsString());
                        syncErrorFound = true;
                        continue;
                    }
                    HColumnDescriptor hcd = htd.getFamily(mcd.getName());
                    if (!schemaEntityAttributesInSync(mtd.getNameAsString() + ":" + mcd.getNameAsString(),
                            COLDESCRIPTOR_ATTRIBUTE_SYNC_ERROR_MSG, mcd.getConfiguration(), hcd.getConfiguration(),
                            mcd.getValues(), hcd.getValues())) {
                        syncErrorFound = true;
                    }
                }
            }
        }
        if (syncErrorFound) {
            logger.warn("DISCREPANCIES found between " + PRODUCT_NAME + " repository and schema "
                    + "structures in HBase; invocation of RepositoryAdmin#discoverSchema method "
                    + "may be required for resynchronization.");
        }
        return syncErrorFound;
    }

    private boolean schemaEntityAttributesInSync(String entityName, String errorMsg,
            Map<String, String> repositoryConfigurationMap, Map<String, String> hbaseConfigurationMap,
            Map<ImmutableBytesWritable, ImmutableBytesWritable> repositoryValuesMap,
            Map<ImmutableBytesWritable, ImmutableBytesWritable> hbaseValuesMap) {

        for (Entry<String, String> configEntry : repositoryConfigurationMap.entrySet()) {
            if (configEntry.getKey().equals(MColumnDescriptor.COL_DEFINITIONS_ENFORCED_KEY)
                    || configEntry.getKey().equals(MColumnDescriptor.COL_ALIASES_ENABLED_KEY)) {
                continue;
            }
            String configValue = hbaseConfigurationMap.get(configEntry.getKey());
            if (configValue == null || !configValue.equals(configEntry.getValue())) {
                logger.warn(errorMsg + entityName);
                return false;
            }
        }
        for (Entry<String, String> configEntry : hbaseConfigurationMap.entrySet()) {
            String configValue = repositoryConfigurationMap.get(configEntry.getKey());
            if (configValue == null || !configValue.equals(configEntry.getValue())) {
                logger.warn(errorMsg + entityName);
                return false;
            }
        }
        if (repositoryValuesMap == null || hbaseValuesMap == null) { // Namespace has no values Map!
            return true;
        }
        for (Entry<ImmutableBytesWritable, ImmutableBytesWritable> valueEntry : repositoryValuesMap.entrySet()) {
            ImmutableBytesWritable valueEntryValue = hbaseValuesMap.get(valueEntry.getKey());
            if (valueEntryValue == null || !valueEntryValue.equals(valueEntry.getValue())) {
                logger.warn(errorMsg + entityName);
                return false;
            }
        }
        for (Entry<ImmutableBytesWritable, ImmutableBytesWritable> valueEntry : hbaseValuesMap.entrySet()) {
            ImmutableBytesWritable valueEntryValue = repositoryValuesMap.get(valueEntry.getKey());
            if (valueEntryValue == null || !valueEntryValue.equals(valueEntry.getValue())) {
                logger.warn(errorMsg + entityName);
                return false;
            }
        }
        return true;
    }

    static boolean namespaceExists(Admin hbaseAdmin, NamespaceDescriptor nd) throws IOException {
        try {
            hbaseAdmin.getNamespaceDescriptor(nd.getName());
        } catch (NamespaceNotFoundException e) {
            return false;
        }
        return true;
    }

    static boolean namespaceExists(Admin hbaseAdmin, byte[] namespace) throws IOException {
        return namespaceExists(hbaseAdmin, NamespaceDescriptor.create(Bytes.toString(namespace)).build());
    }

    private boolean isIncludedNamespace(String namespaceName) {
        if (namespaceName.equals(HBASE_SYSTEM_NAMESPACE_DESCRIPTOR.getName())
                || namespaceName.equals(REPOSITORY_NAMESPACE_DESCRIPTOR.getName())
                || namespaceName.equals(ColumnInvalidityReport.TEMP_REPORT_NAMESPACE)) {
            return false;
        }
        if (includedNamespaces == null) {
            if (excludedNamespaces == null) {
                return true; // if nothing stipulated by administrator, all user namespaces included
            } else {
                return !excludedNamespaces.contains(namespaceName);
            }
        } else {
            return includedNamespaces.contains(namespaceName);
        }
    }

    boolean isIncludedTable(TableName tableName) {
        if (!isIncludedNamespace(tableName.getNamespaceAsString())) {
            return false;
        }
        if (includedTables == null && includedEntireNamespaces == null) {
            if (excludedTables == null && excludedEntireNamespaces == null) {
                return true; // if nothing stipulated by administrator, all user tables included
            } else {
                return excludedTables.contains(tableName) ? false
                        : !excludedEntireNamespaces.contains(tableName.getNamespaceAsString());
            }
        } else {
            return includedTables.contains(tableName) ? true
                    : includedEntireNamespaces.contains(tableName.getNamespaceAsString());
        }
    }

    private static byte[] generateUniqueForeignKey() {
        UUID uuid = UUID.randomUUID();
        ByteBuffer uniqueID = ByteBuffer.wrap(new byte[UNIQUE_FOREIGN_KEY_LENGTH]);
        uniqueID.putLong(uuid.getMostSignificantBits());
        uniqueID.putLong(uuid.getLeastSignificantBits());
        return uniqueID.array();
    }

    /**
     * Persist Namespace SchemaEntity in Repository
     *
     * @param nd NamespaceDescriptor to persist in SchemaEntity format
     * @return foreign key value of repository row that holds namespace SchemaEntity
     * @throws IOException if a remote or network exception occurs
     */
    byte[] putNamespaceSchemaEntity(NamespaceDescriptor nd) throws IOException {
        if (!isIncludedNamespace(nd.getName())) {
            return null;
        }
        RowId namespaceRowId = new RowId(SchemaEntityType.NAMESPACE.getRecordType(), NAMESPACE_PARENT_FOREIGN_KEY,
                Bytes.toBytes(nd.getName()));
        Map<byte[], byte[]> entityAttributeMap = buildEntityAttributeMap(EMPTY_VALUES, nd.getConfiguration());
        return putSchemaEntity(namespaceRowId, entityAttributeMap, false);
    }

    /**
     * Persist Table SchemaEntity in Repository
     *
     * @param htd TableDescriptor to persist in SchemaEntity format
     * @return foreign key value of repository row that holds table SchemaEntity
     * @throws IOException if a remote or network exception occurs
     */
    byte[] putTableSchemaEntity(HTableDescriptor htd) throws IOException {
        if (!isIncludedTable(htd.getTableName())) {
            return null;
        }
        byte[] namespaceForeignKey = getNamespaceForeignKey(htd.getTableName().getNamespace());
        RowId tableRowId = new RowId(SchemaEntityType.TABLE.getRecordType(), namespaceForeignKey,
                htd.getTableName().getName());
        Map<byte[], byte[]> entityAttributeMap = buildEntityAttributeMap(htd.getValues(), htd.getConfiguration());
        byte[] tableForeignKey = putSchemaEntity(tableRowId, entityAttributeMap, false);

        // Account for potentially deleted ColumnFamilies
        Set<byte[]> oldMcdNames = new TreeSet<>(Bytes.BYTES_RAWCOMPARATOR);
        for (MColumnDescriptor oldMcd : getMColumnDescriptors(tableForeignKey)) {
            oldMcdNames.add(oldMcd.getName());
        }
        for (HColumnDescriptor newHcd : htd.getColumnFamilies()) {
            oldMcdNames.remove(newHcd.getName());
        }
        for (byte[] deletedMcdName : oldMcdNames) {
            deleteColumnFamily(htd.getTableName(), deletedMcdName);
        }

        // Account for added/modified ColumnFamilies
        for (HColumnDescriptor hcd : htd.getColumnFamilies()) {
            putColumnFamilySchemaEntity(tableForeignKey, hcd, htd.getTableName());
        }
        return tableForeignKey;
    }

    /**
     * Persist ColumnFamily SchemaEntity in Repository
     *
     * @param tn TableName
     * @param hcd HColumnDescriptor
     * @return foreign key value of repository row that holds Column Family SchemaEntity
     * @throws IOException
     */
    byte[] putColumnFamilySchemaEntity(TableName tn, HColumnDescriptor hcd) throws IOException {
        if (!isIncludedTable(tn)) {
            return null;
        }
        byte[] tableForeignKey = getTableForeignKey(tn);
        // Note that ColumnManager can be installed atop an already-existing HBase
        //  installation, so table metadata might not yet have been captured in repository.
        if (tableForeignKey == null) {
            tableForeignKey = putTableSchemaEntity(standardAdmin.getTableDescriptor(tn));
        }
        return putColumnFamilySchemaEntity(tableForeignKey, hcd, tn);
    }

    /**
     * Persist ColumnFamily SchemaEntity in Repository
     *
     * @param tableForeignKey
     * @param hcd
     * @return foreign key value of repository row that holds Column Family SchemaEntity
     * @throws IOException if a remote or network exception occurs
     */
    private byte[] putColumnFamilySchemaEntity(byte[] tableForeignKey, HColumnDescriptor hcd, TableName tableName)
            throws IOException {
        Map<byte[], byte[]> entityAttributeMap = buildEntityAttributeMap(hcd.getValues(), hcd.getConfiguration());

        return putSchemaEntity(
                new RowId(SchemaEntityType.COLUMN_FAMILY.getRecordType(), tableForeignKey, hcd.getName()),
                entityAttributeMap, false);
    }

    /**
     * Invoked during serialization process, when fully-formed MTableDescriptor is submitted for
     * persistence (e.g., during importation of schema from external source).
     *
     * @param mtd MTableDescriptor
     * @return true if all serializations complete successfully
     * @throws IOException if a remote or network exception occurs
     */
    boolean putColumnAuditorSchemaEntities(MTableDescriptor mtd) throws IOException {
        if (!isIncludedTable(mtd.getTableName())) {
            return false;
        }
        byte[] tableForeignKey = getTableForeignKey(mtd);
        boolean serializationCompleted = true;
        for (MColumnDescriptor mcd : mtd.getMColumnDescriptorArray()) {
            byte[] colDescForeignKey = getForeignKey(SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                    tableForeignKey, mcd.getName());
            if (colDescForeignKey == null) {
                serializationCompleted = false;
                continue;
            }
            for (ColumnAuditor columnAuditor : mcd.getColumnAuditors()) {
                byte[] colForeignKey = putColumnAuditorSchemaEntity(colDescForeignKey, columnAuditor);
                if (colForeignKey == null) {
                    serializationCompleted = false;
                }
            }
        }
        return serializationCompleted;
    }

    /**
     * Private invocation.
     *
     * @param colFamilyForeignKey
     * @param columnAuditor
     * @return foreign key value of repository row that holds {@link ColumnAuditor} SchemaEntity
     * @throws IOException if a remote or network exception occurs
     */
    private byte[] putColumnAuditorSchemaEntity(byte[] colFamilyForeignKey, ColumnAuditor columnAuditor)
            throws IOException {
        Map<byte[], byte[]> entityAttributeMap = buildEntityAttributeMap(columnAuditor.getValues(),
                columnAuditor.getConfiguration());

        return putSchemaEntity(new RowId(SchemaEntityType.COLUMN_AUDITOR.getRecordType(), colFamilyForeignKey,
                columnAuditor.getName()), entityAttributeMap, false);
    }

    /**
     * Invoked by MTableMultiplexer.
     *
     * @param tableName TableName
     * @param mutations List of Mutations
     * @throws IOException if a remote or network exception occurs
     */
    void putColumnAuditorSchemaEntities(TableName tableName, List<? extends Mutation> mutations)
            throws IOException {
        if (!isIncludedTable(tableName)) {
            return;
        }
        MTableDescriptor mtd = getMTableDescriptor(tableName);
        for (Mutation mutation : mutations) {
            putColumnAuditorSchemaEntities(mtd, mutation);
        }
    }

    /**
     * Invoked by MTableMultiplexer.
     *
     * @param tableName TableName
     * @param mutation Mutation
     * @throws IOException if a remote or network exception occurs
     */
    void putColumnAuditorSchemaEntities(TableName tableName, Mutation mutation) throws IOException {
        if (!isIncludedTable(tableName)) {
            return;
        }
        putColumnAuditorSchemaEntities(getMTableDescriptor(tableName), mutation);
    }

    /**
     * Invoked by MTable for real-time audit of mutations.
     *
     * @param mtd MTableDescriptor
     * @param mutations RowMutations
     * @throws IOException if a remote or network exception occurs
     */
    void putColumnAuditorSchemaEntities(MTableDescriptor mtd, RowMutations mutations) throws IOException {
        if (!isIncludedTable(mtd.getTableName())) {
            return;
        }
        for (Mutation mutation : mutations.getMutations()) {
            putColumnAuditorSchemaEntities(mtd, mutation);
        }
    }

    /**
     * Invoked by MBufferedMutator
     *
     * @param mtd MTableDescriptor
     * @param mutations RowMutations
     * @throws IOException if a remote or network exception occurs
     */
    void putColumnAuditorSchemaEntities(MTableDescriptor mtd, List<? extends Mutation> mutations)
            throws IOException {
        if (!isIncludedTable(mtd.getTableName())) {
            return;
        }
        for (Mutation mutation : mutations) {
            putColumnAuditorSchemaEntities(mtd, mutation);
        }
    }

    /**
     * Invoked at application runtime to persist {@link ColumnAuditor} SchemaEntity in the Repository
     * (invoked after user application successfully invokes a {@code Mutation} to a table).
     *
     * @param mtd ColumnManager TableDescriptor -- deserialized from Repository
     * @param mutation object from which column SchemaEntity is extracted
     * @throws IOException if a remote or network exception occurs
     */
    void putColumnAuditorSchemaEntities(MTableDescriptor mtd, Mutation mutation) throws IOException {
        if (!isIncludedTable(mtd.getTableName())
                // column-cell deletes do not affect Repository
                || Delete.class.isAssignableFrom(mutation.getClass())) {
            return;
        }
        for (Entry<byte[], List<Cell>> colFamilyCellList : mutation.getFamilyCellMap().entrySet()) {
            MColumnDescriptor mcd = mtd.getMColumnDescriptor(colFamilyCellList.getKey());
            for (Cell cell : colFamilyCellList.getValue()) {
                byte[] colQualifier = Bytes.copy(cell.getQualifierArray(), cell.getQualifierOffset(),
                        cell.getQualifierLength());
                ColumnAuditor oldColAuditor = getColumnAuditor(mcd.getForeignKey(), colQualifier);
                if (oldColAuditor != null && cell.getValueLength() <= oldColAuditor.getMaxValueLengthFound()) {
                    continue;
                }
                ColumnAuditor newColAuditor = new ColumnAuditor(colQualifier);
                if (oldColAuditor == null || cell.getValueLength() > oldColAuditor.getMaxValueLengthFound()) {
                    newColAuditor.setMaxValueLengthFound(cell.getValueLength());
                } else {
                    newColAuditor.setMaxValueLengthFound(oldColAuditor.getMaxValueLengthFound());
                }
                boolean suppressUserName = (oldColAuditor == null) ? false : true;
                Map<byte[], byte[]> entityAttributeMap = buildEntityAttributeMap(newColAuditor.getValues(),
                        newColAuditor.getConfiguration());
                putSchemaEntity(new RowId(SchemaEntityType.COLUMN_AUDITOR.getRecordType(), mcd.getForeignKey(),
                        newColAuditor.getName()), entityAttributeMap, suppressUserName);
            }
        }
    }

    /**
     * Invoked as part of discovery process. This method also invoked directly from mapreduce
     * discovery.
     *
     * @param mtd table descriptor for parent table of columns found in row
     * @param row Result object from which {@link ColumnAuditor} SchemaEntity is extracted
     * @throws IOException if a remote or network exception occurs
     */
    void putDiscoveredColumnAuditors(MTableDescriptor mtd, Result row, boolean keyOnlyFilterUsed)
            throws IOException {
        if (!isIncludedTable(mtd.getTableName())) {
            return;
        }
        for (Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> familyToColumnsMapEntry : row.getMap()
                .entrySet()) {
            MColumnDescriptor mcd = mtd.getMColumnDescriptor(familyToColumnsMapEntry.getKey());
            for (Entry<byte[], NavigableMap<Long, byte[]>> colEntry : familyToColumnsMapEntry.getValue()
                    .entrySet()) {
                byte[] colQualifier = colEntry.getKey();
                RowId rowId = new RowId(SchemaEntityType.COLUMN_AUDITOR.getRecordType(), mcd.getForeignKey(),
                        colQualifier);
                Set<Entry<Long, byte[]>> cellEntries = colEntry.getValue().entrySet();
                for (Entry<Long, byte[]> cellEntry : cellEntries) {
                    int colValueLength;
                    if (keyOnlyFilterUsed) {
                        colValueLength = Bytes.toInt(cellEntry.getValue()); // value *length* returned as value
                    } else {
                        colValueLength = cellEntry.getValue().length;
                    }
                    ColumnAuditor oldColAuditor = getColumnAuditor(mcd.getForeignKey(), colQualifier);
                    if (oldColAuditor != null && colValueLength <= oldColAuditor.getMaxValueLengthFound()) {
                        continue;
                    }
                    ColumnAuditor newColAuditor = new ColumnAuditor(colQualifier);
                    if (oldColAuditor == null || colValueLength > oldColAuditor.getMaxValueLengthFound()) {
                        newColAuditor.setMaxValueLengthFound(colValueLength);
                    } else {
                        newColAuditor.setMaxValueLengthFound(oldColAuditor.getMaxValueLengthFound());
                    }
                    boolean suppressUserName = false;
                    if (oldColAuditor != null) {
                        suppressUserName = true;
                    }
                    Map<byte[], byte[]> entityAttributeMap = buildEntityAttributeMap(newColAuditor.getValues(),
                            newColAuditor.getConfiguration());
                    putSchemaEntity(rowId, entityAttributeMap, suppressUserName);
                }
                repositoryTable.incrementColumnValue(rowId.getByteArray(), REPOSITORY_CF, COL_COUNTER_QUALIFIER, 1);
                repositoryTable.incrementColumnValue(rowId.getByteArray(), REPOSITORY_CF, CELL_COUNTER_QUALIFIER,
                        cellEntries.size());
            }
        }
    }

    /**
     * Invoked administratively to persist administrator-managed {@link ColumnAuditor}s in
     * Repository.
     *
     * @param tableName name of <i>Table</i> to which {@link ColumnDefinition}s are to be added
     * @param colFamily <i>Column Family</i> to which {@link ColumnDefinition}>s are to be added
     * @param colAuditors List of {@link ColumnAuditor}s to be added or modified
     * @return true if all puts complete successfully
     * @throws IOException if a remote or network exception occurs
     */
    boolean putColumnAuditorSchemaEntities(TableName tableName, byte[] colFamily, List<ColumnAuditor> colAuditors)
            throws IOException {
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        boolean allPutsCompleted = false;
        byte[] colFamilyForeignKey = getForeignKey(SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                getTableForeignKey(tableName), colFamily);
        if (colFamilyForeignKey != null) {
            allPutsCompleted = true;
            for (ColumnAuditor colDefinition : colAuditors) {
                byte[] columnForeignKey = putColumnAuditorSchemaEntity(colFamilyForeignKey, colDefinition);
                if (columnForeignKey == null) {
                    allPutsCompleted = false;
                }
            }
        }
        return allPutsCompleted;
    }

    void validateColumns(MTableDescriptor mtd, Mutation mutation) throws IOException {
        if (!isIncludedTable(mtd.getTableName()) || !mtd.hasColDescriptorWithColDefinitionsEnforced()
                || Delete.class.isAssignableFrom(mutation.getClass())) { // Deletes not validated
            return;
        }
        for (Entry<byte[], List<Cell>> colFamilyCellList : mutation.getFamilyCellMap().entrySet()) {
            MColumnDescriptor mcd = mtd.getMColumnDescriptor(colFamilyCellList.getKey());
            if (!mcd.columnDefinitionsEnforced()) {
                continue;
            }
            for (Cell cell : colFamilyCellList.getValue()) {
                byte[] colQualifier = Bytes.copy(cell.getQualifierArray(), cell.getQualifierOffset(),
                        cell.getQualifierLength());
                ColumnDefinition colDefinition = mcd.getColumnDefinition(colQualifier);
                if (colDefinition == null) {
                    throw new ColumnDefinitionNotFoundException(mtd.getTableName().getName(), mcd.getName(),
                            colQualifier, null);
                }
                if (colDefinition.getColumnLength() > 0
                        && cell.getValueLength() > colDefinition.getColumnLength()) {
                    throw new ColumnValueInvalidException(mtd.getTableName().getName(), mcd.getName(), colQualifier,
                            null,
                            "Value length of <" + cell.getValueLength() + "> is longer than maximum length of <"
                                    + colDefinition.getColumnLength() + "> defined for the column in its "
                                    + "corresponding ColumnDefinition.");
                }
                String colValidationRegex = colDefinition.getColumnValidationRegex();
                if (colValidationRegex != null && colValidationRegex.length() > 0) {
                    byte[] colValue = Bytes.getBytes(CellUtil.getValueBufferShallowCopy(cell));
                    if (!Bytes.toString(colValue).matches(colValidationRegex)) {
                        throw new ColumnValueInvalidException(mtd.getTableName().getName(), mcd.getName(),
                                colQualifier, colValue,
                                "Value does not match the regular expression defined for the column in its "
                                        + "corresponding ColumnDefinition: <" + colValidationRegex + ">");
                    }
                }
            }
        }
    }

    void validateColumns(MTableDescriptor mtd, RowMutations mutations) throws IOException {
        if (isIncludedTable(mtd.getTableName()) && mtd.hasColDescriptorWithColDefinitionsEnforced()) {
            for (Mutation mutation : mutations.getMutations()) {
                validateColumns(mtd, mutation);
            }
        }
    }

    void validateColumns(MTableDescriptor mtd, List<? extends Mutation> mutations) throws IOException {
        if (isIncludedTable(mtd.getTableName()) && mtd.hasColDescriptorWithColDefinitionsEnforced()) {
            for (Mutation mutation : mutations) {
                validateColumns(mtd, mutation);
            }
        }
    }

    void validateColumns(TableName tableName, Mutation mutation) throws IOException {
        if (!isIncludedTable(tableName)) {
            return;
        }
        MTableDescriptor mtd = getMTableDescriptor(tableName);
        if (mtd != null && mtd.hasColDescriptorWithColDefinitionsEnforced()) {
            validateColumns(mtd, mutation);
        }
    }

    void validateColumns(TableName tableName, List<? extends Mutation> mutations) throws IOException {
        if (!isIncludedTable(tableName)) {
            return;
        }
        MTableDescriptor mtd = getMTableDescriptor(tableName);
        if (mtd != null && mtd.hasColDescriptorWithColDefinitionsEnforced()) {
            for (Mutation mutation : mutations) {
                validateColumns(mtd, mutation);
            }
        }
    }

    /**
     * Invoked during importation (from external source) process, when fully-formed MTableDescriptor
     * is submitted for persistence.
     *
     * @param mtd MTableDescriptor
     * @return true if all serializations complete successfully
     * @throws IOException if a remote or network exception occurs
     */
    private boolean putColumnDefinitionSchemaEntities(MTableDescriptor mtd) throws IOException {
        if (!isIncludedTable(mtd.getTableName())) {
            throw new TableNotIncludedForProcessingException(mtd.getTableName().getName(), null);
        }
        byte[] tableForeignKey = getTableForeignKey(mtd);
        boolean serializationCompleted = true;
        for (MColumnDescriptor mcd : mtd.getMColumnDescriptorArray()) {
            byte[] colDescForeignKey = getForeignKey(SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                    tableForeignKey, mcd.getName());
            if (colDescForeignKey == null) {
                serializationCompleted = false;
                continue;
            }
            for (ColumnDefinition colDef : mcd.getColumnDefinitions()) {
                byte[] colDefinitionForeignKey = putColumnDefinitionSchemaEntity(colDescForeignKey, colDef);
                if (colDefinitionForeignKey == null) {
                    serializationCompleted = false;
                }
            }
        }
        return serializationCompleted;
    }

    /**
     * Invoked administratively to persist administrator-managed {@link ColumnDefinition}s in
     * Repository.
     *
     * @param tableName name of <i>Table</i> to which {@link ColumnDefinition}s are to be added
     * @param colFamily <i>Column Family</i> to which {@link ColumnDefinition}>s are to be added
     * @param colDefinitions List of {@link ColumnDefinition}s to be added or modified
     * @return true if all puts complete successfully
     * @throws IOException if a remote or network exception occurs
     */
    boolean putColumnDefinitionSchemaEntities(TableName tableName, byte[] colFamily,
            List<ColumnDefinition> colDefinitions) throws IOException {
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        boolean allPutsCompleted = false;
        byte[] colFamilyForeignKey = getForeignKey(SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                getTableForeignKey(tableName), colFamily);
        if (colFamilyForeignKey != null) {
            allPutsCompleted = true;
            for (ColumnDefinition colDefinition : colDefinitions) {
                byte[] columnForeignKey = putColumnDefinitionSchemaEntity(colFamilyForeignKey, colDefinition);
                if (columnForeignKey == null) {
                    allPutsCompleted = false;
                }
            }
        }
        return allPutsCompleted;
    }

    /**
     * Private invocation.
     *
     * @param colFamilyForeignKey
     * @param colDef
     * @return foreign key value of repository row that holds {@link ColumnDefinition} SchemaEntity
     * @throws IOException if a remote or network exception occurs
     */
    private byte[] putColumnDefinitionSchemaEntity(byte[] colFamilyForeignKey, ColumnDefinition colDef)
            throws IOException {
        Map<byte[], byte[]> entityAttributeMap = buildEntityAttributeMap(colDef.getValues(),
                colDef.getConfiguration());

        return putSchemaEntity(new RowId(SchemaEntityType.COLUMN_DEFINITION.getRecordType(), colFamilyForeignKey,
                colDef.getName()), entityAttributeMap, false);
    }

    private Map<byte[], byte[]> buildEntityAttributeMap(Map<ImmutableBytesWritable, ImmutableBytesWritable> values,
            Map<String, String> configuration) {
        Map<byte[], byte[]> entityAttributeMap = new TreeMap<>(Bytes.BYTES_RAWCOMPARATOR);
        for (Entry<ImmutableBytesWritable, ImmutableBytesWritable> valueEntry : values.entrySet()) {
            byte[] attributeKeySuffix = valueEntry.getKey().get();
            byte[] attributeValue = valueEntry.getValue().get();
            if (attributeKeySuffix.length > COUNTER_COLUMN_PREFIX_BYTES.length
                    && Bytes.startsWith(attributeKeySuffix, COUNTER_COLUMN_PREFIX_BYTES)) {
                continue; // bypass all counters
            }
            if (attributeKeySuffix.length > TIMESTAMP_KEY_PREFIX_BYTES.length
                    && Bytes.startsWith(attributeKeySuffix, TIMESTAMP_KEY_PREFIX_BYTES)) {
                continue; // bypass all timestamps
            }
            ByteBuffer attributeKey = ByteBuffer
                    .allocate(VALUE_COLUMN_PREFIX_BYTES.length + attributeKeySuffix.length);
            attributeKey.put(VALUE_COLUMN_PREFIX_BYTES).put(attributeKeySuffix);
            entityAttributeMap.put(Bytes.toBytes(attributeKey), attributeValue);
        }
        for (Entry<String, String> configEntry : configuration.entrySet()) {
            entityAttributeMap.put(Bytes.toBytes(CONFIG_COLUMN_PREFIX + configEntry.getKey()),
                    Bytes.toBytes(configEntry.getValue()));
        }

        return entityAttributeMap;
    }

    private byte[] putSchemaEntity(RowId rowId, Map<byte[], byte[]> entityAttributeMap, boolean suppressUserName)
            throws IOException {
        Result oldRow = repositoryTable.get(new Get(rowId.getByteArray()));
        Put newRow = new Put(rowId.getByteArray());
        Map<byte[], byte[]> oldEntityAttributeMap;

        // ADD Columns to newRow to set foreignKey and entityStatus values appropriately
        byte[] foreignKey;
        if (oldRow.isEmpty()) {
            oldEntityAttributeMap = null;
            foreignKey = generateUniqueForeignKey(); // note that foreignKey ignored in column-entities
            newRow.addColumn(REPOSITORY_CF, FOREIGN_KEY_COLUMN, foreignKey);
            newRow.addColumn(REPOSITORY_CF, ENTITY_STATUS_COLUMN, ACTIVE_STATUS);
        } else {
            SchemaEntity entity = deserializeSchemaEntity(oldRow);
            oldEntityAttributeMap = buildEntityAttributeMap(entity.getValues(), entity.getConfiguration());
            foreignKey = oldRow.getValue(REPOSITORY_CF, FOREIGN_KEY_COLUMN);
            if (!Bytes.equals(oldRow.getValue(REPOSITORY_CF, ENTITY_STATUS_COLUMN), ACTIVE_STATUS)) {
                newRow.addColumn(REPOSITORY_CF, ENTITY_STATUS_COLUMN, ACTIVE_STATUS);
            }
        }

        // ADD Columns to newRow based on changes in attributeValues
        for (Entry<byte[], byte[]> newAttribute : entityAttributeMap.entrySet()) {
            byte[] newAttributeKey = newAttribute.getKey();
            byte[] newAttributeValue = newAttribute.getValue();
            byte[] oldAttributeValue;
            if (oldEntityAttributeMap == null) {
                oldAttributeValue = null;
            } else {
                oldAttributeValue = oldEntityAttributeMap.get(newAttributeKey);
            }
            boolean attributeValueChanged = false;
            if (oldAttributeValue == null) {
                if (newAttributeValue != null) {
                    attributeValueChanged = true;
                }
            } else {
                if (!Bytes.equals(oldAttributeValue, newAttributeValue)) {
                    attributeValueChanged = true;
                }
            }
            if (attributeValueChanged) {
                if (newAttributeValue == null) {
                    newRow.addColumn(REPOSITORY_CF, newAttributeKey, null);
                } else {
                    newRow.addColumn(REPOSITORY_CF, newAttributeKey, newAttributeValue);
                }
            }
        }

        // ADD Columns to newRow to nullify value if attribute is in old map but NOT in new map
        if (oldEntityAttributeMap != null) {
            for (byte[] oldAttributeKey : oldEntityAttributeMap.keySet()) {
                if (!entityAttributeMap.containsKey(oldAttributeKey)) {
                    newRow.addColumn(REPOSITORY_CF, oldAttributeKey, null);
                }
            }
        }

        // PUT newRow to Repository
        if (!newRow.isEmpty()) {
            if (!suppressUserName) {
                newRow.addColumn(REPOSITORY_CF, JAVA_USERNAME_PROPERTY_KEY, javaUsername);
            }
            if (rowId.entityType == SchemaEntityType.COLUMN_AUDITOR.getRecordType()) {
                List<Cell> maxValueLengthCells = newRow.get(REPOSITORY_CF,
                        ColumnAuditor.MAX_VALUE_LENGTH_KEY_BYTES);
                if (maxValueLengthCells == null || maxValueLengthCells.size() == 0) {
                    repositoryTable.put(newRow);
                } else {
                    // #checkAndPut to prevent bogus overlay of maxValueLength when submitted via mapReduce
                    repositoryTable.checkAndPut(rowId.getByteArray(), REPOSITORY_CF,
                            ColumnAuditor.MAX_VALUE_LENGTH_KEY_BYTES, CompareFilter.CompareOp.LESS,
                            maxValueLengthCells.get(0).getValueArray(), newRow);
                }

            } else {
                repositoryTable.put(newRow);
            }
        }
        return foreignKey;
    }

    MNamespaceDescriptor getMNamespaceDescriptor(String namespaceName) throws IOException {
        Result row = getActiveRow(SchemaEntityType.NAMESPACE.getRecordType(), NAMESPACE_PARENT_FOREIGN_KEY,
                Bytes.toBytes(namespaceName), null);
        if (row == null || row.isEmpty()) {
            // Note that ColumnManager can be installed atop an already-existing HBase
            //  installation, so namespace SchemaEntity might not yet have been captured in repository,
            //  or namespaceName may not represent included namespace (and so not stored in repository).
            if (isIncludedNamespace(namespaceName)) {
                putNamespaceSchemaEntity(standardAdmin.getNamespaceDescriptor(namespaceName));
                row = getActiveRow(SchemaEntityType.NAMESPACE.getRecordType(), NAMESPACE_PARENT_FOREIGN_KEY,
                        Bytes.toBytes(namespaceName), null);
            } else {
                return null;
            }
        }
        MNamespaceDescriptor nd = new MNamespaceDescriptor(deserializeSchemaEntity(row));
        return nd;
    }

    Set<MNamespaceDescriptor> getMNamespaceDescriptors() throws IOException {
        Set<MNamespaceDescriptor> mNamespaceDescriptors = new TreeSet<>();
        for (Result row : getActiveRows(SchemaEntityType.NAMESPACE.getRecordType(), NAMESPACE_PARENT_FOREIGN_KEY)) {
            mNamespaceDescriptors.add(new MNamespaceDescriptor(deserializeSchemaEntity(row)));
        }
        return mNamespaceDescriptors;
    }

    MTableDescriptor getMTableDescriptor(TableName tn) throws IOException {
        byte[] namespaceForeignKey = getNamespaceForeignKey(tn.getNamespace());
        Result row = getActiveRow(SchemaEntityType.TABLE.getRecordType(), namespaceForeignKey, tn.getName(), null);
        if (row == null || row.isEmpty()) {
            // Note that ColumnManager can be installed atop an already-existing HBase
            //  installation, so table SchemaEntity might not yet have been captured in repository,
            //  or TableName may not represent included Table (and so not stored in repository).
            if (isIncludedTable(tn)) {
                putTableSchemaEntity(standardAdmin.getTableDescriptor(tn));
                row = getActiveRow(SchemaEntityType.TABLE.getRecordType(), namespaceForeignKey, tn.getName(), null);
            } else {
                return null;
            }
        }
        MTableDescriptor mtd = new MTableDescriptor(deserializeSchemaEntity(row));
        for (MColumnDescriptor mcd : getMColumnDescriptors(mtd.getForeignKey())) {
            mtd.addFamily(mcd);
        }
        return mtd;
    }

    Set<MTableDescriptor> getMTableDescriptors(byte[] namespaceForeignKey) throws IOException {
        Set<MTableDescriptor> mTableDescriptors = new TreeSet<>();
        for (Result row : getActiveRows(SchemaEntityType.TABLE.getRecordType(), namespaceForeignKey)) {
            MTableDescriptor mtd = new MTableDescriptor(deserializeSchemaEntity(row));
            for (MColumnDescriptor mcd : getMColumnDescriptors(mtd.getForeignKey())) {
                mtd.addFamily(mcd);
            }
            mTableDescriptors.add(mtd);
        }
        return mTableDescriptors;
    }

    private Set<MColumnDescriptor> getMColumnDescriptors(byte[] tableForeignKey) throws IOException {
        Set<MColumnDescriptor> mColumnDescriptors = new TreeSet<>();
        for (Result row : getActiveRows(SchemaEntityType.COLUMN_FAMILY.getRecordType(), tableForeignKey)) {
            MColumnDescriptor mcd = new MColumnDescriptor(deserializeSchemaEntity(row));
            mColumnDescriptors.add(mcd.addColumnAuditors(getColumnAuditors(mcd.getForeignKey()))
                    .addColumnDefinitions(getColumnDefinitions(mcd.getForeignKey())));
        }
        return mColumnDescriptors;
    }

    private MColumnDescriptor getMColumnDescriptor(byte[] tableForeignKey, byte[] colFamily) throws IOException {
        Result row = getActiveRow(SchemaEntityType.COLUMN_FAMILY.getRecordType(), tableForeignKey, colFamily, null);
        return row == null ? null : new MColumnDescriptor(deserializeSchemaEntity(row));
    }

    Set<ColumnAuditor> getColumnAuditors(HTableDescriptor htd, HColumnDescriptor hcd) throws IOException {
        if (!isIncludedTable(htd.getTableName())) {
            throw new TableNotIncludedForProcessingException(htd.getTableName().getName(), null);
        }
        byte[] colFamilyForeignKey = getForeignKey(SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                getTableForeignKey(htd), hcd.getName());
        return (colFamilyForeignKey == null) ? null : getColumnAuditors(colFamilyForeignKey);
    }

    private Set<ColumnAuditor> getColumnAuditors(byte[] colFamilyForeignKey) throws IOException {
        Set<ColumnAuditor> columnAuditors = new TreeSet<>();
        Result[] colAuditorRows = getActiveRows(SchemaEntityType.COLUMN_AUDITOR.getRecordType(),
                colFamilyForeignKey);
        if (colAuditorRows != null) {
            for (Result row : colAuditorRows) {
                columnAuditors.add(new ColumnAuditor(deserializeSchemaEntity(row)));
            }
        }
        return columnAuditors;
    }

    private ColumnAuditor getColumnAuditor(byte[] colFamilyForeignKey, byte[] colQualifier) throws IOException {
        Result row = getActiveRow(SchemaEntityType.COLUMN_AUDITOR.getRecordType(), colFamilyForeignKey,
                colQualifier, null);
        return (row == null) ? null : new ColumnAuditor(deserializeSchemaEntity(row));
    }

    Set<ColumnDefinition> getColumnDefinitions(HTableDescriptor htd, HColumnDescriptor hcd) throws IOException {
        if (!isIncludedTable(htd.getTableName())) {
            throw new TableNotIncludedForProcessingException(htd.getTableName().getName(), null);
        }
        byte[] colFamilyForeignKey = getForeignKey(SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                getTableForeignKey(htd), hcd.getName());
        return getColumnDefinitions(colFamilyForeignKey);
    }

    private Set<ColumnDefinition> getColumnDefinitions(byte[] colFamilyForeignKey) throws IOException {
        Set<ColumnDefinition> columnDefinitions = new TreeSet<>();
        for (Result row : getActiveRows(SchemaEntityType.COLUMN_DEFINITION.getRecordType(), colFamilyForeignKey)) {
            columnDefinitions.add(new ColumnDefinition(deserializeSchemaEntity(row)));
        }
        return columnDefinitions;
    }

    private ColumnDefinition getColumnDefinition(byte[] colFamilyForeignKey, byte[] colQualifier)
            throws IOException {
        Result row = getActiveRow(SchemaEntityType.COLUMN_DEFINITION.getRecordType(), colFamilyForeignKey,
                colQualifier, null);
        return (row == null) ? null : new ColumnDefinition(deserializeSchemaEntity(row));
    }

    private SchemaEntity deserializeSchemaEntity(Result row) {
        if (row == null || row.isEmpty()) {
            return null;
        }
        RowId rowId = new RowId(row.getRow());
        SchemaEntity entity = new SchemaEntity(rowId.getEntityType(), rowId.getEntityName());
        // full #getMap required to extract timestamps of counter columns
        for (Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> familyToCellsMapEntry : row.getMap()
                .entrySet()) {
            for (Entry<byte[], NavigableMap<Long, byte[]>> colEntry : familyToCellsMapEntry.getValue().entrySet()) {
                byte[] key = colEntry.getKey();
                for (Entry<Long, byte[]> cellEntry : colEntry.getValue().entrySet()) {
                    byte[] value = cellEntry.getValue();
                    if (Bytes.equals(key, FOREIGN_KEY_COLUMN)) {
                        entity.setForeignKey(cellEntry.getValue());
                    } else if (key.length > VALUE_COLUMN_PREFIX_BYTES.length
                            && Bytes.startsWith(key, VALUE_COLUMN_PREFIX_BYTES)) {
                        entity.setValue(Bytes.tail(key, key.length - VALUE_COLUMN_PREFIX_BYTES.length), value);
                    } else if (key.length > COUNTER_COLUMN_PREFIX_BYTES.length
                            && Bytes.startsWith(key, COUNTER_COLUMN_PREFIX_BYTES)) {
                        entity.setValue(key, value);
                        if (Bytes.equals(key, COL_COUNTER_QUALIFIER)) {
                            entity.setValue(COL_COUNTER_TIMESTAMP_KEY, Bytes.toBytes(cellEntry.getKey()));
                        } else if (Bytes.equals(key, CELL_COUNTER_QUALIFIER)) {
                            entity.setValue(CELL_COUNTER_TIMESTAMP_KEY, Bytes.toBytes(cellEntry.getKey()));
                        }
                    } else if (key.length > CONFIG_COLUMN_PREFIX_BYTES.length
                            && Bytes.startsWith(key, CONFIG_COLUMN_PREFIX_BYTES)) {
                        entity.setConfiguration(
                                Bytes.toString(Bytes.tail(key, key.length - CONFIG_COLUMN_PREFIX_BYTES.length)),
                                Bytes.toString(cellEntry.getValue()));
                    }
                    break;
                }
            }
        }
        return entity;
    }

    private Result getActiveRow(byte recordType, byte[] parentForeignKey, byte[] entityName, byte[] columnToGet)
            throws IOException {
        Result[] rows = getActiveRows(false, recordType, parentForeignKey, entityName, columnToGet);
        return (rows == null || rows.length == 0) ? null : rows[0];
    }

    private Result[] getActiveRows(byte recordType, byte[] parentForeignKey) throws IOException {
        return getActiveRows(false, recordType, parentForeignKey, null, null);
    }

    private Result[] getActiveRows(boolean getRowIdAndStatusOnly, byte recordType, byte[] parentForeignKey,
            byte[] entityName, byte[] columnToGet) throws IOException {
        SingleColumnValueFilter activeRowsOnlyFilter = new SingleColumnValueFilter(REPOSITORY_CF,
                ENTITY_STATUS_COLUMN, CompareFilter.CompareOp.EQUAL, ACTIVE_STATUS);
        activeRowsOnlyFilter.setFilterIfMissing(true);
        return getRepositoryRows(getRowIdAndStatusOnly, recordType, parentForeignKey, entityName, columnToGet,
                activeRowsOnlyFilter);
    }

    private Result[] getRepositoryRows(byte recordType, byte[] parentForeignKey, byte[] columnToGet)
            throws IOException {
        return getRepositoryRows(false, recordType, parentForeignKey, null, columnToGet, null);
    }

    private Result[] getRepositoryRows(boolean getRowIdAndStatusOnly, byte recordType, byte[] parentForeignKey,
            byte[] entityName, byte[] columnToGet, Filter filter) throws IOException {
        if (parentForeignKey == null) {
            return null;
        }
        RowId startRowId = new RowId(recordType, parentForeignKey, entityName);
        byte[] stopRowId = startRowId.getStopRowIdByteArray();
        Scan scanParms = new Scan(startRowId.getByteArray(), stopRowId);
        if (getRowIdAndStatusOnly || columnToGet != null) {
            scanParms.addColumn(REPOSITORY_CF, FOREIGN_KEY_COLUMN);
            scanParms.addColumn(REPOSITORY_CF, ENTITY_STATUS_COLUMN);
            if (columnToGet != null) {
                scanParms.addColumn(REPOSITORY_CF, columnToGet);
            }
        }
        if (filter != null) {
            scanParms.setFilter(filter);
        }
        List<Result> rows = new ArrayList<>();
        try (ResultScanner results = repositoryTable.getScanner(scanParms)) {
            for (Result row : results) {
                rows.add(row);
            }
        }
        return rows.toArray(new Result[rows.size()]);
    }

    /**
     * Returns foreign key via lookup on Repository Table. The three parameters concatenated together
     * comprise the row's unique RowId.
     *
     * @param recordType
     * @param parentForeignKey
     * @param entityName
     * @return Entity's foreign key
     * @throws IOException if a remote or network exception occurs
     */
    private byte[] getForeignKey(byte recordType, byte[] parentForeignKey, byte[] entityName) throws IOException {
        if (parentForeignKey == null || entityName == null) {
            return null;
        }
        Result row = repositoryTable
                .get(new Get(new RowId(recordType, parentForeignKey, entityName).getByteArray()));
        return row.isEmpty() ? null : row.getValue(REPOSITORY_CF, FOREIGN_KEY_COLUMN);
    }

    private byte[] getNamespaceForeignKey(byte[] namespace) throws IOException {
        byte[] namespaceForeignKey = getForeignKey(SchemaEntityType.NAMESPACE.getRecordType(),
                NAMESPACE_PARENT_FOREIGN_KEY, namespace);
        // Note that ColumnManager could be installed atop an already-existing HBase
        //  installation, so namespace SchemaEntity might not be in repository the first
        //  time its foreign key is accessed or one of its descendents is modified.
        if (namespaceForeignKey == null) {
            if (namespaceExists(standardAdmin, namespace)) {
                namespaceForeignKey = putNamespaceSchemaEntity(
                        standardAdmin.getNamespaceDescriptor(Bytes.toString(namespace)));
            }
        }
        return namespaceForeignKey;
    }

    /**
     * Returns Table's foreign key via lookup on Repository Table.
     *
     * @param tableName
     * @return Table's foreign key value
     * @throws IOException if a remote or network exception occurs
     */
    private byte[] getTableForeignKey(TableName tableName) throws IOException {
        byte[] namespaceForeignKey = getNamespaceForeignKey(tableName.getNamespace());
        byte[] tableForeignKey = getForeignKey(SchemaEntityType.TABLE.getRecordType(), namespaceForeignKey,
                tableName.getName());
        // Note that ColumnManager could be installed atop an already-existing HBase
        //  installation, so table SchemaEntity might not be in repository the first
        //  time its foreign key is accessed or one of its descendents is modified.
        if (tableForeignKey == null) {
            if (standardAdmin.tableExists(tableName)) {
                tableForeignKey = putTableSchemaEntity(standardAdmin.getTableDescriptor(tableName));
            }
        }
        return tableForeignKey;
    }

    /**
     * Returns Table's foreign key via lookup on Repository Table.
     *
     * @param table
     * @return Table's foreign key value
     * @throws IOException if a remote or network exception occurs
     */
    private byte[] getTableForeignKey(Table table) throws IOException {
        return getTableForeignKey(table.getName());
    }

    /**
     * Returns Table's foreign key via lookup on Repository Table.
     *
     * @param htd HTableDescriptor of table
     * @return Table's foreign key value
     * @throws IOException if a remote or network exception occurs
     */
    private byte[] getTableForeignKey(HTableDescriptor htd) throws IOException {
        return getTableForeignKey(htd.getTableName());
    }

    boolean columnDefinitionsEnforced(TableName tableName, byte[] colFamily) throws IOException {
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        MColumnDescriptor mcd = getMColumnDescriptor(getTableForeignKey(tableName), colFamily);
        return (mcd == null) ? false : mcd.columnDefinitionsEnforced();
    }

    void enableColumnDefinitionEnforcement(boolean enabled, TableName tableName, byte[] colFamily)
            throws IOException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        byte[] tableForeignKey = getTableForeignKey(tableName);
        MColumnDescriptor mcd = getMColumnDescriptor(tableForeignKey, colFamily);
        if (mcd == null) {
            return;
        }
        if (mcd.columnDefinitionsEnforced() != enabled) {
            mcd.enableColumnDefinitionEnforcement(enabled);
            putColumnFamilySchemaEntity(tableForeignKey, mcd, tableName);
        }
    }

    void enableColumnAliases(boolean enabled, TableName tableName, byte[] colFamily) throws IOException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        byte[] tableForeignKey = getTableForeignKey(tableName);
        MColumnDescriptor mcd = getMColumnDescriptor(tableForeignKey, colFamily);
        if (mcd == null) {
            return;
        }
        if (mcd.columnAliasesEnabled() != enabled) {
            mcd.enableColumnAliases(enabled);
            putColumnFamilySchemaEntity(tableForeignKey, mcd, tableName);
        }
    }

    private String buildOrderedCommaDelimitedString(List<String> list) {
        Set<String> set = new TreeSet<>(list);
        StringBuilder stringBuilder = new StringBuilder();
        int itemCount = 0;
        for (String item : set) {
            stringBuilder.append(item);
            if (++itemCount < set.size()) {
                stringBuilder.append(',');
            }
        }
        return stringBuilder.toString();
    }

    void purgeNamespaceShemaEntity(String name) throws IOException {
        deleteNamespaceSchemaEntity(true, name);
    }

    void deleteNamespaceSchemaEntity(String name) throws IOException {
        deleteNamespaceSchemaEntity(false, name);
    }

    private void deleteNamespaceSchemaEntity(boolean purge, String name) throws IOException {
        deleteSchemaEntity(purge, false, SchemaEntityType.NAMESPACE.getRecordType(), NAMESPACE_PARENT_FOREIGN_KEY,
                Bytes.toBytes(name));
    }

    void purgeTableSchemaEntity(TableName tableName) throws IOException {
        deleteTableSchemaEntity(true, false, tableName);
    }

    void truncateTableColumns(TableName tableName) throws IOException {
        deleteTableSchemaEntity(false, true, tableName);
    }

    void deleteTableSchemaEntity(TableName tableName) throws IOException {
        deleteTableSchemaEntity(false, false, tableName);
    }

    private void deleteTableSchemaEntity(boolean purge, boolean truncateColumns, TableName tableName)
            throws IOException {
        byte[] namespaceForeignKey = getNamespaceForeignKey(tableName.getNamespace());
        deleteSchemaEntity(purge, truncateColumns, SchemaEntityType.TABLE.getRecordType(), namespaceForeignKey,
                tableName.getName());
    }

    void deleteColumnFamily(TableName tableName, byte[] name) throws IOException {
        deleteSchemaEntity(false, false, SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                getTableForeignKey(tableName), name);
    }

    /**
     * Used only in administrative deletion of {@link ColumnDefinition}
     *
     * @param tableName name of <i>Table</i> from which {@link ColumnDefinition} is to be deleted
     * @param colFamily <i>Column Family</i> from which {@link ColumnDefinition} is to be deleted
     * @param colQualifier alias that identifies the {@link ColumnDefinition} to be deleted
     * @throws IOException
     */
    void deleteColumnDefinition(TableName tableName, byte[] colFamily, byte[] colQualifier) throws IOException {
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        byte[] colFamilyForeignKey = getForeignKey(SchemaEntityType.COLUMN_FAMILY.getRecordType(),
                getTableForeignKey(tableName), colFamily);
        if (colFamilyForeignKey == null) {
            return;
        }
        // TO DO?: before (or as part of) conceptual deletion, reset
        //   ColumnDefinition's validation-related attributes
        deleteSchemaEntity(false, false, SchemaEntityType.COLUMN_DEFINITION.getRecordType(), colFamilyForeignKey,
                colQualifier);
    }

    private void deleteSchemaEntity(boolean purge, boolean truncateColumns, byte recordType,
            byte[] parentForeignKey, byte[] entityName) throws IOException {
        if (parentForeignKey == null) {
            return;
        }
        for (Result row : getRepositoryRows(true, recordType, parentForeignKey, entityName, null, null)) {
            if (!truncateColumns
                    || (truncateColumns && recordType == SchemaEntityType.COLUMN_AUDITOR.getRecordType())) {
                if (purge) {
                    repositoryTable.delete(new Delete(row.getRow()));
                } else {
                    if (!Bytes.equals(row.getValue(REPOSITORY_CF, ENTITY_STATUS_COLUMN), DELETED_STATUS)) {
                        repositoryTable.put(
                                new Put(row.getRow()).addColumn(REPOSITORY_CF, ENTITY_STATUS_COLUMN, DELETED_STATUS)
                                        .addColumn(REPOSITORY_CF, JAVA_USERNAME_PROPERTY_KEY, javaUsername));
                    }
                }
            }

            // cascade to child entities
            byte childRecordType;
            switch (SchemaEntityType.ENTITY_TYPE_BYTE_TO_ENUM_MAP.get(recordType)) {
            case NAMESPACE:
                childRecordType = SchemaEntityType.TABLE.getRecordType();
                break;
            case TABLE:
                childRecordType = SchemaEntityType.COLUMN_FAMILY.getRecordType();
                break;
            case COLUMN_FAMILY:
                childRecordType = SchemaEntityType.COLUMN_AUDITOR.getRecordType();
                break;
            case COLUMN_AUDITOR: // ColumnAuditors and ColumnDefinitions have no children!!
            case COLUMN_DEFINITION:
            default:
                continue;
            }
            deleteSchemaEntity(purge, truncateColumns, childRecordType,
                    row.getValue(REPOSITORY_CF, FOREIGN_KEY_COLUMN), null);
            if (childRecordType == SchemaEntityType.COLUMN_AUDITOR.getRecordType()) {
                deleteSchemaEntity(purge, truncateColumns, SchemaEntityType.COLUMN_DEFINITION.getRecordType(),
                        row.getValue(REPOSITORY_CF, FOREIGN_KEY_COLUMN), null);
            }
        }
    }

    final void discoverSchema(boolean includeColumnQualifiers, boolean includeAllCells, boolean useMapReduce)
            throws IOException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        for (NamespaceDescriptor nd : standardAdmin.listNamespaceDescriptors()) {
            if (!isIncludedNamespace(nd.getName())) {
                continue;
            }
            putNamespaceSchemaEntity(nd);
            for (HTableDescriptor htd : standardAdmin.listTableDescriptorsByNamespace(nd.getName())) {
                if (!isIncludedTable(htd.getTableName()) || standardAdmin.isTableDisabled(htd.getTableName())) {
                    continue;
                }
                discoverSchema(htd.getTableName(), includeColumnQualifiers, includeAllCells, useMapReduce);
            }
        }
    }

    final void discoverSchema(String namespace, boolean includeColumnQualifiers, boolean includeAllCells,
            boolean useMapReduce) throws IOException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        NamespaceDescriptor nd = getAdmin().getNamespaceDescriptor(namespace); // Exception if not found
        if (!isIncludedNamespace(namespace)) {
            throw new TableNotIncludedForProcessingException(
                    Bytes.toBytes(namespace + ALL_TABLES_WILDCARD_INDICATOR),
                    "NO table from namespace <" + namespace + "> is included for " + PRODUCT_NAME + " processing.");
        }
        putNamespaceSchemaEntity(nd);
        for (HTableDescriptor htd : standardAdmin.listTableDescriptorsByNamespace(nd.getName())) {
            if (!isIncludedTable(htd.getTableName()) || standardAdmin.isTableDisabled(htd.getTableName())) {
                continue;
            }
            discoverSchema(htd.getTableName(), includeColumnQualifiers, includeAllCells, useMapReduce);
        }
    }

    final void discoverSchema(TableName tableName, boolean includeColumnQualifiers, boolean includeAllCells,
            boolean useMapReduce) throws IOException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        putTableSchemaEntity(standardAdmin.getTableDescriptor(tableName));
        if (includeColumnQualifiers) {
            discoverColumnMetadata(tableName, includeAllCells, useMapReduce);
        }
    }

    private void discoverColumnMetadata(TableName tableName, boolean includeAllCells, boolean useMapReduce)
            throws IOException {
        MTableDescriptor mtd = getMTableDescriptor(tableName);
        if (mtd == null) {
            return;
        }
        // for any previously-discovered ColumnAuditors, reset counters
        for (MColumnDescriptor mcd : mtd.getMColumnDescriptors()) {
            for (ColumnAuditor colAuditor : mcd.getColumnAuditors()) {
                byte[] rowId = new RowId(SchemaEntityType.COLUMN_AUDITOR.getRecordType(), mcd.getForeignKey(),
                        colAuditor.getColumnQualifier()).getByteArray();
                long resetValue = repositoryTable.incrementColumnValue(rowId, REPOSITORY_CF, COL_COUNTER_QUALIFIER,
                        0) * -1;
                repositoryTable.incrementColumnValue(rowId, REPOSITORY_CF, COL_COUNTER_QUALIFIER, resetValue);
                resetValue = repositoryTable.incrementColumnValue(rowId, REPOSITORY_CF, CELL_COUNTER_QUALIFIER, 0)
                        * -1;
                repositoryTable.incrementColumnValue(rowId, REPOSITORY_CF, CELL_COUNTER_QUALIFIER, resetValue);
            }
        }
        // perform full scan w/ KeyOnlyFilter(true), so only col name & length returned
        if (useMapReduce) {
            try {
                int jobCompletionCode = ToolRunner.run(MConfiguration.create(), new ColumnDiscoveryTool(),
                        new String[] { TABLE_NAME_ARG_KEY + tableName.getNameAsString(),
                                INCLUDE_ALL_CELLS_ARG_KEY + includeAllCells });
                if (jobCompletionCode != 0) {
                    logger.warn("Mapreduce process failure in " + ColumnDiscoveryTool.class.getSimpleName());
                }
            } catch (Exception e) {
                if (IOException.class.isAssignableFrom(e.getClass())) {
                    throw (IOException) e;
                } else {
                    throw new IOException("Mapreduce process failure", e);
                }
            }
        } else {
            Table table = hbaseConnection.getTable(tableName);
            Scan colScan = new Scan().setFilter(new KeyOnlyFilter(true));
            if (includeAllCells) {
                colScan.setMaxVersions();
            }
            try (ResultScanner rows = table.getScanner(colScan)) {
                for (Result row : rows) {
                    putDiscoveredColumnAuditors(mtd, row, true);
                }
            }
        }
    }

    /**
     * Returns alias of alias if colFamily has columnAliasesEnabled; otherwise it simply
    returns the alias.
     *
     * @param mtd
     * @param colFamily
     * @param colQualifier
     * @return alias of alias if colFamily has columnAliasesEnabled; otherwise it simply
    returns the alias
     * @throws IOException
     */
    byte[] getAlias(final MTableDescriptor mtd, final byte[] colFamily, final byte[] colQualifier)
            throws IOException {
        if (mtd.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
            NavigableSet<byte[]> colQualifierSet = new TreeSet<>(Bytes.BYTES_RAWCOMPARATOR);
            colQualifierSet.add(colQualifier);
            return getQualifierToAliasMap(mtd.getTableName(), colFamily, colQualifierSet, false).get(colQualifier);
        } else {
            return colQualifier;
        }
    }

    NavigableMap<byte[], byte[]> getQualifierToAliasMap(TableName tableName, byte[] colFamily, List<Cell> cellList,
            boolean addAliasIfNotFound) throws IOException {
        NavigableSet<byte[]> colQualifierSet = new TreeSet<>(Bytes.BYTES_RAWCOMPARATOR);
        for (Cell cell : cellList) {
            colQualifierSet.add(
                    Bytes.copy(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()));
        }
        return getQualifierToAliasMap(tableName, colFamily, colQualifierSet, addAliasIfNotFound);
    }

    NavigableMap<byte[], byte[]> getQualifierToAliasMap(TableName tableName, byte[] colFamily,
            NavigableSet<byte[]> colQualifierSet, boolean addAliasIfNotFound) throws IOException {
        NavigableMap<byte[], byte[]> aliasMap = new TreeMap<>(Bytes.BYTES_RAWCOMPARATOR);
        aliasMap.put(HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY); // no alias for empty qualifier
        // get existing aliases from aliasTable
        RowId rowId = new RowId(SchemaEntityType.COLUMN_FAMILY.getRecordType(), getTableForeignKey(tableName),
                colFamily);
        Get getAliasRow = new Get(rowId.getByteArray());
        if (colQualifierSet == null) {
            getAliasRow.addFamily(ALIAS_CF);
        } else {
            for (byte[] colQualifier : colQualifierSet) {
                getAliasRow.addColumn(ALIAS_CF, colQualifier);
            }
        }
        Result aliasRow = aliasTable.get(getAliasRow);
        if (!aliasRow.isEmpty()) {
            aliasMap.putAll(aliasRow.getFamilyMap(ALIAS_CF));
        }
        if (colQualifierSet != null) {
            for (byte[] colQualifier : colQualifierSet) {
                if (aliasMap.get(colQualifier) == null) {
                    if (addAliasIfNotFound) {
                        aliasMap.put(colQualifier, getNewAlias(rowId.getByteArray(), colQualifier));
                    } else {
                        // invalid alias mapped to invalid alias
                        aliasMap.put(colQualifier, INVALID_ALIAS);
                    }
                }
            }
        }
        aliasMap.remove(ALIAS_INCREMENTOR_COLUMN);
        return aliasMap;
    }

    private byte[] getNewAlias(byte[] aliasTableRowId, byte[] colQualifier) throws IOException {
        byte[] newAlias = Bytes.toBytes(
                new Long(aliasTable.incrementColumnValue(aliasTableRowId, ALIAS_CF, ALIAS_INCREMENTOR_COLUMN, 1))
                        .intValue());
        Put putNewAlias = new Put(aliasTableRowId).addColumn(ALIAS_CF, colQualifier, newAlias);
        boolean putSucceeded = aliasTable.checkAndPut(aliasTableRowId, ALIAS_CF, colQualifier, null, putNewAlias);
        // put may NOT have succeeded if concurrent thread already stored an alias for the qualifier
        if (!putSucceeded) {
            Get getAlias = new Get(aliasTableRowId).addColumn(ALIAS_CF, colQualifier);
            newAlias = aliasTable.get(getAlias).getValue(ALIAS_CF, colQualifier);
        }
        return newAlias;
    }

    private void validateNamespaceTableNameIncludedForProcessing(String namespace, TableName tableName)
            throws TableNotIncludedForProcessingException {
        if (tableName == null || tableName.getNameAsString().isEmpty()) {
            if (namespace != null && !namespace.isEmpty() && !isIncludedNamespace(namespace)) {
                throw new TableNotIncludedForProcessingException(
                        Bytes.toBytes(namespace + ALL_TABLES_WILDCARD_INDICATOR), "NO table from namespace <"
                                + namespace + "> is included for " + PRODUCT_NAME + " processing.");
            }
        } else {
            if (!isIncludedTable(tableName)) {
                throw new TableNotIncludedForProcessingException(tableName.getName(), null);
            }
        }
    }

    void exportSchema(String sourceNamespace, TableName sourceTableName, File targetFile)
            throws IOException, JAXBException, XMLStreamException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        validateNamespaceTableNameIncludedForProcessing(sourceNamespace, sourceTableName);
        String allLiteral = "";
        if ((sourceNamespace == null || sourceNamespace.isEmpty())
                && (sourceTableName == null || sourceTableName.getNameAsString().isEmpty())) {
            allLiteral = "ALL ";
        }
        logger.info("EXPORT of " + allLiteral
                + "ColumnManager repository schema to external XML file has been invoked.");
        if (sourceNamespace != null && !sourceNamespace.isEmpty()) {
            logger.info("EXPORT source NAMESPACE: " + sourceNamespace);
        }
        if (sourceTableName != null && !sourceTableName.getNameAsString().isEmpty()) {
            logger.info("EXPORT source TABLE: " + sourceTableName.getNameAsString());
        }
        logger.info("EXPORT target FILE NAME: " + targetFile.getAbsolutePath());

        HBaseSchemaArchive.exportToXmlFile(new HBaseSchemaArchive(sourceNamespace, sourceTableName, this),
                targetFile);
        logger.info("EXPORT of ColumnManager repository schema has been completed.");
    }

    void importSchema(File sourceHsaFile, String namespaceFilter, TableName tableNameFilter, byte[] colFamilyFilter,
            boolean includeColumnAuditors, boolean bypassNamespacesTablesAndCFs) throws IOException, JAXBException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        validateNamespaceTableNameIncludedForProcessing(namespaceFilter, tableNameFilter);
        submitImportMessagesToLogger(sourceHsaFile, namespaceFilter, tableNameFilter, colFamilyFilter,
                includeColumnAuditors, bypassNamespacesTablesAndCFs);

        Set<Object> importedDescriptors = new LinkedHashSet<>();
        for (SchemaEntity entity : HBaseSchemaArchive.deserializeXmlFile(sourceHsaFile).getSchemaEntities()) {
            importedDescriptors.addAll(SchemaEntity.convertToNamespaceAndTableDescriptorSet(entity, namespaceFilter,
                    tableNameFilter, colFamilyFilter));
        }
        createImportedStructures(importedDescriptors, includeColumnAuditors, bypassNamespacesTablesAndCFs);
    }

    private void submitImportMessagesToLogger(File sourceHsaFile, String namespaceFilter, TableName tableNameFilter,
            byte[] colFamilyFilter, boolean includeColumnAuditors, boolean bypassNamespacesTablesAndCFs) {
        logger.info("IMPORT of " + ((bypassNamespacesTablesAndCFs) ? "<COLUMN DEFINITION> " : "") + "schema "
                + ((includeColumnAuditors) ? "<INCLUDING COLUMN AUDITOR METADATA> " : "")
                + "from external HBaseSchemaArchive (XML) file has been requested.");
        if (namespaceFilter != null && !namespaceFilter.isEmpty()
                && (tableNameFilter == null || tableNameFilter.getNameAsString().isEmpty())) {
            logger.info("IMPORT NAMESPACE: " + namespaceFilter);
        }
        if (tableNameFilter != null && !tableNameFilter.getNameAsString().isEmpty()) {
            logger.info("IMPORT TABLE: " + tableNameFilter.getNameAsString());
        }
        if (colFamilyFilter != null && colFamilyFilter.length > 0) {
            logger.info("IMPORT COLUMN FAMILY: " + Bytes.toString(colFamilyFilter));
        }
        logger.info("IMPORT source PATH/FILE-NAME: " + sourceHsaFile.getAbsolutePath());
    }

    private void createImportedStructures(Set<Object> importedDescriptors, boolean includeColumnAuditors,
            boolean bypassNamespacesTablesAndCFs) throws IOException {
        for (Object descriptor : importedDescriptors) {
            if (MNamespaceDescriptor.class.isAssignableFrom(descriptor.getClass())) {
                NamespaceDescriptor nd = ((MNamespaceDescriptor) descriptor).getNamespaceDescriptor();
                if (!isIncludedNamespace(nd.getName()) || namespaceExists(nd.getName())) {
                    continue;
                }
                getAdmin().createNamespace(nd);
                putNamespaceSchemaEntity(nd);
                logger.info("IMPORT COMPLETED FOR NAMESPACE: " + nd.getName());
            } else if (MTableDescriptor.class.isAssignableFrom(descriptor.getClass())) {
                MTableDescriptor mtd = (MTableDescriptor) descriptor;
                if (!isIncludedTable(mtd.getTableName())) {
                    continue;
                }
                if (getAdmin().tableExists(mtd.getTableName())) {
                    if (bypassNamespacesTablesAndCFs) {
                        putColumnDefinitionSchemaEntities(mtd);
                    }
                } else {
                    getAdmin().createTable(mtd); // includes creation of Column Families
                    putTableSchemaEntity(mtd);
                    putColumnDefinitionSchemaEntities(mtd);
                    if (includeColumnAuditors) {
                        putColumnAuditorSchemaEntities(mtd);
                    }
                    logger.info("IMPORT COMPLETED FOR TABLE: " + mtd.getNameAsString()
                            + (includeColumnAuditors ? " <INCLUDING COLUMN AUDITOR METADATA>" : ""));
                }
            }
        }
    }

    void dumpRepositoryTable() throws IOException {
        logger.info("DUMP of ColumnManager repository table has been requested.");
        try (ResultScanner results = repositoryTable.getScanner(new Scan().setMaxVersions())) {
            logger.info("** START OF COMPLETE SCAN OF " + PRODUCT_NAME + " REPOSITORY TABLE **");
            for (Result result : results) {
                byte[] rowId = result.getRow();
                logger.info("Row type: " + Bytes.toString(rowId).substring(0, 1));
                logger.info("  Row ID: " + getPrintableString(rowId));
                logger.info("  Element name: " + Bytes.toString(new RowId(rowId).getEntityName()));
                for (Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> cfEntry : result.getMap()
                        .entrySet()) {
                    logger.info("  Column Family: " + Bytes.toString(cfEntry.getKey()));
                    for (Entry<byte[], NavigableMap<Long, byte[]>> colEntry : cfEntry.getValue().entrySet()) {
                        logger.info("    Column: " + getPrintableString(colEntry.getKey()));
                        for (Entry<Long, byte[]> cellEntry : colEntry.getValue().entrySet()) {
                            logger.info("      Cell timestamp: " + cellEntry.getKey());
                            logger.info("      Cell value: " + getPrintableString(cellEntry.getValue()));
                        }
                    }
                }
            }
            logger.info("** END OF COMPLETE SCAN OF " + PRODUCT_NAME + " REPOSITORY TABLE **");
        }
        logger.info("DUMP of ColumnManager repository table is complete.");
    }

    boolean outputReportOnColumnQualifiers(String namespace, TableName tableName, byte[] colFamily, File targetFile)
            throws IOException {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        if (tableName == null && !isIncludedNamespace(namespace)) {
            throw new TableNotIncludedForProcessingException(
                    Bytes.toBytes(namespace + ALL_TABLES_WILDCARD_INDICATOR),
                    "NO table from namespace <" + namespace + "> is included for " + PRODUCT_NAME + " processing.");
        }
        if (tableName != null && !isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        ColumnQualifierReport columnQualifierReport = new ColumnQualifierReport(namespace, tableName, colFamily,
                this, targetFile);
        return columnQualifierReport.isEmpty();
    }

    boolean outputReportOnInvalidColumns(ColumnInvalidityReport.ReportType reportType, TableName tableName,
            byte[] colFamily, File targetFile, boolean verbose, boolean includeAllCells, boolean useMapreduce)
            throws Exception {
        if (!this.isActivated()) {
            throw new ColumnManagerIOException(REPOSITORY_NOT_ACTIVATED_MSG) {
            };
        }
        if (!isIncludedTable(tableName)) {
            throw new TableNotIncludedForProcessingException(tableName.getName(), null);
        }
        MTableDescriptor mtd = getMTableDescriptor(tableName);
        if (mtd == null || !mtd.hasColumnDefinitions()) {
            throw new ColumnDefinitionNotFoundException(tableName.getName(), colFamily, null,
                    "No ColumnDefinitions found for table/columnFamily");
        }
        if (colFamily != null && (mtd.getMColumnDescriptor(colFamily) == null
                || mtd.getMColumnDescriptor(colFamily).getColumnDefinitions().isEmpty())) {
            throw new ColumnDefinitionNotFoundException(tableName.getName(), colFamily, null,
                    "No ColumnDefinitions found for columnFamily");
        }
        try (ColumnInvalidityReport columnInvalidityReport = new ColumnInvalidityReport(reportType, hbaseConnection,
                mtd, colFamily, targetFile, verbose, includeAllCells, useMapreduce)) {
            return !columnInvalidityReport.isEmpty();
        }
    }

    static void dropRepository(Admin hbaseAdmin, Logger logger) throws IOException {
        Admin standardAdmin = getStandardAdmin(hbaseAdmin);
        if (!standardAdmin.tableExists(REPOSITORY_TABLENAME)) {
            return;
        }
        logger.warn("DROP (disable/delete) of " + PRODUCT_NAME
                + " Repository tables and namespace has been requested.");
        if (standardAdmin.isTableEnabled(REPOSITORY_TABLENAME)) {
            standardAdmin.disableTable(REPOSITORY_TABLENAME);
        }
        standardAdmin.deleteTable(REPOSITORY_TABLENAME);
        logger.warn("DROP (disable/delete) of " + PRODUCT_NAME + " Repository table has been completed: "
                + REPOSITORY_TABLENAME.getNameAsString());
        if (standardAdmin.isTableEnabled(ALIAS_DIRECTORY_TABLENAME)) {
            standardAdmin.disableTable(ALIAS_DIRECTORY_TABLENAME);
        }
        standardAdmin.deleteTable(ALIAS_DIRECTORY_TABLENAME);
        logger.warn("DROP (disable/delete) of " + PRODUCT_NAME + " AliasDirectory table has been completed: "
                + REPOSITORY_TABLENAME.getNameAsString());
        standardAdmin.deleteNamespace(REPOSITORY_NAMESPACE_DESCRIPTOR.getName());
        logger.warn("DROP (delete) of " + PRODUCT_NAME + " Repository namespace has been completed: "
                + REPOSITORY_NAMESPACE_DESCRIPTOR.getName());
    }

    boolean namespaceExists(String namespaceName) throws IOException {
        try {
            getAdmin().getNamespaceDescriptor(namespaceName);
        } catch (NamespaceNotFoundException e) {
            return false;
        }
        return true;
    }

    void logIOExceptionAsError(IOException e, String originatingClassName) {
        logger.error(new StringBuilder(PRODUCT_NAME).append(" ").append(e.getClass().getSimpleName())
                .append(" encountered in multithreaded ").append(originatingClassName).append(" processing.")
                .toString(), e);
    }

    static String getPrintableString(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return "";
        }
        if (isPrintable(bytes)) {
            return Bytes.toString(bytes);
        } else {
            StringBuilder sb = new StringBuilder("H\'");
            for (byte b : bytes) {
                sb.append(String.format("%02x", b));
            }
            sb.append("\'");
            return sb.toString();
        }
    }

    static boolean isPrintable(byte[] bytes) {
        if (bytes == null) {
            return false;
        }
        for (byte nextByte : bytes) {
            if (!(Character.isDefined(nextByte) && nextByte > 31)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isInteger(String input) {
        try {
            Integer.parseInt(input);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    static class RowId {
        private final ByteBuffer rowIdByteBuffer;
        private final byte entityType;
        private final byte[] parentForeignKey;
        private final byte[] entityName;

        RowId(byte recordType, byte[] parentForeignKey, byte[] entityName) {
            this.entityType = recordType;
            this.parentForeignKey = parentForeignKey;
            this.entityName = entityName;
            if (this.entityName == null) {
                rowIdByteBuffer = ByteBuffer.allocate(1 + this.parentForeignKey.length);
                rowIdByteBuffer.put(recordType).put(this.parentForeignKey);
            } else {
                rowIdByteBuffer = ByteBuffer.allocate(1 + this.parentForeignKey.length + this.entityName.length);
                rowIdByteBuffer.put(this.entityType).put(this.parentForeignKey).put(this.entityName);
            }
        }

        RowId(byte[] rowIdByteArray) {
            if (rowIdByteArray.length < 2) {
                throw new RuntimeException("Invalid RowId length (less than 2) encountered in " + PRODUCT_NAME
                        + " Repository Table processing.");
            }
            entityType = rowIdByteArray[0];
            int entityNamePosition;
            switch (SchemaEntityType.ENTITY_TYPE_BYTE_TO_ENUM_MAP.get(entityType)) {
            case NAMESPACE:
                entityNamePosition = 2;
                parentForeignKey = NAMESPACE_PARENT_FOREIGN_KEY;
                break;
            case TABLE:
            case COLUMN_FAMILY:
            case COLUMN_AUDITOR:
            case COLUMN_DEFINITION:
                entityNamePosition = 1 + UNIQUE_FOREIGN_KEY_LENGTH;
                parentForeignKey = Bytes.copy(rowIdByteArray, 1, UNIQUE_FOREIGN_KEY_LENGTH);
                break;
            default:
                throw new RuntimeException("Invalid record type encountered in " + PRODUCT_NAME
                        + " Repository Table processing: <" + String.valueOf(entityType) + ">");
            }
            entityName = new byte[rowIdByteArray.length - entityNamePosition];
            rowIdByteBuffer = ByteBuffer.wrap(rowIdByteArray);
            rowIdByteBuffer.position(entityNamePosition);
            rowIdByteBuffer.get(entityName, 0, entityName.length);
        }

        byte[] getByteArray() {
            return rowIdByteBuffer.array();
        }

        byte getEntityType() {
            return entityType;
        }

        byte[] getParentForeignKey() {
            return parentForeignKey;
        }

        byte[] getEntityName() {
            return entityName;
        }

        /**
         * If submitted entityName is null, stopRowId will be concatenation of startRowId and a
         * byte-array of 0xff value bytes (making for a Scan intended to return one-to-many Rows,
         * all with RowIds prefixed with the startRowId value); if entityName is NOT null,
         * stopRowId will be concatenation of startRowId and a byte-array of 0x00 value bytes
         * (making for a Scan intended to return a single Row with RowId precisely equal to
         * startRowId value).
         *
         * @return stopRowId value
         */
        byte[] getStopRowIdByteArray() {
            final byte[] fillerArray;
            if (entityName == null) {
                fillerArray = HEX_FF_ARRAY;
            } else {
                fillerArray = HEX_00_ARRAY;
            }
            return ByteBuffer.allocate(rowIdByteBuffer.array().length + fillerArray.length)
                    .put(rowIdByteBuffer.array()).put(fillerArray).array();
        }
    }

    // ALIAS METHODS START HERE

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyQualifierToAliasMap(
            MTableDescriptor mTableDescriptor, Mutation mutation) throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        Class<?> mutationClass = mutation.getClass();
        if (Append.class.isAssignableFrom(mutationClass)) {
            familyQualifierToAliasMap = getFamilyQualifierToAliasMap(mTableDescriptor, (Append) mutation);
        } else if (Increment.class.isAssignableFrom(mutationClass)) {
            familyQualifierToAliasMap = getFamilyQualifierToAliasMap(mTableDescriptor, (Increment) mutation);
        } else if (Delete.class.isAssignableFrom(mutationClass) || Put.class.isAssignableFrom(mutationClass)
                || RowMutations.class.isAssignableFrom(mutationClass)) {
            // ignore: familyQualifierToAliasMap not passed to alias-processing for these mutation-types
        }
        return familyQualifierToAliasMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyQualifierToAliasMap(
            MTableDescriptor mTableDescriptor, Get get) throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        NavigableSet<byte[]> aliasEnabledFamiliesInScan = new TreeSet<>(Bytes.BYTES_COMPARATOR);
        if (get.hasFamilies()) {
            for (byte[] colFamily : get.familySet()) {
                if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                    aliasEnabledFamiliesInScan.add(colFamily);
                }
            }
        } else {
            for (MColumnDescriptor mColumnDescriptor : mTableDescriptor.getMColumnDescriptors()) {
                if (mColumnDescriptor.columnAliasesEnabled()) {
                    aliasEnabledFamiliesInScan.add(mColumnDescriptor.getName());
                }
            }
        }
        if (!aliasEnabledFamiliesInScan.isEmpty()) {
            if (get.hasFamilies()) {
                for (Entry<byte[], NavigableSet<byte[]>> familyEntry : get.getFamilyMap().entrySet()) {
                    byte[] colFamily = familyEntry.getKey();
                    NavigableSet<byte[]> colQualifiers = familyEntry.getValue(); // could be null
                    if (aliasEnabledFamiliesInScan.contains(colFamily)) {
                        familyQualifierToAliasMap.put(colFamily, getQualifierToAliasMap(
                                mTableDescriptor.getTableName(), colFamily, colQualifiers, false));
                    }
                }
            } else {
                for (byte[] aliasEnabledFamilyInScan : aliasEnabledFamiliesInScan) {
                    familyQualifierToAliasMap.put(aliasEnabledFamilyInScan, getQualifierToAliasMap(
                            mTableDescriptor.getTableName(), aliasEnabledFamilyInScan, NULL_NAVIGABLE_SET, false));
                }
            }
        }
        return familyQualifierToAliasMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyQualifierToAliasMap(
            MTableDescriptor mTableDescriptor, List<? extends Row> rowList, int intForUniqueSignature)
            throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> masterFamilyQualifierToAliasMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        for (Row row : rowList) {
            Class<?> rowClass = row.getClass();
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> partialFamilyQualifierToAliasMap = new TreeMap<>(
                    Bytes.BYTES_COMPARATOR);
            if (Append.class.isAssignableFrom(rowClass)) {
                partialFamilyQualifierToAliasMap = getFamilyQualifierToAliasMap(mTableDescriptor, (Append) row);
            } else if (Get.class.isAssignableFrom(rowClass)) {
                partialFamilyQualifierToAliasMap = getFamilyQualifierToAliasMap(mTableDescriptor, (Get) row);
            } else if (Increment.class.isAssignableFrom(rowClass)) {
                partialFamilyQualifierToAliasMap = getFamilyQualifierToAliasMap(mTableDescriptor, (Increment) row);
            } else if (Delete.class.isAssignableFrom(rowClass) || Put.class.isAssignableFrom(rowClass)
                    || RowMutations.class.isAssignableFrom(rowClass)) {
                continue;
            }
            for (Entry<byte[], NavigableMap<byte[], byte[]>> partialFamilyEntry : partialFamilyQualifierToAliasMap
                    .entrySet()) {
                byte[] colFamily = partialFamilyEntry.getKey();
                NavigableMap<byte[], byte[]> masterQualifierToAliasMap = masterFamilyQualifierToAliasMap
                        .get(colFamily);
                if (masterQualifierToAliasMap == null) {
                    masterFamilyQualifierToAliasMap.put(colFamily, partialFamilyEntry.getValue());
                } else {
                    masterQualifierToAliasMap.putAll(partialFamilyEntry.getValue());
                }
            }
        }
        return masterFamilyQualifierToAliasMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyQualifierToAliasMap(
            MTableDescriptor mTableDescriptor, List<Get> gets) throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        NavigableSet<byte[]> aliasEnabledFamiliesInScan = new TreeSet<>(Bytes.BYTES_COMPARATOR);
        for (Get get : gets) {
            if (get.hasFamilies()) {
                for (byte[] colFamily : get.familySet()) {
                    if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                        aliasEnabledFamiliesInScan.add(colFamily);
                    }
                }
            } else {
                for (MColumnDescriptor mColumnDescriptor : mTableDescriptor.getMColumnDescriptors()) {
                    if (mColumnDescriptor.columnAliasesEnabled()) {
                        aliasEnabledFamiliesInScan.add(mColumnDescriptor.getName());
                    }
                }
            }
        }
        if (!aliasEnabledFamiliesInScan.isEmpty()) {
            for (Get get : gets) {
                if (get.hasFamilies()) {
                    for (Entry<byte[], NavigableSet<byte[]>> familyEntry : get.getFamilyMap().entrySet()) {
                        byte[] colFamily = familyEntry.getKey();
                        NavigableSet<byte[]> colQualifiers = familyEntry.getValue(); // could be null
                        if (aliasEnabledFamiliesInScan.contains(colFamily)) {
                            NavigableMap<byte[], byte[]> qualifierToAliasMap = familyQualifierToAliasMap
                                    .get(colFamily);
                            if (qualifierToAliasMap == null) {
                                familyQualifierToAliasMap.put(colFamily, getQualifierToAliasMap(
                                        mTableDescriptor.getTableName(), colFamily, colQualifiers, false));
                            } else {
                                qualifierToAliasMap.putAll(getQualifierToAliasMap(mTableDescriptor.getTableName(),
                                        colFamily, colQualifiers, false));
                            }
                        }
                    }
                } else {
                    // if no families specified in Get, need all alias entries for entire family
                    for (byte[] aliasEnabledFamilyInScan : aliasEnabledFamiliesInScan) {
                        familyQualifierToAliasMap.put(aliasEnabledFamilyInScan,
                                getQualifierToAliasMap(mTableDescriptor.getTableName(), aliasEnabledFamilyInScan,
                                        NULL_NAVIGABLE_SET, false));
                    }
                }
            }
        }
        return familyQualifierToAliasMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyQualifierToAliasMap(
            MTableDescriptor mTableDescriptor, Scan scan) throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        NavigableSet<byte[]> aliasEnabledFamiliesInScan = new TreeSet<>(Bytes.BYTES_COMPARATOR);
        if (scan.hasFamilies()) {
            for (byte[] colFamily : scan.getFamilies()) {
                if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                    aliasEnabledFamiliesInScan.add(colFamily);
                }
            }
        } else {
            for (MColumnDescriptor mColumnDescriptor : mTableDescriptor.getMColumnDescriptors()) {
                if (mColumnDescriptor.columnAliasesEnabled()) {
                    aliasEnabledFamiliesInScan.add(mColumnDescriptor.getName());
                }
            }
        }
        if (!aliasEnabledFamiliesInScan.isEmpty()) {
            if (scan.hasFamilies()) {
                for (Entry<byte[], NavigableSet<byte[]>> familyEntry : scan.getFamilyMap().entrySet()) {
                    byte[] colFamily = familyEntry.getKey();
                    NavigableSet<byte[]> colQualifiers = familyEntry.getValue(); // could be null
                    if (aliasEnabledFamiliesInScan.contains(colFamily)) {
                        familyQualifierToAliasMap.put(colFamily, getQualifierToAliasMap(
                                mTableDescriptor.getTableName(), colFamily, colQualifiers, false));
                    }
                }
            } else {
                for (byte[] aliasEnabledFamilyInScan : aliasEnabledFamiliesInScan) {
                    familyQualifierToAliasMap.put(aliasEnabledFamilyInScan, getQualifierToAliasMap(
                            mTableDescriptor.getTableName(), aliasEnabledFamilyInScan, NULL_NAVIGABLE_SET, false));
                }
            }
        }
        return familyQualifierToAliasMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyQualifierToAliasMap(
            MTableDescriptor mTableDescriptor, Append append) throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        for (Entry<byte[], List<Cell>> familyToCellsMap : append.getFamilyCellMap().entrySet()) {
            byte[] colFamily = familyToCellsMap.getKey();
            List<Cell> cellList = familyToCellsMap.getValue();
            if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                familyQualifierToAliasMap.put(colFamily,
                        getQualifierToAliasMap(mTableDescriptor.getTableName(), colFamily, cellList, true));
            }
        }
        return familyQualifierToAliasMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyQualifierToAliasMap(
            MTableDescriptor mTableDescriptor, Increment increment) throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        for (Entry<byte[], List<Cell>> familyToCellsMap : increment.getFamilyCellMap().entrySet()) {
            byte[] colFamily = familyToCellsMap.getKey();
            List<Cell> cellList = familyToCellsMap.getValue();
            if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                familyQualifierToAliasMap.put(colFamily,
                        getQualifierToAliasMap(mTableDescriptor.getTableName(), colFamily, cellList, true));
            }
        }
        return familyQualifierToAliasMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyAliasToQualifierMap(
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap) {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyAliasToQualifierMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        for (Entry<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasEntry : familyQualifierToAliasMap
                .entrySet()) {
            NavigableMap<byte[], byte[]> aliasToQualifierMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
            for (Entry<byte[], byte[]> qualifierToAliasMap : familyQualifierToAliasEntry.getValue().entrySet()) {
                aliasToQualifierMap.put(qualifierToAliasMap.getValue(), qualifierToAliasMap.getKey());
            }
            familyAliasToQualifierMap.put(familyQualifierToAliasEntry.getKey(), aliasToQualifierMap);
        }
        return familyAliasToQualifierMap;
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyAliasToQualifierMap(
            MTableDescriptor mTableDescriptor, byte[] colFamily) throws IOException {
        return getFamilyAliasToQualifierMap(mTableDescriptor, colFamily, NULL_NAVIGABLE_SET);
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyAliasToQualifierMap(
            MTableDescriptor mTableDescriptor, byte[] colFamily, byte[] colQualifier) throws IOException {
        NavigableSet<byte[]> colQualifierSet = new TreeSet<>(Bytes.BYTES_COMPARATOR);
        colQualifierSet.add(colQualifier);
        return getFamilyAliasToQualifierMap(mTableDescriptor, colFamily, colQualifierSet);
    }

    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getFamilyAliasToQualifierMap(
            MTableDescriptor mTableDescriptor, byte[] colFamily, NavigableSet<byte[]> colQualifierSet)
            throws IOException {
        NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyAliasToQualifierMap = new TreeMap<>(
                Bytes.BYTES_COMPARATOR);
        if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap = new TreeMap<>(
                    Bytes.BYTES_COMPARATOR);
            familyQualifierToAliasMap.put(colFamily,
                    getQualifierToAliasMap(mTableDescriptor.getTableName(), colFamily, colQualifierSet, false));
            familyAliasToQualifierMap = getFamilyAliasToQualifierMap(familyQualifierToAliasMap);
        }
        return familyAliasToQualifierMap;
    }

    Row convertQualifiersToAliases(MTableDescriptor mTableDescriptor, final Row originalRow,
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap, int intForUniqueSignature)
            throws IOException {
        // Append, Delete, Get, Increment, Mutation, Put, RowMutations
        Class<?> originalRowClass = originalRow.getClass();
        if (Append.class.isAssignableFrom(originalRowClass)) {
            return convertQualifiersToAliases(mTableDescriptor, (Append) originalRow, familyQualifierToAliasMap);
        } else if (Delete.class.isAssignableFrom(originalRowClass)) {
            return convertQualifiersToAliases(mTableDescriptor, (Delete) originalRow);
        } else if (Get.class.isAssignableFrom(originalRowClass)) {
            return convertQualifiersToAliases(mTableDescriptor, (Get) originalRow, familyQualifierToAliasMap);
        } else if (Increment.class.isAssignableFrom(originalRowClass)) {
            return convertQualifiersToAliases(mTableDescriptor, (Increment) originalRow, familyQualifierToAliasMap);
        } else if (Put.class.isAssignableFrom(originalRowClass)) {
            return convertQualifiersToAliases(mTableDescriptor, (Put) originalRow);
        } else if (RowMutations.class.isAssignableFrom(originalRowClass)) {
            return convertQualifiersToAliases(mTableDescriptor, (RowMutations) originalRow);
        }
        return null;
    }

    Get convertQualifiersToAliases(MTableDescriptor mTableDescriptor, final Get originalGet,
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap) throws IOException {
        if (!originalGet.hasFamilies()) {
            return originalGet;
        }
        NavigableMap<byte[], NavigableSet<byte[]>> modifiedFamilyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
        for (Entry<byte[], NavigableSet<byte[]>> familyToQualifiersMap : originalGet.getFamilyMap().entrySet()) {
            byte[] colFamily = familyToQualifiersMap.getKey();
            NavigableSet<byte[]> colQualifierSet = familyToQualifiersMap.getValue();
            if (colQualifierSet == null
                    || !mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                modifiedFamilyMap.put(colFamily, colQualifierSet); // no modifications
            } else {
                NavigableMap<byte[], byte[]> qualifierToAliasMap = familyQualifierToAliasMap.get(colFamily);
                NavigableSet<byte[]> aliasSet = new TreeSet<>(Bytes.BYTES_COMPARATOR);
                for (byte[] qualifier : colQualifierSet) {
                    byte[] alias = qualifierToAliasMap.get(qualifier);
                    aliasSet.add(alias);
                }
                modifiedFamilyMap.put(colFamily, aliasSet);
            }
        }
        Get convertedGet = cloneGetWithoutFamilyMap(originalGet);
        for (Entry<byte[], NavigableSet<byte[]>> modifiedFamilyToQualifiersMap : modifiedFamilyMap.entrySet()) {
            byte[] colFamily = modifiedFamilyToQualifiersMap.getKey();
            NavigableSet<byte[]> colQualifierSet = modifiedFamilyToQualifiersMap.getValue();
            if (colQualifierSet == null) {
                convertedGet.addFamily(colFamily);
            } else {
                for (byte[] colQualifier : colQualifierSet) {
                    convertedGet.addColumn(colFamily, colQualifier);
                }
            }
        }
        return convertedGet;
    }

    /**
     * Method may need modification if Get attributes are added or removed in future HBase releases.
     *
     * @param originalGet
     * @return convertedGet
     * @throws IOException
     */
    Get cloneGetWithoutFamilyMap(Get originalGet) throws IOException {
        Get convertedGet = new Get(originalGet.getRow());
        // from Query
        convertedGet.setFilter(originalGet.getFilter());
        convertedGet.setReplicaId(originalGet.getReplicaId());
        convertedGet.setConsistency(originalGet.getConsistency());
        // from Get
        convertedGet.setCacheBlocks(originalGet.getCacheBlocks());
        convertedGet.setMaxVersions(originalGet.getMaxVersions());
        convertedGet.setMaxResultsPerColumnFamily(originalGet.getMaxResultsPerColumnFamily());
        convertedGet.setRowOffsetPerColumnFamily(originalGet.getRowOffsetPerColumnFamily());
        convertedGet.setCheckExistenceOnly(originalGet.isCheckExistenceOnly());
        convertedGet.setClosestRowBefore(originalGet.isClosestRowBefore());
        for (Map.Entry<String, byte[]> attr : originalGet.getAttributesMap().entrySet()) {
            convertedGet.setAttribute(attr.getKey(), attr.getValue());
        }
        for (Map.Entry<byte[], TimeRange> entry : originalGet.getColumnFamilyTimeRange().entrySet()) {
            TimeRange tr = entry.getValue();
            convertedGet.setColumnFamilyTimeRange(entry.getKey(), tr.getMin(), tr.getMax());
        }
        return convertedGet;
    }

    Scan convertQualifiersToAliases(MTableDescriptor mTableDescriptor, final Scan originalScan,
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap) throws IOException {
        if (!originalScan.hasFamilies()) {
            return originalScan;
        }
        NavigableMap<byte[], NavigableSet<byte[]>> modifiedFamilyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
        for (Entry<byte[], NavigableSet<byte[]>> familyToQualifiersMap : originalScan.getFamilyMap().entrySet()) {
            byte[] colFamily = familyToQualifiersMap.getKey();
            NavigableSet<byte[]> colQualifierSet = familyToQualifiersMap.getValue();
            if (colQualifierSet == null
                    || !mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                modifiedFamilyMap.put(colFamily, colQualifierSet);
            } else {
                NavigableMap<byte[], byte[]> qualifierToAliasMap = familyQualifierToAliasMap.get(colFamily);
                NavigableSet<byte[]> aliasSet = new TreeSet<>(Bytes.BYTES_COMPARATOR);
                for (byte[] qualifier : colQualifierSet) {
                    byte[] alias = qualifierToAliasMap.get(qualifier);
                    aliasSet.add(alias);
                }
                modifiedFamilyMap.put(colFamily, aliasSet);
            }
        }
        // clone original Scan, but assign modifiedFamilyMap that has qualifiers replaced by aliases
        return new Scan(originalScan).setFamilyMap(modifiedFamilyMap);
    }

    RowMutations convertQualifiersToAliases(MTableDescriptor mTableDescriptor,
            final RowMutations originalRowMutations) throws IOException {
        RowMutations modifiedRowMutations = new RowMutations(originalRowMutations.getRow());
        for (Mutation originalMutation : originalRowMutations.getMutations()) {
            Class<?> mutationClass = originalMutation.getClass();
            if (Put.class.isAssignableFrom(mutationClass)) {
                modifiedRowMutations.add(convertQualifiersToAliases(mTableDescriptor, (Put) originalMutation));
            } else if (Delete.class.isAssignableFrom(mutationClass)) {
                modifiedRowMutations.add(convertQualifiersToAliases(mTableDescriptor, (Delete) originalMutation));
            }
        }
        return modifiedRowMutations;
    }

    Put convertQualifiersToAliases(MTableDescriptor mTableDescriptor, final Put originalPut) throws IOException {
        // clone Put, but remove all cell entries by setting familyToCellsMap to empty Map
        Put modifiedPut = new Put(originalPut)
                .setFamilyCellMap(new TreeMap<byte[], List<Cell>>(Bytes.BYTES_COMPARATOR));

        for (Entry<byte[], List<Cell>> familyToCellsMap : originalPut.getFamilyCellMap().entrySet()) {
            byte[] colFamily = familyToCellsMap.getKey();
            List<Cell> cellList = familyToCellsMap.getValue();
            if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                NavigableMap<byte[], byte[]> qualifierToAliasMap = getQualifierToAliasMap(
                        mTableDescriptor.getTableName(), colFamily, cellList, true);
                for (Cell originalCell : cellList) {
                    modifiedPut.addColumn(colFamily,
                            qualifierToAliasMap.get(Bytes.copy(originalCell.getQualifierArray(),
                                    originalCell.getQualifierOffset(), originalCell.getQualifierLength())),
                            originalCell.getTimestamp(), Bytes.copy(originalCell.getValueArray(),
                                    originalCell.getValueOffset(), originalCell.getValueLength()));
                }
            } else {
                for (Cell originalCell : cellList) {
                    modifiedPut.add(originalCell);
                }
            }
        }
        return modifiedPut;
    }

    Append convertQualifiersToAliases(MTableDescriptor mTableDescriptor, final Append originalAppend,
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap) throws IOException {
        // clone Append, but remove all cell entries by setting familyToCellsMap to empty Map
        Append modifiedAppend = new Append(originalAppend)
                .setFamilyCellMap(new TreeMap<byte[], List<Cell>>(Bytes.BYTES_COMPARATOR));

        for (Entry<byte[], List<Cell>> familyToCellsMap : originalAppend.getFamilyCellMap().entrySet()) {
            byte[] colFamily = familyToCellsMap.getKey();
            List<Cell> cellList = familyToCellsMap.getValue();
            if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                for (Cell originalCell : cellList) {
                    modifiedAppend.add(colFamily,
                            familyQualifierToAliasMap.get(colFamily)
                                    .get(Bytes.copy(originalCell.getQualifierArray(),
                                            originalCell.getQualifierOffset(), originalCell.getQualifierLength())),
                            Bytes.copy(originalCell.getValueArray(), originalCell.getValueOffset(),
                                    originalCell.getValueLength()));
                }
            } else {
                for (Cell originalCell : cellList) {
                    modifiedAppend.add(originalCell);
                }
            }
        }
        return modifiedAppend;
    }

    Increment convertQualifiersToAliases(MTableDescriptor mTableDescriptor, final Increment originalIncrement,
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyQualifierToAliasMap) throws IOException {
        // clone Increment, but remove all cell entries by setting familyToCellsMap to empty Map
        Increment modifiedIncrement = new Increment(originalIncrement)
                .setFamilyCellMap(new TreeMap<byte[], List<Cell>>(Bytes.BYTES_COMPARATOR));

        for (Entry<byte[], List<Cell>> familyToCellsMap : originalIncrement.getFamilyCellMap().entrySet()) {
            byte[] colFamily = familyToCellsMap.getKey();
            List<Cell> cellList = familyToCellsMap.getValue();
            if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                NavigableMap<byte[], byte[]> qualifierToAliasMap = familyQualifierToAliasMap.get(colFamily);
                for (Cell originalCell : cellList) {
                    modifiedIncrement.addColumn(colFamily,
                            familyQualifierToAliasMap.get(colFamily)
                                    .get(Bytes.copy(originalCell.getQualifierArray(),
                                            originalCell.getQualifierOffset(), originalCell.getQualifierLength())),
                            Bytes.toLong(Bytes.copy(originalCell.getValueArray(), originalCell.getValueOffset(),
                                    originalCell.getValueLength())));
                }
            } else {
                for (Cell originalCell : cellList) {
                    modifiedIncrement.add(originalCell);
                }
            }
        }
        return modifiedIncrement;
    }

    Delete convertQualifiersToAliases(MTableDescriptor mTableDescriptor, final Delete originalDelete)
            throws IOException {
        // clone Delete, but remove all cell entries by setting familyToCellsMap to empty Map
        Delete modifiedDelete = new Delete(originalDelete)
                .setFamilyCellMap(new TreeMap<byte[], List<Cell>>(Bytes.BYTES_COMPARATOR));

        for (Entry<byte[], List<Cell>> familyToCellsMap : originalDelete.getFamilyCellMap().entrySet()) {
            byte[] colFamily = familyToCellsMap.getKey();
            List<Cell> cellList = familyToCellsMap.getValue();
            if (mTableDescriptor.getMColumnDescriptor(colFamily).columnAliasesEnabled()) {
                NavigableMap<byte[], byte[]> qualifierToAliasMap = getQualifierToAliasMap(
                        mTableDescriptor.getTableName(), colFamily, cellList, false);
                for (Cell originalCell : cellList) {
                    byte[] colQualifier = Bytes.copy(originalCell.getQualifierArray(),
                            originalCell.getQualifierOffset(), originalCell.getQualifierLength());
                    byte[] colAlias = qualifierToAliasMap.get(colQualifier);
                    if (originalCell.getTypeByte() == KeyValue.Type.DeleteFamilyVersion.getCode()) {
                        modifiedDelete.addFamilyVersion(colFamily, originalCell.getTimestamp());
                    } else if (originalCell.getTypeByte() == KeyValue.Type.DeleteFamily.getCode()) {
                        modifiedDelete.addFamily(colFamily);
                    } else if (originalCell.getTypeByte() == KeyValue.Type.DeleteColumn.getCode()) {
                        modifiedDelete.addColumns(colFamily, colAlias, originalCell.getTimestamp());
                    } else if (originalCell.getTypeByte() == KeyValue.Type.Delete.getCode()) {
                        modifiedDelete.addColumn(colFamily, colAlias, originalCell.getTimestamp());
                    }
                }
            } else { // colFamily NOT aliasEnabled, so "clone" cells using standard Delete interface
                for (Cell originalCell : cellList) {
                    byte[] colQualifier = Bytes.copy(originalCell.getQualifierArray(),
                            originalCell.getQualifierOffset(), originalCell.getQualifierLength());
                    if (originalCell.getTypeByte() == KeyValue.Type.DeleteFamilyVersion.getCode()) {
                        modifiedDelete.addFamilyVersion(colFamily, originalCell.getTimestamp());
                    } else if (originalCell.getTypeByte() == KeyValue.Type.DeleteFamily.getCode()) {
                        modifiedDelete.addFamily(colFamily);
                    } else if (originalCell.getTypeByte() == KeyValue.Type.DeleteColumn.getCode()) {
                        modifiedDelete.addColumns(colFamily, colQualifier, originalCell.getTimestamp());
                    } else if (originalCell.getTypeByte() == KeyValue.Type.Delete.getCode()) {
                        modifiedDelete.addColumn(colFamily, colQualifier, originalCell.getTimestamp());
                    }
                }
            }
        }
        return modifiedDelete;
    }

    Result convertAliasesToQualifiers(Result result,
            NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyAliasToQualifierMap) {
        NavigableSet<Cell> convertedCellSet = new TreeSet<Cell>(KeyValue.COMPARATOR);
        for (Cell originalCell : result.rawCells()) {
            byte[] cellFamily = Bytes.copy(originalCell.getFamilyArray(), originalCell.getFamilyOffset(),
                    originalCell.getFamilyLength());
            NavigableMap<byte[], byte[]> aliasToQualifierMap = familyAliasToQualifierMap.get(cellFamily);
            if (aliasToQualifierMap == null) {
                convertedCellSet.add(originalCell); // if no aliasToQualifierMap, no conversion done
            } else {
                convertedCellSet.add(CellUtil.createCell(
                        Bytes.copy(originalCell
                                .getRowArray(), originalCell.getRowOffset(), originalCell.getRowLength()),
                        cellFamily,
                        aliasToQualifierMap.get(Bytes.copy(originalCell.getQualifierArray(),
                                originalCell.getQualifierOffset(), originalCell.getQualifierLength())),
                        originalCell.getTimestamp(), KeyValue.Type.codeToType(originalCell.getTypeByte()),
                        Bytes.copy(originalCell.getValueArray(), originalCell.getValueOffset(),
                                originalCell.getValueLength()),
                        Bytes.copy(originalCell.getTagsArray(), originalCell.getTagsOffset(),
                                originalCell.getTagsLength())));
            }
        }
        return Result.create(convertedCellSet.toArray(new Cell[convertedCellSet.size()]));
    }
    // ALIAS METHODS END HERE
}