/** * Copyright 2014 Sunny Gleason and 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. */ package io.kazuki.v0.store.cmd; import io.airlift.command.Arguments; import io.airlift.command.Cli; import io.airlift.command.Cli.CliBuilder; import io.airlift.command.Command; import io.airlift.command.Help; import io.airlift.command.Option; import io.airlift.command.OptionType; import io.kazuki.v0.internal.helper.EncodingHelper; import io.kazuki.v0.store.cmd.impl.SqlCommandHelper; import io.kazuki.v0.store.cmd.impl.SqlCommandHelper.ResultHandler; import io.kazuki.v0.store.cmd.impl.SqlCommandHelper.RowHandler; import io.kazuki.v0.store.guice.KazukiModule; import io.kazuki.v0.store.guice.impl.DataSourceModuleH2Impl; import io.kazuki.v0.store.guice.impl.LifecycleModuleDefaultImpl; import io.kazuki.v0.store.jdbi.IdbiProvider; import io.kazuki.v0.store.jdbi.JdbiDataSourceConfiguration; import io.kazuki.v0.store.keyvalue.KeyValueIterable; import io.kazuki.v0.store.keyvalue.KeyValuePair; import io.kazuki.v0.store.keyvalue.KeyValueStore; import io.kazuki.v0.store.keyvalue.KeyValueStoreConfiguration; import io.kazuki.v0.store.keyvalue.KeyValueStoreIteration.SortDirection; import io.kazuki.v0.store.lifecycle.Lifecycle; import io.kazuki.v0.store.management.ComponentRegistrar; import io.kazuki.v0.store.management.KazukiManager; import io.kazuki.v0.store.management.impl.KazukiManagerImpl; import io.kazuki.v0.store.sequence.KeyImpl; import io.kazuki.v0.store.sequence.SequenceService; import io.kazuki.v0.store.sequence.SequenceServiceConfiguration; import java.io.PrintWriter; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.skife.jdbi.v2.IDBI; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.Scopes; import com.google.inject.name.Names; /** * Entry point for Kazuki command-line utils */ public class Main { public static final String STORE_NAME = "db"; public static void main(String[] args) { CliBuilder<Runnable> builder = Cli.<Runnable>builder("kazuki") .withDescription("the Kazuki command-line interface") .withDefaultCommand(Help.class) .withCommands(Help.class, ListStores.class, RawExport.class, DropTable.class, KeyValueExport.class, KeyValueDelete.class, KeyValueDeleteHard.class); Cli<Runnable> gitParser = builder.build(); Runnable cmd = gitParser.parse(args); if (cmd instanceof KazukiCommand) { Injector inject = Guice.createInjector(((KazukiCommand) cmd).getModules()); inject.injectMembers(cmd); ((KazukiCommand) cmd).init(); } cmd.run(); } public abstract static class KazukiCommand implements Runnable { @Option(type = OptionType.GLOBAL, name = "-v", description = "Verbose mode", required = false) public boolean verbose; @Option(type = OptionType.GLOBAL, name = "-u", description = "JDBC user", required = true) public String user; @Option(type = OptionType.GLOBAL, name = "-p", description = "JDBC password", required = true) public String password; @Option(type = OptionType.GLOBAL, name = "-f", description = "H2 database file", required = true) public String h2file; protected IDBI database; public KazukiCommand() {} public void init() { Injector inject = Guice.createInjector(getModules()); inject.getBinding(Key.get(DataSource.class, Names.named(STORE_NAME))).getProvider().get(); this.database = inject.getBinding(IDBI.class).getProvider().get(); Lifecycle life = inject.getBinding(Key.get(Lifecycle.class, Names.named(STORE_NAME))).getProvider().get(); life.init(); life.start(); } public List<Module> getModules() { return ImmutableList.<Module>of( new LifecycleModuleDefaultImpl(STORE_NAME, Key.get(ComponentRegistrar.class, Names.named(STORE_NAME))), new DataSourceModuleH2Impl(STORE_NAME, Key.get(ComponentRegistrar.class, Names.named(STORE_NAME)), Key.get(Lifecycle.class, Names.named(STORE_NAME)), Key.get( JdbiDataSourceConfiguration.class, Names.named(STORE_NAME))), new AbstractModule() { @Override protected void configure() { KazukiManager kzManager = new KazukiManagerImpl(); bind(Key.get(KazukiManager.class, Names.named(STORE_NAME))).toInstance(kzManager); bind(Key.get(ComponentRegistrar.class, Names.named(STORE_NAME))).toInstance( (ComponentRegistrar) kzManager); bind(Key.get(JdbiDataSourceConfiguration.class, Names.named(STORE_NAME))).toInstance( getJdbiConfig()); Provider<DataSource> provider = binder().getProvider(Key.get(DataSource.class, Names.named(STORE_NAME))); bind(IDBI.class).toProvider(new IdbiProvider(SequenceService.class, provider)).in( Scopes.SINGLETON); } }); } public JdbiDataSourceConfiguration getJdbiConfig() { return new JdbiDataSourceConfiguration.Builder().withJdbcDriver("org.h2.Driver") .withJdbcUrl("jdbc:h2:file:" + h2file).withJdbcUser(user).withJdbcPassword(password) .withPoolMaxConnections(10).withPoolMinConnections(10).build(); } } @Command(name = "list", description = "list Kazuki stores") public static class ListStores extends KazukiCommand { @Override public void run() { try (PrintWriter output = new PrintWriter(System.out)) { SqlCommandHelper.outputResults(database, "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '\\_%'", new RowHandler() { @Override public void handleRow(Map<String, Object> row) { try { String table = (String) row.get("table_name"); String[] parts = table.split("__"); String[] names = parts[0].split("_"); String group = names[1]; String store = names[2]; String type = parts[1]; ImmutableMap.Builder<String, String> result = ImmutableMap.<String, String>builder(); result.put("group", group); result.put("store", store); if (parts.length > 2) { result.put("partition", parts[2]); } result.put("$type", type); result.put("$table_name", table); output.println(EncodingHelper.convertToJson(result.build())); } catch (Exception e) { throw Throwables.propagate(e); } } }); } catch (Exception e) { throw Throwables.propagate(e); } } } @Command(name = "raw_export", description = "export raw Kazuki tables as JSON") public static class RawExport extends KazukiCommand { @Arguments(description = "names of tables to be exported") public List<String> tableNames; @Override public void run() { try (PrintWriter output = new PrintWriter(System.out)) { for (String tableName : tableNames) { SqlCommandHelper.outputResults(this.database, "SELECT * FROM \"" + tableName + "\"", new RowHandler() { @Override public void handleRow(Map<String, Object> row) { try { if (row.containsKey("_value")) { Object val = row.remove("_value"); Object newVal = EncodingHelper.parseSmile((byte[]) val, Object.class); row.put("_value", newVal); } output.println(EncodingHelper.convertToJson(row)); } catch (Exception e) { throw Throwables.propagate(e); } } }); } } catch (Exception e) { throw Throwables.propagate(e); } } } @Command(name = "drop_tables", description = "Drops specified tables") public static class DropTable extends KazukiCommand { @Arguments(description = "names of tables to be dropped") public List<String> tableNames; @Override public void run() { try (PrintWriter output = new PrintWriter(System.out)) { for (final String tableName : tableNames) { SqlCommandHelper.executeStatement(database, "drop table \"" + tableName + "\"", new ResultHandler() { @Override public void handleResult(int result) { try { output.println(EncodingHelper.convertToJson(ImmutableMap.of("drop", tableName, "result", result))); } catch (Exception e) { throw Throwables.propagate(e); } } }); } } catch (Exception e) { throw Throwables.propagate(e); } } } public abstract static class KeyValueStoreCommand extends KazukiCommand { @Option(type = OptionType.GLOBAL, name = "-g", description = "Kazuki store Group name", required = true) public String group; @Option(type = OptionType.GLOBAL, name = "-s", description = "Kazuki store Store name", required = true) public String store; @Option(type = OptionType.GLOBAL, name = "-t", description = "Kazuki store Partition name", required = false) public String partition = "default"; protected SequenceService sequence; protected KeyValueStore keyValueStore; public void init() { Injector inject = Guice.createInjector(getModules()); inject.getBinding(Key.get(DataSource.class, Names.named(STORE_NAME))).getProvider().get(); this.sequence = inject.getBinding(Key.get(SequenceService.class, Names.named(STORE_NAME))).getProvider() .get(); this.keyValueStore = inject.getBinding(Key.get(KeyValueStore.class, Names.named(STORE_NAME))).getProvider() .get(); Lifecycle life = inject.getBinding(Key.get(Lifecycle.class, Names.named(STORE_NAME))).getProvider().get(); life.init(); life.start(); } @Override public List<Module> getModules() { return ImmutableList.<Module>of(new KazukiModule.Builder(STORE_NAME) .withJdbiConfiguration(STORE_NAME, getJdbiConfig()) .withSequenceServiceConfiguration(STORE_NAME, getSequenceConfig()) .withKeyValueStoreConfiguration(STORE_NAME, getKeyValueConfig()).build()); } public KeyValueStoreConfiguration getKeyValueConfig() { return new KeyValueStoreConfiguration.Builder().withDbType("h2").withGroupName(group) .withStoreName(store).withPartitionName(partition).withStrictTypeCreation(false).build(); } public SequenceServiceConfiguration getSequenceConfig() { return new SequenceServiceConfiguration.Builder().withDbType("h2").withGroupName(group) .withStoreName(store).withIncrementBlockSize(100_000L).withStrictTypeCreation(false) .build(); } } @Command(name = "kv_export", description = "export key value entries") public static class KeyValueExport extends KeyValueStoreCommand { @Override public void run() { try (PrintWriter output = new PrintWriter(System.out)) { int i = 1; String type = "$schema"; for (;;) { try (KeyValueIterable<KeyValuePair<LinkedHashMap>> iter = keyValueStore.iterators().entries(type, LinkedHashMap.class, SortDirection.ASCENDING)) { for (KeyValuePair<LinkedHashMap> entry : iter) { output.println(EncodingHelper.convertToJson(ImmutableMap.of("type", type, "key", entry.getKey().getIdentifier(), "value", entry.getValue()))); output.flush(); } } i += 1; try { type = sequence.getTypeName(i); } catch (Exception e) { break; } } } catch (Exception e) { throw Throwables.propagate(e); } } } @Command(name = "kv_delete", description = "delete (soft) one or more KV entries by identifier") public static class KeyValueDelete extends KeyValueStoreCommand { @Arguments(description = "keys to delete") public List<String> toDelete; @Override public void run() { try (PrintWriter output = new PrintWriter(System.out)) { for (String key : toDelete) { boolean result = keyValueStore.delete(KeyImpl.valueOf(key)); output .println(EncodingHelper.convertToJson(ImmutableMap.of("key", key, "result", result))); } } catch (Exception e) { throw Throwables.propagate(e); } } } @Command(name = "kv_delete_hard", description = "delete (hard) one or more KV entries by identifier") public static class KeyValueDeleteHard extends KeyValueStoreCommand { @Arguments(description = "keys to delete") public List<String> toDelete; @Override public void run() { try (PrintWriter output = new PrintWriter(System.out)) { for (String key : toDelete) { boolean result = keyValueStore.deleteHard(KeyImpl.valueOf(key)); output .println(EncodingHelper.convertToJson(ImmutableMap.of("key", key, "result", result))); } } catch (Exception e) { throw Throwables.propagate(e); } } } }