package com.opower.updater; import com.google.common.base.Preconditions; import com.google.common.io.Closeables; import com.opower.updater.admin.KijiLayoutUpdateTable; import com.opower.updater.admin.KijiLayoutUpdateTableUtils; import com.opower.updater.admin.LayoutUpdateTable; import com.opower.updater.admin.loader.ResourceUpdateLoader; import com.opower.updater.admin.loader.UpdateLoader; import com.opower.updater.ddl.DDLRunner; import com.opower.updater.ddl.KijiDDLRunner; import com.opower.updater.operation.ShellClientTableDDLGenerator; import com.opower.updater.operation.TableUpdater; import com.opower.updater.operation.UpdateException; import org.kiji.schema.Kiji; import org.kiji.schema.KijiURI; import org.kiji.schema.shell.api.Client; import org.kiji.schema.util.ToJson; import java.io.IOException; import java.util.List; /** * Base class for updater tools that operates on a single kiji instance table. * * @author felix.trepanier */ public abstract class BaseUpdaterTableTool extends BaseUpdaterTool { private KijiURI tableURI; protected Client client; protected Kiji kiji; protected KijiLayoutUpdateTable updateTable; protected DDLRunner ddlRunner; protected UpdateLoader updateLoader; protected BaseUpdaterTableTool(UpdaterLocker locker) { super(locker); } /** * @return The command line flag corresponding to the given table URI. */ protected abstract String getTableURIFlag(); /** * Execute the tool operation on the kiji table. This operation is guarded by an exclusive lock obtained from * zookeeper. * * @return The operation result code. * @throws Exception */ protected abstract int executeTableOperation() throws Exception; /** * @return The update loader to use for loading updates. */ protected UpdateLoader createUpdateLoader() { return ResourceUpdateLoader.DEFAULT; } /** * {@inheritDoc} */ @Override protected void validateFlags() throws Exception { super.validateFlags(); String tableURIFlag = getTableURIFlag(); Preconditions.checkArgument((tableURIFlag != null) && !tableURIFlag.isEmpty(), "Specify the table to create with --table=kiji://hbase-address/kiji-instance/table"); tableURI = KijiURI.newBuilder(tableURIFlag).build(); } /** * {@inheritDoc} */ @Override protected void setup() throws Exception { super.setup(); kiji = Kiji.Factory.open(kijiURI, getConf()); KijiLayoutUpdateTableUtils.ensureLayoutUpdateTableExists(kiji); updateTable = KijiLayoutUpdateTable.newInstance(kiji); client = Client.newInstance(kijiURI); ddlRunner = new KijiDDLRunner(client); //TODO todo find a way to get this from the classpath so that UpdateLoader can be customized updateLoader = createUpdateLoader(); } /** * {@inheritDoc} */ @Override protected final KijiURI createKijiURI() { return tableURI; } /** * @return The name of the kiji table the tool is operating on. */ protected final String getKijiTableName() { return kijiURI.getTable(); } /** * {@inheritDoc} */ @Override protected final int executeToolOperation(List<String> nonFlagArgs) throws Exception { autoUpdateLayoutUpdateTable(kiji, updateTable, ddlRunner); try { return executeTableOperation(); } catch (UpdateException ex) { getPrintStream().println("Error: Could not apply update " + ex.getFailedUpdate().getId() + " for table " + ex.getTableName() + " because of '" + ex.getMessage() + "'"); getPrintStream().println("Full error report: \n" + generateUpdateErrorReport(kiji, ex)); return FAILURE; } } /** * {@inheritDoc} */ @Override protected void cleanup() throws IOException { Closeables.close(client, true); Closeables.close(updateTable, true); try { kiji.release(); } finally { kiji = null; super.cleanup(); } } private void autoUpdateLayoutUpdateTable(Kiji kiji, KijiLayoutUpdateTable layoutUpdateTable, DDLRunner kijiDDLRunner) throws IOException { getPrintStream().println("Updating the internal '" + LayoutUpdateTable.TABLE_NAME + "' table."); new TableUpdater(kijiDDLRunner, layoutUpdateTable, kiji.getMetaTable(), new ShellClientTableDDLGenerator(client)) .updateTable(LayoutUpdateTable.TABLE_NAME, ResourceUpdateLoader.DEFAULT.loadUpdates(LayoutUpdateTable.TABLE_NAME), false); } private String generateUpdateErrorReport(Kiji kiji, UpdateException updateException) throws IOException { return "Problematic update DDL is: \n" + updateException.getFailedUpdate().getDDL() + "\n" + "Table " + updateException.getTableName() + " current layout is:\n" + ToJson.toJsonString(kiji.getMetaTable().getTableLayout(updateException.getTableName()).getDesc()); } }