com.bah.culvert.Client.java Source code

Java tutorial

Introduction

Here is the source code for com.bah.culvert.Client.java

Source

/**
 * Copyright 2011 Booz Allen Hamilton.
 * 
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership. Booz Allen Hamilton
 * licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bah.culvert;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.apache.hadoop.conf.Configuration;

import com.bah.culvert.adapter.DatabaseAdapter;
import com.bah.culvert.constraints.Constraint;
import com.bah.culvert.data.CKeyValue;
import com.bah.culvert.data.Result;
import com.bah.culvert.data.index.Index;
import com.bah.culvert.transactions.Put;
import com.bah.culvert.util.Bytes;
import com.bah.culvert.util.ConfUtils;
import com.bah.culvert.util.LexicographicBytesComparator;

/**
 * Main entry point for interacting with the indexed database
 */
public class Client {

    private static final String DATABASE_ADAPTER_CONF_KEY = "culvert.database.adapter";
    private static final String INDEXES_CONF_KEY = "culvert.indices.names";
    private static final String DATABASE_ADAPTER_CONF_PREFIX = "culvert.database.conf";

    private final Configuration configuration;

    public Client(Configuration conf) {
        this.configuration = conf;
    }

    /**
     * Create a record in the ClientAdapter for the information. Also
     * automatically indexes that {@link Put} for future use
     * @param put
     * @throws RuntimeException If an error occurs.
     */
    public void put(String tableName, Put put) {
        // Get the KeyValue list
        Iterable<CKeyValue> keyValueList = put.getKeyValueList();
        List<CKeyValue> indexValues = new ArrayList<CKeyValue>();
        // for each index, add only the keyvalues that should be indexed
        for (Index index : getIndices()) {
            indexValues.clear();
            for (CKeyValue keyValue : keyValueList) {
                if (Bytes.compareTo(index.getColumnFamily(), keyValue.getFamily()) == 0) {
                    if (Bytes.compareTo(index.getColumnQualifier(), keyValue.getQualifier()) == 0) {
                        indexValues.add(keyValue);
                    }
                }
            }
            index.handlePut(new Put(indexValues));
        }
        // TODO this is obviously not performant for every put
        // we should probably do some caching here
        DatabaseAdapter db = getDatabaseAdapter();
        if (!db.verify())
            throw new RuntimeException(
                    "Could not connect to the database to make the put of the actual value. Index may be corrupt.");
        db.getTableAdapter(tableName).put(put);
    }

    /**
     * Creates a map of the Index keyed by the index name.
     * @return map of [name, index]
     */
    public HashMap<String, Index> getIndexMap() {
        HashMap<String, Index> indexMap = new HashMap<String, Index>();

        for (Index index : getIndices()) {
            indexMap.put(index.getName(), index);
        }

        return indexMap;
    }

    /**
     * Query the ClientAdapter associated with <code>this</code>
     * @param query
     * @return an iterator to the list of results from the query
     */
    public Iterator<Result> query(Constraint query) {
        return query.getResultIterator();
    }

    private static String indexClassConfKey(String indexName) {
        return "culvert.indices.class." + indexName;
    }

    private static String indexConfPrefix(String indexName) {
        return "culvert.indices.conf." + indexName;
    }

    /**
     * Get the indices assigned to this client.
     * @return stored indicies
     */
    public Index[] getIndices() {
        String[] indexNames = this.configuration.getStrings(INDEXES_CONF_KEY);
        int arrayLength = indexNames == null ? 0 : indexNames.length;
        Index[] indices = new Index[arrayLength];
        for (int i = 0; i < arrayLength; i++) {
            String name = indexNames[i];
            Class<?> indexClass = configuration.getClass(indexClassConfKey(name), null);
            Configuration indexConf = ConfUtils.unpackConfigurationInPrefix(indexConfPrefix(name), configuration);
            Index index;
            try {
                index = Index.class.cast(indexClass.newInstance());
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            index.setConf(indexConf);
            indices[i] = index;
        }
        return indices;
    }

    /**
     * Get the indices assigned to this client.
     * @return The indices for this table.
     */
    public Index[] getIndicesForTable(String tableName) {
        String[] indexNames = this.configuration.getStrings(INDEXES_CONF_KEY);
        List<Index> indices = new ArrayList<Index>();
        for (int i = 0; i < indexNames.length; i++) {
            String name = indexNames[i];
            Class<?> indexClass = configuration.getClass(indexClassConfKey(name), null);
            Configuration indexConf = ConfUtils.unpackConfigurationInPrefix(indexConfPrefix(name), configuration);
            String primaryTableName = indexConf.get(Index.PRIMARY_TABLE_CONF_KEY);
            if (tableName.equals(primaryTableName)) {
                Index index;
                try {
                    index = Index.class.cast(indexClass.newInstance());
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
                index.setConf(indexConf);
                indices.add(index);
            }
        }
        return indices.toArray(new Index[indices.size()]);
    }

    /**
     * Get an index by name.
     * @param string The name of the index.
     * @return The index with the name, or null if no such index exists for this
     *         client.
     */
    public Index getIndexByName(String string) {
        Index[] indices = getIndices();
        for (Index index : indices) {
            if (index.getName().equals(string)) {
                return index;
            }
        }
        return null;
    }

    /**
     * Return any indices that index this column.
     * @param table primary table that index indexes
     * @param family The column family to search for.
     * @param qualifier The column Qualifier to search for.
     * @return The indices over the column.
     */
    public Index[] getIndicesForColumn(String table, byte[] family, byte[] qualifier) {
        Index[] indices = getIndices();
        List<Index> indicesForColumn = new ArrayList<Index>();
        for (Index index : indices) {
            if (table.equals(index.getPrimaryTableName())) {
                if (LexicographicBytesComparator.INSTANCE.compare(family, index.getColumnFamily()) == 0) {
                    if (LexicographicBytesComparator.INSTANCE.compare(qualifier, index.getColumnQualifier()) == 0) {
                        indicesForColumn.add(index);
                    }
                }
            }
        }
        return indicesForColumn.toArray(new Index[indicesForColumn.size()]);
    }

    /**
     * Add an index to the primary table that this client us being used on.
     * @param index The index to add to this table.
     * @throws RuntimeException If the index name already exists.
     */
    public void addIndex(Index index) {
        String name = index.getName();
        String[] currentIndices = configuration.getStrings(INDEXES_CONF_KEY, new String[0]);
        for (String existingName : currentIndices) {
            if (existingName.equals(name)) {
                throw new RuntimeException("Index with name " + name + " already exists");
            }
        }

        String[] newNames = new String[currentIndices.length + 1];
        System.arraycopy(currentIndices, 0, newNames, 0, currentIndices.length);
        newNames[currentIndices.length] = name;
        ConfUtils.packConfigurationInPrefix(indexConfPrefix(name), index.getConf(), configuration);
        configuration.setStrings(INDEXES_CONF_KEY, newNames);
        configuration.set(indexClassConfKey(name), index.getClass().getName());
    }

    /**
     * Get the configuration for this client.
     * @return This client's configuration.
     */
    public Configuration getConf() {
        return configuration;
    }

    /**
     * Set the database the client is currently talking to
     * @param db DatabaseAdapter to connect to the database
     * @param conf Top level configuration to pack the database's configuration in
     */
    public static void setDatabase(DatabaseAdapter db, Configuration conf) {
        conf.set(DATABASE_ADAPTER_CONF_KEY, db.getClass().getName());
        ConfUtils.packConfigurationInPrefix(DATABASE_ADAPTER_CONF_PREFIX, db.getConf(), conf);
    }

    private DatabaseAdapter getDatabaseAdapter() {
        try {
            DatabaseAdapter adapter = DatabaseAdapter.class
                    .cast(configuration.getClass(DATABASE_ADAPTER_CONF_KEY, null).newInstance());
            Configuration dbConf = ConfUtils.unpackConfigurationInPrefix(DATABASE_ADAPTER_CONF_PREFIX,
                    configuration);
            adapter.setConf(dbConf);
            return adapter;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    // testing util
    public static void setDatabaseAdapter(Configuration conf, Class<? extends DatabaseAdapter> adapterClass) {
        conf.setClass(DATABASE_ADAPTER_CONF_KEY, adapterClass, DatabaseAdapter.class);
    }

    public boolean tableExists(String tableName) {
        DatabaseAdapter adapter = getDatabaseAdapter();
        return adapter.tableExists(tableName);
    }

    /**
     * Ensure that the client is can connect to the database
     * @return <tt>true</tt> if it can connect, <tt>false</tt> otherwise
     */
    public boolean verify() {
        return getDatabaseAdapter().verify();
    }
}