Java tutorial
/** * (c) Copyright 2014 WibiData, Inc. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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.kiji.schema.impl.cassandra; import java.io.IOException; import java.nio.ByteBuffer; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicReference; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import org.apache.hadoop.hbase.util.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.kiji.annotations.ApiAudience; import org.kiji.commons.ByteUtils; import org.kiji.schema.Kiji; import org.kiji.schema.KijiNotInstalledException; import org.kiji.schema.KijiSystemTable; import org.kiji.schema.KijiURI; import org.kiji.schema.avro.SystemTableBackup; import org.kiji.schema.avro.SystemTableEntry; import org.kiji.schema.cassandra.CassandraTableName; import org.kiji.schema.impl.Versions; import org.kiji.schema.util.CloseableIterable; import org.kiji.schema.util.ProtocolVersion; /** * <p>The Kiji system table that is stored in Cassandra.</p> * * <p>The system table (a Kiji system table) is a simple key-value store for system-wide * properties of a Kiji installation. There is a single column family "value". For a * key-value property (K,V), the key K is stored as the row key in the Cassandra table, * and the value V is stored in the "value:" column.<p> */ @ApiAudience.Private public final class CassandraSystemTable implements KijiSystemTable { private static final Logger LOG = LoggerFactory.getLogger(CassandraSystemTable.class); /** The Cassandra column family that stores the value of the properties. */ public static final String KEY_COLUMN = "key"; public static final String VALUE_COLUMN = "value"; /** The Cassandra row key that stores the installed Kiji data format version. */ public static final String KEY_DATA_VERSION = "data-version"; /** The Cassandra row key that stores the Kiji security version. */ public static final String SECURITY_PROTOCOL_VERSION = "security-version"; /** * The name of the file that stores the current system table defaults that are loaded * at installation time. */ public static final String DEFAULTS_PROPERTIES_FILE = "org/kiji/schema/system-default.properties"; /** URI of the Kiji instance this system table belongs to. */ private final KijiURI mInstanceURI; /** The Cassandra table that stores the Kiji instance properties. */ private final CassandraTableName mTable; /** Cassandra cluster connection. */ private final CassandraAdmin mAdmin; /** States of a SystemTable instance. */ private static enum State { UNINITIALIZED, OPEN, CLOSED } /** Tracks the state of this SystemTable instance. */ private AtomicReference<State> mState = new AtomicReference<State>(State.UNINITIALIZED); private final PreparedStatement mPreparedStatementGetValue; private final PreparedStatement mPreparedStatementPutValue; /** * Wrap an existing Cassandra table that is assumed to be the table that stores the * Kiji instance properties. * * @param instanceURI URI of the Kiji instance this table belongs to. * @param admin the Cassandra connection. */ public CassandraSystemTable(KijiURI instanceURI, CassandraAdmin admin) { mInstanceURI = Preconditions.checkNotNull(instanceURI); mAdmin = Preconditions.checkNotNull(admin); mTable = CassandraTableName.getSystemTableName(instanceURI); if (!mAdmin.tableExists(mTable)) { throw new KijiNotInstalledException("System table not installed.", mInstanceURI); } final State oldState = mState.getAndSet(State.OPEN); Preconditions.checkState(oldState == State.UNINITIALIZED, "Cannot open SystemTable instance in state %s.", oldState); // Prepare some statements for CQL queries final String selectQuery = String.format("SELECT %s FROM %s WHERE %s=?", VALUE_COLUMN, mTable, KEY_COLUMN); mPreparedStatementGetValue = mAdmin.getPreparedStatement(selectQuery); final String insertQuery = String.format("INSERT INTO %s (%s, %s) VALUES (?, ?);", mTable, KEY_COLUMN, VALUE_COLUMN); mPreparedStatementPutValue = mAdmin.getPreparedStatement(insertQuery); } /** {@inheritDoc} */ @Override public synchronized ProtocolVersion getDataVersion() throws IOException { final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot get data version from SystemTable instance in state %s.", state); byte[] result = getValue(KEY_DATA_VERSION); return result == null ? null : ProtocolVersion.parse(Bytes.toString(result)); } /** {@inheritDoc} */ @Override public synchronized void setDataVersion(ProtocolVersion version) throws IOException { final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot set data version in SystemTable instance in state %s.", state); putValue(KEY_DATA_VERSION, Bytes.toBytes(version.toString())); } /** {@inheritDoc} */ @Override public synchronized ProtocolVersion getSecurityVersion() throws IOException { final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot get security version from SystemTable instance in state %s.", state); byte[] result = getValue(SECURITY_PROTOCOL_VERSION); return result == null ? Versions.UNINSTALLED_SECURITY_VERSION : ProtocolVersion.parse(Bytes.toString(result)); } /** {@inheritDoc} */ @Override public synchronized void setSecurityVersion(ProtocolVersion version) throws IOException { Preconditions.checkNotNull(version); final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot set security version in SystemTable instance in state %s.", state); Kiji.Factory.open(mInstanceURI).getSecurityManager().checkCurrentGrantAccess(); putValue(SECURITY_PROTOCOL_VERSION, Bytes.toBytes(version.toString())); } /** {@inheritDoc} */ @Override public synchronized void close() throws IOException { final State oldState = mState.getAndSet(State.CLOSED); Preconditions.checkState(oldState == State.OPEN, "Cannot close KijiSystemTable instance in state %s.", oldState); } /** {@inheritDoc} */ @Override public byte[] getValue(String key) throws IOException { final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot get value from SystemTable instance in state %s.", state); ResultSet resultSet = mAdmin.execute(mPreparedStatementGetValue.bind(key)); // Extra the value from the byte buffer, otherwise return this empty buffer // TODO: Some additional sanity checks here? List<Row> rows = resultSet.all(); Preconditions.checkArgument(rows.size() <= 1, "Expected to get 0 or 1 rows from system table, but got %s.", rows); if (rows.size() == 1) { Row row = rows.get(0); return ByteUtils.toBytes(row.getBytes(VALUE_COLUMN)); } return null; } /** {@inheritDoc} */ @Override public void putValue(String key, byte[] value) throws IOException { //LOG.info(String.format("Putting key, value = %s,%s", key, value)); final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot put value into SystemTable instance in state %s.", state); ByteBuffer valAsByteBuffer = ByteBuffer.wrap(value); // TODO: Check for success? mAdmin.execute(mPreparedStatementPutValue.bind(key, valAsByteBuffer)); } /** {@inheritDoc} */ @Override public CloseableIterable<SimpleEntry<String, byte[]>> getAll() throws IOException { final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot get all from SystemTable instance in state %s.", state); // TODO: Make this a prepared query. String queryText = "SELECT * FROM " + mTable + ";"; ResultSet resultSet = mAdmin.execute(queryText); // Extra the value from the byte buffer, otherwise return this empty buffer // TODO: Some checks here? return new CassandraSystemTableIterable(resultSet); } /** * Loads a map of properties from the properties file named by resource. * * @param resource The name of the properties resource holding the defaults. * @return The properties in the file as a Map. * @throws java.io.IOException If there is an error. */ public static Map<String, String> loadPropertiesFromFileToMap(String resource) throws IOException { final Properties defaults = new Properties(); defaults.load(CassandraSystemTable.class.getClassLoader().getResourceAsStream(resource)); return Maps.fromProperties(defaults); } /** * Load the system table with the key/value pairs specified in properties. Default properties are * loaded for any not specified. * * @param properties The properties to load into the system table. * @throws java.io.IOException If there is an I/O error. */ protected void loadSystemTableProperties(Map<String, String> properties) throws IOException { final Map<String, String> defaults = loadPropertiesFromFileToMap(DEFAULTS_PROPERTIES_FILE); final Map<String, String> newProperties = Maps.newHashMap(defaults); newProperties.putAll(properties); for (Map.Entry<String, String> entry : newProperties.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); putValue(key, Bytes.toBytes(value)); } } /** * Installs a Kiji system table into a running HBase instance. * * @param admin The HBase cluster to install into. * @param kijiURI The KijiURI. * @throws java.io.IOException If there is an error. */ public static void install(CassandraAdmin admin, KijiURI kijiURI) throws IOException { install(admin, kijiURI, ImmutableMap.<String, String>of()); } /** * Installs a Kiji system table into a running C* instance. * * @param admin The Cassandra cluster and keyspace install into. * @param kijiURI The KijiURI. * @param properties The initial system properties to be used in addition to the defaults. * @throws java.io.IOException If there is an error. */ public static void install(CassandraAdmin admin, KijiURI kijiURI, Map<String, String> properties) throws IOException { // Install the table. Sadly, we have to just use blobs and byte arrays here, so that we are // compliant with everything else in Kiji. :( final CassandraTableName systemTableName = CassandraTableName.getSystemTableName(kijiURI); // The layout of this table is straightforward - just blob to blob! // TODO: Any check here first for whether the table exists? final String tableLayout = String.format("CREATE TABLE %s (%s text PRIMARY KEY, %s blob);", systemTableName, KEY_COLUMN, VALUE_COLUMN); admin.createTable(systemTableName, tableLayout); final CassandraSystemTable systemTable = new CassandraSystemTable(kijiURI, admin); try { systemTable.loadSystemTableProperties(properties); } finally { systemTable.close(); } } /** * Disables and delete the system table from HBase. * * @param admin The HBase admin object. * @param kijiURI The URI for the kiji instance to remove. * @throws java.io.IOException If there is an error. */ public static void uninstall(final CassandraAdmin admin, final KijiURI kijiURI) throws IOException { // TODO: Does this actually need to do anything beyond dropping the table? final CassandraTableName tableName = CassandraTableName.getSystemTableName(kijiURI); final String delete = CQLUtils.getDropTableStatement(tableName); admin.execute(delete); } /** {@inheritDoc} */ @Override public SystemTableBackup toBackup() throws IOException { final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot backup SystemTable instance in state %s.", state); ArrayList<SystemTableEntry> backupEntries = new ArrayList<SystemTableEntry>(); CloseableIterable<SimpleEntry<String, byte[]>> entries = getAll(); for (SimpleEntry<String, byte[]> entry : entries) { backupEntries.add(SystemTableEntry.newBuilder().setKey(entry.getKey()) .setValue(ByteBuffer.wrap(entry.getValue())).build()); } return SystemTableBackup.newBuilder().setEntries(backupEntries).build(); } /** {@inheritDoc} */ @Override public void fromBackup(SystemTableBackup backup) throws IOException { final State state = mState.get(); Preconditions.checkState(state == State.OPEN, "Cannot restore backup to SystemTable instance in state %s.", state); LOG.info(String.format("Restoring system table from backup with %d entries.", backup.getEntries().size())); for (SystemTableEntry entry : backup.getEntries()) { putValue(entry.getKey(), entry.getValue().array()); } // TODO: Flush? //mTable.flushCommits(); } /** Private class for providing a CloseableIterable over system table key, value pairs. */ private static class CassandraSystemTableIterable implements CloseableIterable<SimpleEntry<String, byte[]>> { /** Uderlying source of system table parameters. */ //private ResultScanner mResultScanner; /** Iterator returned by iterator(). */ private Iterator<SimpleEntry<String, byte[]>> mIterator; /** * Create a new CassandraSystemTableIterable across system table properties. * * @param resultSet scanner across the target cells. */ public CassandraSystemTableIterable(ResultSet resultSet) { mIterator = new CassandraSystemTableIterator(resultSet.iterator()); //mResultScanner = resultScanner; } /** {@inheritDoc} */ @Override public Iterator<SimpleEntry<String, byte[]>> iterator() { return mIterator; } /** {@inheritDoc} */ @Override public void close() throws IOException { //mResultScanner.close(); } } /** Private class for providing an Iterator to HBaseSystemTableIterable. */ private static class CassandraSystemTableIterator implements Iterator<SimpleEntry<String, byte[]>> { /** * Iterator across result scanner results. * Used to build next() for HBaseSystemTableIterator */ private Iterator<Row> mRowIterator; /** * Create an HBaseSystemTableIterator across the results of a ResultScanner. * * @param rowIterator iterator across the scanned cells. */ public CassandraSystemTableIterator(Iterator<Row> rowIterator) { mRowIterator = rowIterator; } /** {@inheritDoc} */ @Override public boolean hasNext() { return mRowIterator.hasNext(); } /** {@inheritDoc} */ @Override public SimpleEntry<String, byte[]> next() { Row next = mRowIterator.next(); String key = next.getString(KEY_COLUMN); byte[] value = ByteUtils.toBytes(next.getBytes(VALUE_COLUMN)); return new SimpleEntry<String, byte[]>(key, value); } /** {@inheritDoc} */ @Override public void remove() { throw new UnsupportedOperationException(); } } /** {@inheritDoc} */ @Override public String toString() { return Objects.toStringHelper(CassandraSystemTable.class).add("uri", mInstanceURI) .add("state", mState.get()).toString(); } /** {@inheritDoc} */ @Override public KijiURI getKijiURI() { return mInstanceURI; } }