diff --git a/symmetric-client/src/main/java/org/jumpmind/symmetric/AbstractCommandLauncher.java b/symmetric-client/src/main/java/org/jumpmind/symmetric/AbstractCommandLauncher.java index 56f5377258..27971b0ef2 100644 --- a/symmetric-client/src/main/java/org/jumpmind/symmetric/AbstractCommandLauncher.java +++ b/symmetric-client/src/main/java/org/jumpmind/symmetric/AbstractCommandLauncher.java @@ -108,10 +108,7 @@ public abstract class AbstractCommandLauncher { private static boolean serverPropertiesInitialized = false; static { - String symHome = System.getenv("SYM_HOME"); - if (symHome == null) { - symHome = "."; - } + String symHome = AppUtils.getSymHome(); if (isBlank(System.getProperty("h2.baseDir.disable")) && isBlank(System.getProperty("h2.baseDir"))) { System.setProperty("h2.baseDir", symHome + "/db/h2"); } diff --git a/symmetric-client/src/main/java/org/jumpmind/symmetric/SymmetricAdmin.java b/symmetric-client/src/main/java/org/jumpmind/symmetric/SymmetricAdmin.java index 9e359c24bc..682ba1030e 100644 --- a/symmetric-client/src/main/java/org/jumpmind/symmetric/SymmetricAdmin.java +++ b/symmetric-client/src/main/java/org/jumpmind/symmetric/SymmetricAdmin.java @@ -23,6 +23,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; @@ -30,13 +31,17 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Scanner; import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; @@ -48,6 +53,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jumpmind.db.model.Table; +import org.jumpmind.exception.IoException; import org.jumpmind.properties.TypedProperties; import org.jumpmind.security.ISecurityService; import org.jumpmind.security.SecurityConstants; @@ -66,6 +72,7 @@ import org.jumpmind.symmetric.util.ModuleManager; import org.jumpmind.util.AppUtils; import org.jumpmind.util.JarBuilder; +import org.jumpmind.util.ZipBuilder; /** * Perform administration tasks with SymmetricDS. @@ -118,8 +125,12 @@ public class SymmetricAdmin extends AbstractCommandLauncher { private static final String CMD_SEND_SCRIPT = "send-script"; private static final String CMD_SEND_SCHEMA = "send-schema"; + + private static final String CMD_BACKUP_FILE_CONFIGURATION = "backup-config"; + + private static final String CMD_RESTORE_FILE_CONFIGURATION = "restore-config"; - private static final String[] NO_ENGINE_REQUIRED = { CMD_EXPORT_PROPERTIES, CMD_ENCRYPT_TEXT, CMD_OBFUSCATE_TEXT, CMD_LIST_ENGINES, CMD_MODULE }; + private static final String[] NO_ENGINE_REQUIRED = { CMD_EXPORT_PROPERTIES, CMD_ENCRYPT_TEXT, CMD_OBFUSCATE_TEXT, CMD_LIST_ENGINES, CMD_MODULE, CMD_BACKUP_FILE_CONFIGURATION, CMD_RESTORE_FILE_CONFIGURATION }; private static final String OPTION_NODE = "node"; @@ -136,7 +147,9 @@ public class SymmetricAdmin extends AbstractCommandLauncher { private static final String OPTION_NODE_GROUP = "node-group"; private static final String OPTION_REVERSE = "reverse"; - + + private static final String OPTION_IN = "in"; + private static final int WIDTH = 120; private static final int PAD = 3; @@ -201,6 +214,8 @@ protected void printHelp(CommandLine line, Options options) { printHelpLine(pw, CMD_SEND_SQL); printHelpLine(pw, CMD_SEND_SCHEMA); printHelpLine(pw, CMD_SEND_SCRIPT); + printHelpLine(pw, CMD_BACKUP_FILE_CONFIGURATION); + printHelpLine(pw, CMD_RESTORE_FILE_CONFIGURATION); printHelpLine(pw, CMD_UNINSTALL); printHelpLine(pw, CMD_MODULE); pw.flush(); @@ -243,7 +258,7 @@ private void printHelpCommand(CommandLine line) { addOption(options, "w", OPTION_WHERE, true); } if (cmd.equals(CMD_SYNC_TRIGGERS)) { - addOption(options, "o", OPTION_OUT, false); + addOption(options, "o", OPTION_OUT, true); addOption(options, "f", OPTION_FORCE, false); } if (cmd.equals(CMD_RELOAD_NODE)) { @@ -252,6 +267,12 @@ private void printHelpCommand(CommandLine line) { if (cmd.equals(CMD_REMOVE_NODE)) { addOption(options, "n", OPTION_NODE, true); } + if(cmd.equals(CMD_BACKUP_FILE_CONFIGURATION)) { + addOption(options, "o", OPTION_OUT, true); + } + if(cmd.equals(CMD_RESTORE_FILE_CONFIGURATION)) { + addOption(options, "i", OPTION_IN, true); + } if (options.getOptions().size() > 0) { format.printWrapped(writer, WIDTH, "\nOptions:"); @@ -284,6 +305,7 @@ protected void buildOptions(Options options) { addOption(options, "f", OPTION_FORCE, false); addOption(options, "o", OPTION_OUT, true); addOption(options, "r", OPTION_REVERSE, false); + addOption(options, "i", OPTION_IN, true); buildCryptoOptions(options); } @@ -359,6 +381,12 @@ protected boolean executeWithOptions(CommandLine line) throws Exception { } else if (cmd.equals(CMD_MODULE)) { module(line, args); return true; + } else if (cmd.equals(CMD_BACKUP_FILE_CONFIGURATION)) { + backup(line, args); + return true; + } else if (cmd.equals(CMD_RESTORE_FILE_CONFIGURATION)) { + restore(line, args); + return true; } else { throw new ParseException("ERROR: no subcommand '" + cmd + "' was found."); } @@ -495,6 +523,79 @@ private void reloadNode(CommandLine line, List args) { String message = dataService.reloadNode(nodeId, reverse, "symadmin"); System.out.println(message); } + + private void backup(CommandLine line, List args) throws IOException { + String filename = line.getOptionValue(OPTION_OUT); + if(filename == null) { + filename = "symmetric-file-configuration-" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip"; + } + File jarFile = null; + if(filename != null) { + jarFile = new File(filename); + if (jarFile.getParentFile() != null) { + jarFile.getParentFile().mkdirs(); + } + } + List listOfDirs = new ArrayList(); + listOfDirs.add(AbstractCommandLauncher.getEnginesDir()); + listOfDirs.add(AppUtils.getSymHome() + "/conf"); + listOfDirs.add(AppUtils.getSymHome() + "/patches"); + listOfDirs.add(AppUtils.getSymHome() + "/security"); + + String parentDir = new File(DEFAULT_SERVER_PROPERTIES).getParent(); + if(parentDir != null) { + if(listOfDirs.indexOf(parentDir) < 0) { + // Need to add DEFAULT_SERVER_PROPERTIES to list of files to back up + // because the file is specified outside of the SymmetricDS installation + listOfDirs.add(DEFAULT_SERVER_PROPERTIES); + } + } + + File[] arrayOfFile = new File[listOfDirs.size()]; + for(int i = 0; i < listOfDirs.size(); i++) { + arrayOfFile[i] = new File(listOfDirs.get(i)); + } + + System.out.println("Backing up files to " + filename); + try { + ZipBuilder builder = new ZipBuilder(new File(AppUtils.getSymHome()), jarFile, arrayOfFile); + builder.build(); + } catch (Exception e) { + throw new IoException("Failed to backup configuration files into archive", e); + } + } + + private void restore(CommandLine line, List args) throws IOException { + String filename = line.getOptionValue(OPTION_IN); + if(filename == null) { + throw new IoException("Input filename must be specified"); + } + try(FileInputStream finput = new FileInputStream(filename);ZipInputStream zip = new ZipInputStream(finput)) { + ZipEntry entry = null; + for (entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { + if(entry.isDirectory()) { + continue; + } + System.out.println("Restoring " + entry.getName()); + + File fileToOpen = null; + File f = new File(entry.getName()); + if(f.isAbsolute()) { + f.getParentFile().mkdirs(); + fileToOpen = f; + } else { + fileToOpen = new File(AppUtils.getSymHome(), entry.getName()); + } + try(FileOutputStream foutput = new FileOutputStream(fileToOpen)) { + final byte buffer[] = new byte[4096]; + int readCount; + while ((readCount = zip.read(buffer, 0, buffer.length)) > 0) { + foutput.write(buffer, 0, readCount); + } + } + } + } + } private void syncTrigger(CommandLine line, List args) throws IOException { boolean genAlways = line.hasOption(OPTION_FORCE); diff --git a/symmetric-client/src/main/resources/symmetric-messages.properties b/symmetric-client/src/main/resources/symmetric-messages.properties index 0fcd09f588..bf1d4206c5 100644 --- a/symmetric-client/src/main/resources/symmetric-messages.properties +++ b/symmetric-client/src/main/resources/symmetric-messages.properties @@ -74,6 +74,8 @@ SymAdmin.Cmd.send-schema=Send schema change to node SymAdmin.Cmd.send-script=Send script to node SymAdmin.Cmd.uninstall=Uninstall all SymmetricDS objects from the database SymAdmin.Cmd.module=Manage modules to add or remove features +SymAdmin.Cmd.backup-config=Backup configuration files +SymAdmin.Cmd.restore-config=Restore configuration files SymAdmin.Usage.reload-node= SymAdmin.Usage.reload-table= [
...] SymAdmin.Usage.export-batch= [] @@ -95,6 +97,8 @@ SymAdmin.Usage.send-sql=
SymAdmin.Usage.send-schema=[
] ... SymAdmin.Usage.send-script= SymAdmin.Usage.uninstall= +SymAdmin.Usage.backup-config= +SymAdmin.Usage.restore-config= SymAdmin.Usage.module=[install | remove | list-files | list | list-all] SymAdmin.Help.export-sym-tables=Output the SQL to create the SymmetricDS tables. If a filename is given, the SQL statements are written to it, otherwise standard output is used. SymAdmin.Help.run-job=Run one of the scheduled jobs immediately. @@ -118,6 +122,8 @@ SymAdmin.Help.send-schema=Send a schema update for a table to be executed on a r SymAdmin.Help.send-script=Send a script to a node to be run there. The script is read from the filename provided as an argument or read from standard input. Only BeanShell scripts are supported. SymAdmin.Help.uninstall=Uninstall all SymmetricDS objects from the database, including the SYM tables, sequences, functions, stored procedures, and triggers. SymAdmin.Help.module=\nManage modules to add or remove features.\n\nmodule list List modules that are currently installed\nmodule list-all List all modules available to install\nmodule list-files List files for a module that is installed\nmodule list-deps List dependencies for a module\nmodule install Install a module\nmodule remove Remove a module +SymAdmin.Help.backup-config=Backup configuration files to a zip file for later restoration if necessary. +SymAdmin.Help.restore-config=Restore configuration files from a zip file. SymAdmin.Option.catalog=Look for tables in catalog. SymAdmin.Option.schema=Look for tables in schema. SymAdmin.Option.where=Add where clause to SQL statement that selects data from table. @@ -125,6 +131,7 @@ SymAdmin.Option.node=Send to this node ID. SymAdmin.Option.node-group=Send to all nodes in this node group ID. SymAdmin.Option.force=Force triggers to regenerate even if no change is detected. SymAdmin.Option.out=Write output to file +SymAdmin.Option.in=Read from file SymAdmin.Option.reverse=Reverse initial load from client to server DbExport.Option.compatible=Change export to be compatible with given database: db2, db2zos, derby, firebird, greenplum, h2, hsqldb, hsqldb2, informix, interbase, mssql, mysql, oracle, postgres, sybase.