Java tutorial
/* * Copyright 2003-2008 the original author or authors. * * 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. * * You are receiving this code free of charge, which represents many hours of * effort from other individuals and corporations. As a responsible member * of the community, you are asked (but not required) to donate any * enhancements or improvements back to the community under a similar open * source license. Thank you. -TMN */ package info.shelfunit.hbase; import groovy.lang.Closure; import groovy.util.Proxy; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.MasterNotRunningException; import org.apache.hadoop.hbase.ZooKeeperConnectionException; // added import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HTable; // import org.apache.hadoop.hbase.client.RowLock; // import org.apache.hadoop.hbase.client.Scanner; import org.apache.hadoop.hbase.client.ResultScanner; // import org.apache.hadoop.hbase.io.BatchUpdate; import org.apache.hadoop.hbase.client.Put; // import org.apache.hadoop.hbase.io.Cell; import org.apache.hadoop.hbase.Cell; // import org.apache.hadoop.hbase.io.RowResult; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.conf.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p>Code to implement a builder-style DSL in Groovy for the HBase client API. * This provides convenience method as well as providing a more declarative * wrapper for HBase. The class currently supports wrappers for * {@link #create(String, Closure) creating and modifying} tables, * {@link #update(HTable, Closure) batch updates}, and * {@link #scan(Map, HTable, Closure) scanners}.</p> * * <p>Example: * <pre> * def hbase = HBaseBuilder.connect() // may optionally pass host name * * // Create: this will create a table if it does not exist, or disable * & update column families if the table already does exist. The table will be enabled when the create statement returns * hbase.create( 'myTable' ) { * family( 'familyOne' ) { * inMemory = true * bloomFilter = false * } * family 'familyTwo' // relaxed Groovy syntax form without parens and column family defaults * } * * * // Insert/ update rows: * hbase.update( 'myTable' ) { * row( 'rowOne' ) { * family( 'familyOne' ) { * col 'one', 'someValue' * col 'two', 'anotherValue' * col 'three', 1234 * } * * // alternate form that doesn't use nested family name: * col 'familyOne:four', 12345 * } * row( 'rowTwo' ) { more column values } * // etc * } * * * // scan table: * hbase.scan( cols : ['familyOne:*'] ) { row -> * println "Scanning row: ${row.key}" * println " Column val : ${row.getString('familyOne:one')}" * * // return this value to break out of a scan loop * SCAN_BREAK * } * </pre></p> * * @author <a href='mailto:tnichols@enernoc.com'>Tom Nichols</a> */ public class HBaseBuilder { protected Logger log = LoggerFactory.getLogger(getClass()); /** Return this value to break out of a scan closure execution loop if the * user wants to end scanning. Since HBase is in the scan closure scope, * all that is necessary is to call <code>return SCAN_BREAK</code> */ public final String SCAN_BREAK = "com.enernoc.rnd.eps.SCANNER_BREAK"; // HBaseConfiguration conf; Configuration conf; HBaseAdmin admin; /** Default table to use for update/ scan operations */ HTable table; /* Configuration conf = HBaseConfiguration.create(); HTable table = new HTable(conf, mytable?); table.setAutoFlush(false); table.setWriteBufferSize(2 * 1024 * 1024); // 2 Mb // ... do useful stuff table.close() */ /* protected HBaseBuilder() { this( new HBaseConfiguration() ); // HBaseConfiguration.create() ); } */ // protected HBaseBuilder( HBaseConfiguration conf ) { protected HBaseBuilder() { this.conf = HBaseConfiguration.create(); log.info("-------------------------- Connecting to host: {}", conf.toString()); try { HBaseAdmin theAdmin = new HBaseAdmin(conf); theAdmin.checkHBaseAvailable(conf); // HTable table2 = new HTable(conf, "wiki"); // HTableDescriptor table2 = new HTableDescriptor( "wiki2" ); System.out.println("-------------------------- Got a table"); // To add to a row, use Put. A Put constructor takes the name of the row // you want to insert into as a byte array. In HBase, the Bytes class // has utility for converting all kinds of java types to byte arrays. In // the below, we are converting the String "myLittleRow" into a byte // array to use as a row key for our update. Once you have a Put // instance, you can adorn it by setting the names of columns you want // to update on the row, the timestamp to use in your update, etc. // If no timestamp, the server applies current time to the edits. // Put p = new Put(Bytes.toBytes("myLittleRow")); // table2.put( p ); /* HTable table = new HTable(conf, mytable?); table.setAutoFlush(false); table.setWriteBufferSize(2 * 1024 * 1024); // 2 Mb // ... do useful stuff HTable table = ... // instantiate HTable Put put = new Put(Bytes.toBytes(key1?)); put.add(Bytes.toBytes(colfam?), Bytes.toBytes(qual?), Bytes.toBytes(my_value?)); put.add(...); ... table.put(put); */ // table.close(); } catch (Exception e) { e.printStackTrace(); } ////////////////////////////////////////////////////////// } protected HBaseBuilder(Configuration conf) { log.info("Connecting to host: {}", conf.toString()); // get( HConstants.DEFAULT_HOST ) ); this.conf = conf; } /** * Connect to HBase using the default configuration (similar to the no-arg * constructor for {@link HBaseAdmin} and {@link HTable}. * @return the HBase builder instance */ public static HBaseBuilder connect() { return new HBaseBuilder(); } /** * Connect to the given HBase host address. * @see HConstants#MASTER_ADDRESS * @param host host name or IP of the master HBase node. * @return the HBase builder instance */ public static HBaseBuilder connect(String host) { // HBaseConfiguration conf = new HBaseConfiguration(); Configuration conf = HBaseConfiguration.create(); conf.set(HConstants.DEFAULT_HOST, host); return new HBaseBuilder(conf); } /** * Connect to HBase using the given configuration. Analogous to calling * <code>new HTable( conf )</code> and <code>new HBaseAdmin( conf )</code> * @param conf configuration to use for connecting to the HBase server. * @return the HBase builder instance */ public static HBaseBuilder connect(HBaseConfiguration conf) { return new HBaseBuilder(conf); } /** * Allows for named arguments to be passed to the <code>connect</code> * method. All arguments will be converted into a <code>HBaseConfiguration</code> * instance <strong>except</strong> for the following: * <dl> * <dt>host</dt><dd>Host name to connect to. Short for * {@link HConstants#DEFAULT_HOST}. This value will override * any master host address set in a loaded conf file.</dd> * <dt>table</dt><dd>Default table name to use (see {@link #setTableName(String)})</dd> * <dt>confURL</dt><dd>Location of an HBase configuration file to load. * This file is loaded before any extra named parameters are added * to the configuration instance, so explicit named parameters passed to * this method take preference over what is loaded from the config file.</dd> * </dl> * Example: * <pre>def hbase = HBaseBuilder.connect( host : 'localhost', table: 'myTable', * confURL : 'file:///opt/hbase/conf/myconf.xml', * HConstants.HBASE_DIR : '/opt/hbase' )</pre> * @param args * @return the HBase builder instance * @throws IOException if the 'confURL' or tableName param is invalid */ public static HBaseBuilder connect(Map<String, Object> args) throws IOException { String host = (String) args.remove("host"); String tableName = (String) args.remove("table"); String confURL = (String) args.remove("confURL"); HBaseConfiguration conf = new HBaseConfiguration(); if (confURL != null) conf.addResource(new URL(confURL)); // pass any remaining args to HBaseConfiguration for (Map.Entry<String, Object> entry : args.entrySet()) { Object val = entry.getValue(); String key = entry.getKey(); if (val == null) continue; else if (val instanceof Integer) conf.setInt(key, (Integer) val); else if (val instanceof Long) conf.setLong(key, (Long) val); else if (val instanceof Boolean) conf.setBoolean(key, (Boolean) val); else if (val instanceof String[]) conf.setStrings(key, (String[]) val); else conf.set(key, val.toString()); } if (host != null) conf.set(HConstants.DEFAULT_HOST, host); HBaseBuilder hb = new HBaseBuilder(conf); hb.setTableName(tableName); return hb; } /** * Create an HBase table, or modify the properties of an existing table. If * the table already exists, it will be disabled for modification. When * this method returns, the table will be enabled. * * * @see CreateDelegate * @see HBaseAdmin#createTable(HTableDescriptor) * @param tableName name of the table to create or modify * @param tableConfig closure in which to define table settings as well as * calls to family definiton. The closure should accept a single parameter, * which will be an {@link HTableDescriptor} instance which will be used * to create or modify the table once the closure returns. If the table * already exists, the HTableDescriptor instance will be retrieved from * a call to {@link HBaseAdmin#listTables()}. * @return the HTableDescriptor used to define this table. * @throws MasterNotRunningException * @throws IOException thrown by HBaseAdmin */ public HTableDescriptor create(String tableName, Closure tableConfig) throws MasterNotRunningException, IOException { HTableDescriptor table = null; if (getAdmin().tableExists(tableName)) { for (HTableDescriptor td : admin.listTables()) { if (td.getNameAsString().equals(tableName)) { table = td; break; } } log.info("Taking table {} offline for modification", tableName); /* table needs to be disabled before calling the create delegate where family configuration is done on a live table descriptor */ if (admin.isTableEnabled(tableName)) { admin.disableTable(tableName); } } else { log.info("Creating table '{}'...", tableName); table = new HTableDescriptor(tableName); } CreateDelegate cd = new CreateDelegate(admin, table); tableConfig.setDelegate(cd); tableConfig.setResolveStrategy(Closure.DELEGATE_FIRST); tableConfig.call(table); if (admin.tableExists(tableName)) { byte[] tableNameBytes = tableName.getBytes(); List<HColumnDescriptor> existingCols = Arrays .asList(admin.getTableDescriptor(Bytes.toBytes(tableName)).getColumnFamilies()); for (HColumnDescriptor col : table.getColumnFamilies()) { if (existingCols.contains(col)) { // admin.modifyColumn( tableNameBytes, col.getName(), col ); admin.modifyColumn(tableNameBytes, col); } else { admin.addColumn(tableNameBytes, col); } } /* currently this will not delete existing columns if they are not in the new table definition (just to be safe) */ //TODO also update properties on the table? log.info("Updated table: {}", table); } else { admin.createTable(table); log.info("Created table: {}", table); } if (!admin.isTableEnabled(tableName)) { admin.enableTable(tableName); } log.debug("Enabled table: {}", tableName); return table; } /** * Perform row updates on the current table. * @see #setTableName(String) * @see #update(HTable, Closure) * @param updateClosure * @return the HTable instance used for updates. * @throws IOException */ public HTable update(Closure updateClosure) throws IOException { return this.update(this.table, updateClosure); } /** * Short for <code>hbase.update( new HTable(hbase.getConfiguration(), * tableName), updateClosure );</code> * @param tableName table on which to perform the updates * @param updateClosure code to create row and column updates * @return The HTable instance used for committing the BatchUpdates * @throws IOException if the underlying HTable encounters errors during update */ public HTable update(String tableName, Closure updateClosure) throws IOException { return this.update(new HTable(conf, tableName), updateClosure); } /** * <p>Update rows in the given table. The HTable instance will be passed to * the <code>updateClosure</code>. Within that closure, helper methods from * an {@link UpdateDelegate} instance will be available to automatically * create {@link BatchUpdate}s and set column values. All BatchUpdates * are committed after the closure returns.</p> * <p>Example: * <pre>hbase.update( myTable ) { * row( '1234' ) { update -> // creates a BatchUpdate instance * * // helper method that sets the value 1234 on the column 'family:name' on this BatchUpdate * col 'family:name', 1234 * * // if updating many columns in the same family: * family( 'familyName' ) { * // all column updates will be prepended with 'familyName:' * col 'col1', 1234 * col 'col2', 'someString' * col 'col3', * } * }</pre></p> * @see UpdateDelegate * @param table table on which to perform the updates * @param updateClosure code to create row and column updates * @return The HTable instance used for committing the BatchUpdates * @throws IOException if the underlying HTable encounters errors during update */ public HTable update(HTable table, Closure updateClosure) throws IOException { UpdateDelegate delegate = new UpdateDelegate(); try { updateClosure.setDelegate(delegate); updateClosure.setResolveStrategy(Closure.DELEGATE_FIRST); updateClosure.call(table); String tableName = new String(table.getTableName()); List<Put> updates = delegate.getUpdates(); for (Put update : updates) { // table.commit( update ); table.put(update); log.trace("Row update to table '{}': {}", tableName, update); } log.debug("Updated {} rows in table '{}'", updates.size(), tableName); } finally { delegate = null; } return table; } /** * Return a single row. Optional named arguments: * <dl> * <dt>cols</dt><dd>List of columns to return. May be a simple regex pattern</dd> * <dt>versions</dt><dd>numbers of versions</dd> * <dt>timestamp</dt><dd>Either a <code>Date</code> or <code>long</code></dd> * <dt>lock</dt><dd>RowLock instance</dd> * </dl> * * @param args optional named arguments * @param row required row ID to retrieve * @param table required table to search * @return a {@link RowResultProxy} of the row, or <code>null</code> if the * underlying call to {@link HTable#getRow(String) getRow} returned null. * @throws IOException */ /* public RowResultProxy getRow( Map<String,Object> args, String row, HTable table ) throws IOException { long timestamp = getTimestamp( args.get("timestamp") ); Object val = args.get("versions"); Integer versions = val != null ? (Integer)val : 1; String[] cols = null; val = null; val = args.get("cols"); if ( val != null ) { // should be a list List<?> list = (List<?>)val; cols = new String[list.size()]; for ( int i=0; i< list.size(); i++ ) { cols[i] = list.get(i).toString(); } } // might be null; this is OK. RowLock lock = ( RowLock )args.get( "lock" ); Get theGet = new Get( Bytes.toBytes( row ) ); // is this the row, or the row name? // Result rr = table.getRow( row, cols, timestamp, versions, lock ); Result rr = table.get( theGet ); if ( rr == null ) { return null; } return new RowResultProxy( rr ); } */ /** * See {@link #getRow(Map, String, HTable)}. * @param args optional named arguments * @param row required row ID to retrieve * @param table required table to search * @return a {@link RowResultProxy} of the row, or <code>null</code> if the * underlying call to {@link HTable#getRow(String) getRow} returned null. * @throws IOException */ /* public RowResultProxy getRow( Map<String,Object> args, String row, String tableName ) throws IOException { return getRow( args, row, new HTable( conf, tableName ) ); } */ /** * See {@link #getRow(Map, String, HTable)}. * @param args optional named arguments * @param row required row ID to retrieve * @return a {@link RowResultProxy} of the row, or <code>null</code> if the * underlying call to {@link HTable#getRow(String) getRow} returned null. * @throws IOException */ /* public RowResultProxy getRow( Map<String,Object> args, String row ) throws IOException { if ( this.table == null ) { throw new IllegalStateException( "tableName property must be set before calling methods that do not specify a table name." ); } return getRow( args, row, this.table ); } */ /** * Short form of {@link #scan(Map,HTable,Closure)}. This method uses * the default table defined in {@link #setTableName(String)}. */ /* public HTable scan( Map<String,Object> args, Closure scanClosure ) throws IOException { return this.scan( args, this.table, scanClosure ); } */ /** * Alternate form of {@link #scan(Map,HTable,Closure)} which creates an * HTable instance from the <code>tableName</code> string argument. Note * that for readability, the table name may be given as the first parameter, * i.e. <code>htable.scan( 'myTable', cols: ['col:1'] ) { /* result closure * / }</code> * @see #scan(Map,HTable,Closure) * @param args named parameters for scanning * @param tableName name of the table to scan * @param scanClosure closure to iterate over each row result * @return the table that was scanned * @throws IOException if the underlying HBase API throws an exception */ /* public HTable scan( Map<String,Object> args, String tableName, Closure scanClosure ) throws IOException { return this.scan( args, new HTable( conf, tableName ), scanClosure ); } */ /** * <p>Create a scanner on the given table passing each {@link Result} to * the <code>scanClosure</code>. The Scanner is guaranteed to be closed * when the scanner iteration completes (either normally or due to an * exception). Note that each <code>Result</code> is actually {@link RowResultProxy wrapped} with additional * convenience methods when it is passed to the <code>scanClosure</code>.</p> * * <p>Valid named arguments are: * <dl> * <dt>cols</dt><dd>List of columns to return. If not specified, all columns in all families will be returned.</dd> * <dt>start</dt><dd>Starting row</dd> * <dt>end</dt><dd>Ending row (not passed to the closure)</dd> * <dt>timestamp</dt><dd>Either a <code>Date</code> or <code>long</code></dd> * </dl> * </p> * <p>Note that for readability, the table name may be given as the first * parameter, i.e. * <code>htable.scan( 'myTable', cols: ['col:1'] ) { print it['col:1'] }</code> * @see RowResultProxy * @param args named parameters for scanning * @param table table to scan * @param scanClosure closure to iterate over each row result * @return the table that was scanned * @throws IOException if the underlying HBase API throws an exception */ /* public HTable scan( Map<String,Object> args, HTable table, Closure scanClosure ) throws IOException { // TODO this might throw a CCE if value is a GString rather than a String... List<String> cols = (List<String>)args.get("cols"); byte[][] colArray; if ( cols == null ) { // get all columns in all families Collection<HColumnDescriptor> cds = table.getTableDescriptor().getFamilies(); colArray = new byte[cds.size()][]; int i = 0; for ( HColumnDescriptor cd : cds ) { log.debug( "Scanning all columns in family: {}", cd.getNameAsString() ); colArray[i++] = cd.getName(); // WithColon(); } } else { colArray = Bytes.toByteArrays( cols.toArray( new String[ cols.size() ] ) ); } long timestamp = getTimestamp( args.get("timestamp") ); Object tempVal = args.get("start"); byte[] startRow = tempVal instanceof byte[] ? (byte[])tempVal : tempVal != null ? Bytes.toBytes( tempVal.toString() ) : HConstants.EMPTY_START_ROW; tempVal = args.get("end"); byte[] endRow = tempVal instanceof byte[] ? (byte[])tempVal : tempVal != null ? Bytes.toBytes( tempVal.toString() ) : null; // endRow may be null; this is OK ResultScanner scanner; Scan scan; if (endRow == null ) { scan = new Scan( startRow ); } else { scan = new Scan( startRow, endRow ); } // table.getScanner( colArray, startRow ) : table.getScanner( colArray, startRow, endRow, timestamp ); scanner = table.getScanner( scan ); scanClosure.setDelegate( this ); int rowCount = 0; long ts = System.currentTimeMillis(); try { Object result = null; Proxy rowProxy = new RowResultProxy(); while ( result != SCAN_BREAK ) { Result row = scanner.next(); if ( row == null ) { break; } rowCount ++; rowProxy.setAdaptee( row ); result = scanClosure.call( rowProxy ); } } finally { scanner.close(); ts = System.currentTimeMillis() - ts; if ( log.isDebugEnabled() ) { log.debug( "Scanned {} rows on table '{}' in {}ms", new Object[] {rowCount, new String(table.getTableName()), ts} ); } } return table; } */ /** * Can convert Date and Calendar instances to Byte[] for storage, as well as * those supported by {@link Bytes}. Note that Calendar timzone and locale * are not preserved. (Calendar and Date are both simply converted to longs). * @param val value to convert * @return byte[] suitable for storage by HBase * @throws IllegalArgumentException if the argument value is not a * convertible type */ public static byte[] getBytes(Object val) throws IllegalArgumentException { if (val == null) return null; if (val.getClass() == String.class) return Bytes.toBytes((String) val); if (val.getClass() == Integer.class) return Bytes.toBytes((Integer) val); if (val.getClass() == Long.class) return Bytes.toBytes((Long) val); //TODO Double if (val instanceof Date) return Bytes.toBytes(((Date) val).getTime()); if (val instanceof Calendar) return Bytes.toBytes(((Calendar) val).getTime().getTime()); throw new IllegalArgumentException("Value must be either a String, Number, Date or Calendar"); } /** * Create a timestamp from the given object. The object may be an int, * long, Date or Calendar. * @param ts value to convert * @return a suitable HBase timestamp value * @throws IllegalArgumentException if an object of the wrong type is given. */ public static long getTimestamp(Object ts) throws IllegalArgumentException { if (ts == null) return HConstants.LATEST_TIMESTAMP; if (ts.getClass() == Long.class || ts.getClass() == Integer.class) return (Long) ts; if (ts instanceof Date) return ((Date) ts).getTime(); if (ts instanceof Calendar) return ((Calendar) ts).getTime().getTime(); throw new IllegalArgumentException("Timestamp must be either a long, Date or Calendar"); } /** * @return the configuration used by this instance to connect to HBase */ /* public HBaseConfiguration getConfiguration() { return this.conf; } */ public Configuration getConfiguration() { return this.conf; } /** * @return HBaseAdmin instance used when creating or modifying table * structures. An HBaseAdmin instance is normally not created until * required by a call to <code>create(tableName) {...}</code>. This * method will force an HBaseAdmin instance to be created if it has not * already. The underlying {@link getConfiguration() HBaseConfiguration} * is passed to the HBaseAdmin instance. * @return the HBaseAdmin instance that will be used for calls to * <code>create(..)</code> * @throws MasterNotRunningException */ public HBaseAdmin getAdmin() throws MasterNotRunningException, ZooKeeperConnectionException, IOException { if (this.admin == null) { this.admin = new HBaseAdmin(this.conf); } return this.admin; } /** Set the name of the 'default' table, i.e. when no table name is given * in <code>update</code> and <code>scan</code> operations. * @throws IOException if the table name is not a valid table in the current * HBase server. */ public void setTableName(String name) throws IOException { if (name == null || name.length() < 1) { this.table = null; return; } setTable(new HTable(this.conf, name)); } /** * Set the default table used for all methods that do not accept a 'table' * or 'tableName' parameter. * @param table */ public void setTable(HTable table) { this.table = table; } /** * This class is used as the {@link Closure} delegate to add the * <code>family('familyName')</code> method in the scope of the * {@link #create(String, Closure)} method's closure. */ public class CreateDelegate { protected HBaseAdmin admin; protected HTableDescriptor table; /** Called internally by the HBase builder */ protected CreateDelegate(HBaseAdmin admin, HTableDescriptor table) { this.admin = admin; this.table = table; } /** * Create a column family with all default options * @param familyName family name to create * @return the column family definition that was created * @throws IOException if any errors were thrown by the HBase API */ public HColumnDescriptor family(String familyName) throws IOException { return this.family(familyName, null); } public HColumnDescriptor family(String familyName, Closure familyConfig) throws IOException { // column family names must end w/ a colon. if (!familyName.endsWith(":")) { familyName += ':'; } HColumnDescriptor colFamily = table.getFamily(familyName.getBytes()); if (colFamily == null) { colFamily = new HColumnDescriptor(familyName); } // Allow closure arg to configure column family options: if (familyConfig != null) { familyConfig.setDelegate(colFamily); familyConfig.setResolveStrategy(Closure.DELEGATE_ONLY); familyConfig.call(); } // Add column to table. if (!table.hasFamily(familyName.getBytes())) { table.addFamily(colFamily); } else { admin.modifyColumn(table.getNameAsString(), colFamily); } return colFamily; } } // end class CreateDelegate /** * This class is used as the {@link Closure} delegate to add the * <code>family('familyName')</code> and <code>col( name, val)</code> * methods in the scope of the {@link HBase#update(HTable, Closure)} * method's closure argument. */ public class UpdateDelegate { // list of BatchUpdates created from calls to row('id') {....} private List<Put> updates = new ArrayList<Put>(); /** * List of updates created from calls to {@link #row(String, Closure)}. * Each <code>row(..)</code> call creates a new BatchUpdate which is * then appended to this list when the call returns. * @return */ public List<Put> getUpdates() { return this.updates; } /** * Current update instance, available for direct access within each 'row' * call. This will be null outside of any row closure. */ protected Put currentUpdate; /** Set by the call to {@link #family(String, Closure)}. Within the * family closure, this will be set to the family name argument. */ protected String currentFamily = null; /** * User-set timestamp that should be set at the beginning of the * update { ... } closure. If this is not explicitly set it will * default to HConstants.LATEST_TIMESTAMP, which is what BatchUpdate * defaults to. */ Object defaultTimestamp = HConstants.LATEST_TIMESTAMP; /** * Short form for row( rowName, timestamp, rowClosure ) which uses the * default timestamp. */ public void row(String rowName, Closure rowClosure) { row(rowName, this.defaultTimestamp, rowClosure); } /** * Method called within the 'update' closure in order to set values * for each row being inserted or updated. This method takes a timestamp * parameter that is used for all values set in this row update. The * parameter may be of type long, Date or Calendar. The current * BatchUpdate instance is also passed as a parameter to the rowClosure. * @see HBase#getTimestamp(Object) * @param rowName the row key * @param timestamp Date, Calendar or long timestamp to be used for this row update * @param rowClosure closure used to set column values via calls to 'family', * 'col' or direct use of the 'currentUpdate' BatchUpdate property. */ public void row(String rowName, Object timestamp, Closure rowClosure) { if (this.currentUpdate != null) { throw new IllegalStateException("Cannot nest row calls"); } this.currentUpdate = new Put(Bytes.toBytes(rowName)); rowClosure.setDelegate(this); rowClosure.setResolveStrategy(Closure.DELEGATE_FIRST); rowClosure.call(currentUpdate); // currentUpdate.setTimestamp( HBaseBuilder.getTimestamp( timestamp ) ); this.updates.add(this.currentUpdate); this.currentUpdate = null; // 'currentUpdate' should not be available outside of row closure } /** * Method closure used within the row closure to insert several columns in * the same column family. All calls to <code>col( 'name', 'val' )</code> * within this closure will have the enclosing family prepended to each * column name. * * @param familyName family name to automatically use within the colClosure * scope. The family name will automatically be appended with ':' if * not explicitly supplied. * @param colClosure all <code>col</code> calls within this closure will * automatically be prepended with this family name. */ public void family(String familyName, Closure colClosure) { if (currentFamily != null) throw new IllegalStateException("Cannot nest family calls"); if (currentUpdate == null) throw new IllegalStateException("Family must be called from within a row closure"); currentFamily = familyName.endsWith(":") ? familyName : (familyName + ':'); colClosure.setDelegate(this); colClosure.setResolveStrategy(Closure.DELEGATE_FIRST); colClosure.call(); //currentUpdate currentFamily = null; } /** * Method call to set an individual column value. This may be called * directly from a row closure, or from a nested family closure. If it * is called directly from the row closure, a family-qualified column * name must be given (i.e. <code>col( 'family:name', val )</code>. If * this is called from a family closure, only the column name should be * given, i.e. <code>col( 'name', val )</code>. * @see HBase#getBytes(Object) */ public void col(String columnName, Object val) { if (currentUpdate == null) { throw new IllegalStateException("Col must be called from within a row or family closure"); } if (currentFamily != null) { columnName = currentFamily + columnName; } // currentUpdate.put( columnName, HBaseBuilder.getBytes(val) ) ; currentUpdate.add(Bytes.toBytes(currentFamily), Bytes.toBytes(columnName), HBaseBuilder.getBytes(val)); } } // end class UpdateDelegate /** * This wraps {@link Result} instances that are passed to the closure * call in {@link HBase#scan(Map, HTable, Closure) HBase.scan(..)}. This * class essentially extends Result; Groovy's method dispatch will pass * any method calls to the Result instance that do not match the * signature of a method on this class. */ /* public class RowResultProxy extends Proxy implements Iterable<CellResult> { public RowResultProxy() { super(); } public RowResultProxy( Result rr ) { super(); this.setAdaptee( rr ); } /** * Convenience iterator to traverse the set of column keys, assuming * each key is a String. * @Override public Iterator<CellResult> iterator() { return new Iterator<CellResult>() { Iterator<byte[]> iter = getRow().keySet().iterator(); Result row = getRow(); @Override public CellResult next() { byte[] key = iter.next(); return new CellResult( key, row.get(key) ); } @Override public boolean hasNext() { return iter.hasNext(); } @Override public void remove() { iter.remove(); } }; } public CellResult getAt( String key ) { return getAt( Bytes.toBytes( key ) ); } public CellResult getAt( byte[] bKey ) { return new CellResult( bKey, getRow().get( bKey ) ); } /** * Returns the underlying RowResult instance. Note that direct access * to this instance is probably unnecessary as any RowResult method * calls to the proxy are automatically delegated to this underlying * instance. * @return the proxied {@link Result} instance. * public Result getRow() { return ( Result ) getAdaptee(); } /** Convenience method to convert the row key to a string * public String getKey() { return Bytes.toString( getRow().getRow() ); } /** Convenience method to convert row key to a long * public long getKeyAsLong() { return Bytes.toLong( getRow().getRow() ); } /** * Retrieve the given column value for this row as a String * public String getString( String col ) { return Bytes.toString( getRow().get( col.getBytes() ).getValue() ); } /** * Retrieve the given column value for this row as an Int * public int getInt( String col ) { return Bytes.toInt( getRow().get( col.getBytes() ).getValue() ); } /** * Retrieve the given column value for this row as a Long * public long getLong( String col ) { return Bytes.toLong( getRow().get( col.getBytes() ).getValue() ); } /** * Retrieve the given column value for this row as a Date * public Date getDate( String col ) { return new Date( getLong(col) ); } /** * Retrieve the given column value for this row as a String * TODO implement when Bytes.toDouble is implemented * public double getDouble( String col ) { throw new UnsupportedOperationException( "Not yet supported" ); // return Bytes.toDouble( getRow().get( col.getBytes() ).getValue() ); } } // end class RowResultProxy */ /** * Wraps a {@link Cell} in a number of convenience methods, as well as * retaining the column key which is associated with it. */ public class CellResult { byte[] name; Cell cell; public CellResult(byte[] key, Cell cell) { this.name = key; this.cell = cell; } public byte[] getKey() { return this.name; } public String getName() { return Bytes.toString(name); } public Cell getCell() { return this.cell; } public byte[] getValue() { return this.cell.getValue(); } public String getString() { return Bytes.toString(getValue()); } public int getInt() { return Bytes.toInt(getValue()); } public long getLong() { return Bytes.toLong(getValue()); } public Date getDate() { return new Date(getLong()); } /** * Return the timestamp of the underlying cell * @see Cell#getTimestamp() */ public long getTimestamp() { return cell.getTimestamp(); } } // end class CellResult } // end class info.shelfunit.hbase.HBaseBuilder