/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * 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.jumpmind.symmetric; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.h2.util.StringUtils; import org.jumpmind.db.model.Table; import org.jumpmind.security.ISecurityService; import org.jumpmind.security.SecurityConstants; import org.jumpmind.symmetric.model.Node; import org.jumpmind.symmetric.service.IDataExtractorService; import org.jumpmind.symmetric.service.IDataLoaderService; import org.jumpmind.symmetric.service.IDataService; import org.jumpmind.symmetric.service.IPurgeService; import org.jumpmind.symmetric.service.IRegistrationService; import org.jumpmind.symmetric.service.ITriggerRouterService; import org.jumpmind.util.AppUtils; import org.jumpmind.util.JarBuilder; /** * Perform administration tasks with SymmetricDS. */ public class SymmetricAdmin extends AbstractCommandLauncher { @SuppressWarnings("unused") private static final Log log = LogFactory.getLog(SymmetricAdmin.class); private static final String CMD_RELOAD_NODE = "reload-node"; private static final String CMD_RELOAD_TABLE = "reload-table"; private static final String CMD_EXPORT_BATCH = "export-batch"; private static final String CMD_IMPORT_BATCH = "import-batch"; private static final String CMD_RUN_PURGE = "run-purge"; private static final String CMD_ENCRYPT_TEXT = "encrypt-text"; private static final String CMD_CREATE_WAR = "create-war"; private static final String CMD_CREATE_SYM_TABLES = "create-sym-tables"; private static final String CMD_EXPORT_SYM_TABLES = "export-sym-tables"; private static final String CMD_OPEN_REGISTRATION = "open-registration"; private static final String CMD_SYNC_TRIGGERS = "sync-triggers"; private static final String CMD_DROP_TRIGGERS = "drop-triggers"; private static final String CMD_EXPORT_PROPERTIES = "export-properties"; private static final String CMD_UNINSTALL = "uninstall"; private static final String CMD_SEND_SQL = "send-sql"; private static final String CMD_SEND_SCRIPT = "send-script"; private static final String CMD_SEND_SCHEMA = "send-schema"; private static final String[] NO_ENGINE_REQUIRED = { CMD_EXPORT_PROPERTIES }; private static final String OPTION_NODE = "node"; private static final String OPTION_CATALOG = "catalog"; private static final String OPTION_SCHEMA = "schema"; private static final String OPTION_WHERE = "where"; private static final String OPTION_FORCE = "force"; private static final String OPTION_OUT = "out"; private static final String OPTION_NODE_GROUP = "node-group"; private static final String OPTION_REVERSE = "reverse"; private static final int WIDTH = 80; private static final int PAD = 3; public SymmetricAdmin(String app, String argSyntax, String messageKeyPrefix) { super(app, argSyntax, messageKeyPrefix); } public static void main(String[] args) throws Exception { new SymmetricAdmin("symadmin", "<subcommand> [options] [args]", "SymAdmin.Option.") .execute(args); } @Override protected boolean printHelpIfNoOptionsAreProvided() { return true; } @Override protected boolean requiresPropertiesFile() { return true; } @Override protected void printHelp(CommandLine line, Options options) { String[] args = line.getArgs(); if (args.length > 1 && args[0].equals(HELP)) { printHelpCommand(line); } else { System.out.println(app + " version " + Version.version()); System.out.println("Perform administration tasks with SymmetricDS.\n"); System.out .println("Usage: symadmin <subcommand> --engine [engine.name] [options] [args]"); System.out .println(" symadmin <subcommand> --properties [properties file] [options] [args]"); System.out .println("\nType 'symadmin help <subcommand>' for help on a specific subcommand.\n"); System.out.println("Available subcommands:"); PrintWriter pw = new PrintWriter(System.out); printHelpLine(pw, CMD_OPEN_REGISTRATION); printHelpLine(pw, CMD_RELOAD_NODE); printHelpLine(pw, CMD_RELOAD_TABLE); printHelpLine(pw, CMD_EXPORT_BATCH); printHelpLine(pw, CMD_IMPORT_BATCH); printHelpLine(pw, CMD_RUN_PURGE); printHelpLine(pw, CMD_ENCRYPT_TEXT); printHelpLine(pw, CMD_CREATE_WAR); printHelpLine(pw, CMD_CREATE_SYM_TABLES); printHelpLine(pw, CMD_EXPORT_SYM_TABLES); printHelpLine(pw, CMD_SYNC_TRIGGERS); printHelpLine(pw, CMD_DROP_TRIGGERS); printHelpLine(pw, CMD_EXPORT_PROPERTIES); printHelpLine(pw, CMD_SEND_SQL); printHelpLine(pw, CMD_SEND_SCHEMA); printHelpLine(pw, CMD_SEND_SCRIPT); printHelpLine(pw, CMD_UNINSTALL); pw.flush(); } } private void printHelpLine(PrintWriter pw, String cmd) { String text = StringUtils.pad(" " + cmd, 23, " ", true) + Message.get("SymAdmin.Cmd." + cmd); new HelpFormatter().printWrapped(pw, 79, 25, text); } private void printHelpCommand(CommandLine line) { String[] args = line.getArgs(); if (args.length > 1) { String cmd = args[1]; HelpFormatter format = new HelpFormatter(); PrintWriter writer = new PrintWriter(System.out); Options options = new Options(); if (! Message.containsKey("SymAdmin.Usage." + cmd)) { System.err.println("ERROR: no help text for subcommand '" + cmd + "' was found."); System.err.println("For a list of subcommands, use " + app + " --" + HELP + "\n"); return; } format.printWrapped(writer, WIDTH, "Usage: " + app + " " + cmd + " " + Message.get("SymAdmin.Usage." + cmd) + "\n"); format.printWrapped(writer, WIDTH, Message.get("SymAdmin.Help." + cmd)); if (cmd.equals(CMD_SEND_SQL) || cmd.equals(CMD_SEND_SCHEMA) || cmd.equals(CMD_RELOAD_TABLE) || cmd.equals(CMD_SEND_SCRIPT)) { addOption(options, "n", OPTION_NODE, true); addOption(options, "g", OPTION_NODE_GROUP, true); } if (cmd.equals(CMD_RELOAD_TABLE)) { addOption(options, "c", OPTION_CATALOG, true); addOption(options, "s", OPTION_SCHEMA, true); addOption(options, "w", OPTION_WHERE, true); } if (cmd.equals(CMD_SYNC_TRIGGERS)) { addOption(options, "o", OPTION_OUT, false); addOption(options, "f", OPTION_FORCE, false); } if (cmd.equals(CMD_RELOAD_NODE)) { addOption(options, "r", OPTION_REVERSE, false); } if (options.getOptions().size() > 0) { format.printWrapped(writer, WIDTH, "\nOptions:"); format.printOptions(writer, WIDTH, options, PAD, PAD); } if (!ArrayUtils.contains(NO_ENGINE_REQUIRED, cmd)) { format.printWrapped(writer, WIDTH, "\nEngine options:"); options = new Options(); super.buildOptions(options); format.printOptions(writer, WIDTH, options, PAD, PAD); format.printWrapped(writer, WIDTH, "\nCrypto options:"); options = new Options(); buildCryptoOptions(options); format.printOptions(writer, WIDTH, options, PAD, PAD); } writer.flush(); } } @Override protected void buildOptions(Options options) { super.buildOptions(options); addOption(options, "n", OPTION_NODE, true); addOption(options, "g", OPTION_NODE_GROUP, true); addOption(options, "c", OPTION_CATALOG, true); addOption(options, "s", OPTION_SCHEMA, true); addOption(options, "w", OPTION_WHERE, true); addOption(options, "f", OPTION_FORCE, false); addOption(options, "o", OPTION_OUT, true); addOption(options, "r", OPTION_REVERSE, false); buildCryptoOptions(options); } @SuppressWarnings("unchecked") @Override protected boolean executeWithOptions(CommandLine line) throws Exception { List<String> args = line.getArgList(); String cmd = args.remove(0); configureCrypto(line); if (cmd.equals(CMD_EXPORT_PROPERTIES)) { exportDefaultProperties(line, args); return true; } else if (cmd.equals(CMD_CREATE_WAR)) { generateWar(line, args); return true; } else if (cmd.equals(CMD_EXPORT_SYM_TABLES)) { exportSymTables(line, args); return true; } else if (cmd.equals(CMD_RUN_PURGE)) { runPurge(line, args); return true; } else if (cmd.equals(CMD_OPEN_REGISTRATION)) { openRegistration(line, args); return true; } else if (cmd.equals(CMD_RELOAD_NODE)) { reloadNode(line, args); return true; } else if (cmd.equals(CMD_EXPORT_BATCH)) { exportBatch(line, args); return true; } else if (cmd.equals(CMD_SYNC_TRIGGERS)) { syncTrigger(line, args); return true; } else if (cmd.equals(CMD_DROP_TRIGGERS)) { dropTrigger(line, args); return true; } else if (cmd.equals(CMD_CREATE_SYM_TABLES)) { createSymTables(); return true; } else if (cmd.equals(CMD_IMPORT_BATCH)) { importBatch(line, args); return true; } else if (cmd.equals(CMD_ENCRYPT_TEXT)) { encryptText(line, args); return true; } else if (cmd.equals(CMD_SEND_SQL)) { sendSql(line, args); return true; } else if (cmd.equals(CMD_UNINSTALL)) { uninstall(line, args); return true; } else if (cmd.equals(CMD_RELOAD_TABLE)) { reloadTable(line, args); return true; } else if (cmd.equals(CMD_SEND_SCHEMA)) { sendSchema(line, args); return true; } else if (cmd.equals(CMD_SEND_SCRIPT)) { sendScript(line, args); return true; } else { System.err.println("ERROR: no subcommand '" + cmd + "' was found."); System.err.println("For a list of subcommands, use " + app + " --" + HELP + "\n"); } return false; } private String popArg(List<String> args, String argName) { if (args.size() == 0) { System.out.println("ERROR: Expected argument for: " + argName); System.exit(1); } return args.remove(0); } private void runPurge(CommandLine line, List<String> args) { IPurgeService purgeService = getSymmetricEngine().getPurgeService(); boolean all = args.contains("all") || args.size() == 0; if (args.contains("outgoing") || all) { purgeService.purgeOutgoing(true); } if (args.contains("incoming") || all) { purgeService.purgeIncoming(true); } } private void exportBatch(CommandLine line, List<String> args) throws Exception { IDataExtractorService dataExtractorService = getSymmetricEngine().getDataExtractorService(); String nodeId = popArg(args, "Node ID"); String batchId = popArg(args, "Batch ID"); OutputStreamWriter writer = getWriter(args); dataExtractorService.extractBatchRange(writer, nodeId, Long.valueOf(batchId), Long.valueOf(batchId)); writer.close(); } private void importBatch(CommandLine line, List<String> args) throws Exception { IDataLoaderService service = getSymmetricEngine().getDataLoaderService(); InputStream in = null; if (args.size() == 0) { in = System.in; } else { in = new FileInputStream(args.get(0)); } service.loadDataFromPush(getSymmetricEngine().getNodeService().findIdentity(), in, System.out); System.out.flush(); in.close(); } private void encryptText(CommandLine line, List<String> args) { String plainText = popArg(args, "Text"); ISecurityService service = getSymmetricEngine().getSecurityService(); System.out.println(SecurityConstants.PREFIX_ENC + service.encrypt(plainText)); } private void openRegistration(CommandLine line, List<String> args) { String nodeGroupId = popArg(args, "Node Group ID"); String externalId = popArg(args, "External ID"); IRegistrationService registrationService = getSymmetricEngine().getRegistrationService(); registrationService.openRegistration(nodeGroupId, externalId); System.out.println(String.format( "Opened registration for node group of '%s' external ID of '%s'", nodeGroupId, externalId)); } private void reloadNode(CommandLine line, List<String> args) { String nodeId = popArg(args, "Node ID"); boolean reverse = line.hasOption(OPTION_REVERSE); IDataService dataService = getSymmetricEngine().getDataService(); String message = dataService.reloadNode(nodeId, reverse, "symadmin"); System.out.println(message); } private void syncTrigger(CommandLine line, List<String> args) throws IOException { boolean genAlways = line.hasOption(OPTION_FORCE); String filename = line.getOptionValue(OPTION_OUT); String catalogName = line.getOptionValue(OPTION_CATALOG); String schemaName = line.getOptionValue(OPTION_SCHEMA); File file = null; if (filename != null) { file = new File(filename); if (file.getParentFile() != null) { file.getParentFile().mkdirs(); } } ITriggerRouterService triggerService = getSymmetricEngine().getTriggerRouterService(); StringBuilder sqlBuffer = new StringBuilder(); if (args.size() == 0) { triggerService.syncTriggers(sqlBuffer, genAlways); } else { for (String tablename : args) { Table table = platform.getTableFromCache(catalogName, schemaName, tablename, true); if (table != null) { triggerService.syncTriggers(table, genAlways); } else { System.out.println("Unable to find table " + tablename); } } } if (file != null) { FileUtils.writeStringToFile(file, sqlBuffer.toString()); } } private void dropTrigger(CommandLine line, List<String> args) throws IOException { ITriggerRouterService triggerService = getSymmetricEngine().getTriggerRouterService(); if (args.size() == 0) { System.out.println("Dropping all triggers..."); triggerService.dropTriggers(); } else { for (String tablename : args) { System.out.println("Dropping trigger for table " + tablename); Set<String> tables = new HashSet<String>(); tables.add(tablename); triggerService.dropTriggers(tables); } } } private void generateWar(CommandLine line, List<String> args) throws Exception { String warFileName = popArg(args, "Filename"); final File workingDirectory = new File(AppUtils.getSymHome() + "/.war"); FileUtils.deleteDirectory(workingDirectory); FileUtils.copyDirectory(new File(AppUtils.getSymHome() + "/web"), workingDirectory); FileUtils.copyDirectory(new File(AppUtils.getSymHome() + "/conf"), new File(workingDirectory, "WEB-INF/classes")); if (propertiesFile != null && propertiesFile.exists()) { FileUtils.copyFile(propertiesFile, new File(workingDirectory, "WEB-INF/classes/symmetric.properties")); } JarBuilder builder = new JarBuilder(workingDirectory, new File(warFileName), new File[] { workingDirectory }, Version.version()); builder.build(); FileUtils.deleteDirectory(workingDirectory); } private void exportSymTables(CommandLine line, List<String> args) throws IOException { OutputStreamWriter os = getWriter(args); os.write(getSymmetricEngine().getSymmetricDialect().getCreateSymmetricDDL()); os.close(); } private void exportDefaultProperties(CommandLine line, List<String> args) throws IOException { OutputStreamWriter os = getWriter(args); BufferedReader is = new BufferedReader(new InputStreamReader( SymmetricAdmin.class.getResourceAsStream("/symmetric-default.properties"), Charset.defaultCharset())); String str = is.readLine(); while (str != null) { os.write(str); os.write(System.getProperty("line.separator")); str = is.readLine(); } is.close(); os.close(); } private void createSymTables() { getSymmetricEngine().setupDatabase(true); } private void uninstall(CommandLine line, List<String> args) { getSymmetricEngine().uninstall(); } private void sendSql(CommandLine line, List<String> args) { String tableName = popArg(args, "Table Name"); String sql = popArg(args, "SQL"); String catalogName = line.getOptionValue(OPTION_CATALOG); String schemaName = line.getOptionValue(OPTION_SCHEMA); for (Node node : getNodes(line)) { System.out.println("Sending SQL to node '" + node.getNodeId() + "'"); getSymmetricEngine().getDataService().sendSQL(node.getNodeId(), catalogName, schemaName, tableName, sql); } } private void reloadTable(CommandLine line, List<String> args) { String catalogName = line.getOptionValue(OPTION_CATALOG); String schemaName = line.getOptionValue(OPTION_SCHEMA); if (args.size() == 0) { System.out.println("ERROR: Expected argument for: Table Name"); System.exit(1); } for (String tableName : args) { for (Node node : getNodes(line)) { System.out.println("Reloading table '" + tableName + "' to node '" + node.getNodeId() + "'"); if (line.hasOption(OPTION_WHERE)) { System.out.println(getSymmetricEngine().getDataService().reloadTable(node.getNodeId(), catalogName, schemaName, tableName, line.getOptionValue(OPTION_WHERE))); } else { System.out.println(getSymmetricEngine().getDataService().reloadTable(node.getNodeId(), catalogName, schemaName, tableName)); } } } } private void sendSchema(CommandLine line, List<String> args) { String tableName = popArg(args, "Table Name"); String catalog = line.getOptionValue(OPTION_CATALOG); String schema = line.getOptionValue(OPTION_SCHEMA); Collection<Node> nodes = getNodes(line); for (Node node : nodes) { getSymmetricEngine().getDataService().sendSchema(node.getNodeId(), catalog, schema, tableName, false); } } private void sendScript(CommandLine line, List<String> args) throws Exception { String scriptName = popArg(args, "Script Name"); String scriptData = FileUtils.readFileToString(new File(scriptName)); for (Node node : getNodes(line)) { System.out.println("Sending script to node '" + node.getNodeId() + "'"); getSymmetricEngine().getDataService().sendScript(node.getNodeId(), scriptData, false); } } private Collection<Node> getNodes(CommandLine line) { if (line.hasOption(OPTION_NODE_GROUP)) { return getSymmetricEngine().getNodeService().findEnabledNodesFromNodeGroup( line.getOptionValue(OPTION_NODE_GROUP)); } else if (line.hasOption(OPTION_NODE)) { Collection<Node> nodes = new ArrayList<Node>(); String nodeId = line.getOptionValue(OPTION_NODE); Node node = getSymmetricEngine().getNodeService().findNode(nodeId); if (node == null) { System.out.println("ERROR: Unable to find node '" + nodeId + "'"); System.exit(1); } nodes.add(node); return nodes; } else { System.out.println("ERROR: Must specify one option: " + OPTION_NODE + ", " + OPTION_NODE_GROUP); System.exit(1); } return null; } private OutputStreamWriter getWriter(List<String> args) throws IOException { OutputStreamWriter os = null; if (args.size() == 0) { os = new OutputStreamWriter(System.out); } else { File file = new File(args.get(0)); if (file.getParentFile() != null) { file.getParentFile().mkdirs(); } os = new FileWriter(file); } return os; } }