001 package org.kuali.common.util.service; 002 003 import java.io.File; 004 import java.io.IOException; 005 import java.io.PrintStream; 006 import java.util.ArrayList; 007 import java.util.List; 008 009 import org.apache.commons.io.IOUtils; 010 import org.apache.commons.lang3.StringUtils; 011 import org.kuali.common.util.Assert; 012 import org.kuali.common.util.CollectionUtils; 013 import org.kuali.common.util.FormatUtils; 014 import org.kuali.common.util.LocationUtils; 015 import org.kuali.common.util.PrintlnStreamConsumer; 016 import org.slf4j.Logger; 017 import org.slf4j.LoggerFactory; 018 019 public class DefaultMySqlDumpService extends DefaultExecService implements MySqlDumpService { 020 021 private static final Logger logger = LoggerFactory.getLogger(DefaultMySqlDumpService.class); 022 023 @Override 024 public void dump(String username, String password, String hostname, String database, File outputFile) { 025 MySqlDumpContext context = new MySqlDumpContext(); 026 context.setExecutable(DEFAULT_EXECUTABLE); 027 context.setUsername(username); 028 context.setPassword(password); 029 context.setHostname(hostname); 030 context.setDatabase(database); 031 context.setOutputFile(outputFile); 032 dump(context); 033 } 034 035 @Override 036 public void dump(List<String> options, String database, File outputFile) { 037 MySqlDumpContext context = new MySqlDumpContext(); 038 context.setExecutable(DEFAULT_EXECUTABLE); 039 context.setOptions(options); 040 context.setDatabase(database); 041 context.setOutputFile(outputFile); 042 dump(context); 043 } 044 045 @Override 046 public void dump(MySqlDumpContext context) { 047 Assert.notNull(context.getDatabase(), "database is null"); 048 Assert.notNull(context.getOutputFile(), "output file is null"); 049 Assert.notNull(context.getExecutable(), "executable is null"); 050 fillInOptions(context); 051 DefaultExecContext dec = getExecContext(context); 052 beforeDump(context); 053 dump(dec, context); 054 } 055 056 protected void beforeDump(MySqlDumpContext context) { 057 String username = StringUtils.trimToEmpty(context.getUsername()); 058 String hostname = StringUtils.trimToEmpty(context.getHostname()); 059 int port = context.getPort(); 060 String database = context.getDatabase(); 061 String path = LocationUtils.getCanonicalPath(context.getOutputFile()); 062 Object[] args = { username, hostname, port, database, path }; 063 logger.info("Dumping [{}@{}:{}/{}] -> [{}]", args); 064 } 065 066 protected void dump(DefaultExecContext context, MySqlDumpContext msdc) { 067 PrintStream out = null; 068 try { 069 out = LocationUtils.openPrintStream(msdc.getOutputFile()); 070 PrintlnStreamConsumer standardOutConsumer = new PrintlnStreamConsumer(out, msdc.getIgnorers()); 071 context.setStandardOutConsumer(standardOutConsumer); 072 long start = System.currentTimeMillis(); 073 int result = execute(context); 074 long elapsed = System.currentTimeMillis() - start; 075 if (result != 0) { 076 throw new IllegalStateException("Non-zero exit value - " + result); 077 } 078 afterDump(msdc, elapsed, standardOutConsumer.getLineCount(), standardOutConsumer.getSkipCount()); 079 } catch (IOException e) { 080 throw new IllegalStateException("Unexpected IO error", e); 081 } finally { 082 IOUtils.closeQuietly(out); 083 } 084 } 085 086 protected void afterDump(MySqlDumpContext context, long elapsed, long lineCount, long skippedCount) { 087 long length = context.getOutputFile().length(); 088 String time = FormatUtils.getTime(elapsed); 089 String size = FormatUtils.getSize(length); 090 String rate = FormatUtils.getRate(elapsed, length); 091 String lines = FormatUtils.getCount(lineCount); 092 String skipped = FormatUtils.getCount(skippedCount); 093 Object[] args = { time, size, rate, lines, skipped }; 094 logger.info("Dump completed. [Time:{}, Size:{}, Rate:{}, Lines:{} Skipped:{}]", args); 095 } 096 097 protected DefaultExecContext getExecContext(MySqlDumpContext context) { 098 List<String> args = getArgs(context); 099 DefaultExecContext dec = new DefaultExecContext(); 100 dec.setExecutable(context.getExecutable()); 101 dec.setArgs(args); 102 return dec; 103 } 104 105 /** 106 * <code>mysqldump</code> invocation looks like this: 107 * 108 * <pre> 109 * mysqldump [OPTIONS] database [tables] 110 * </pre> 111 */ 112 protected List<String> getArgs(MySqlDumpContext context) { 113 List<String> args = new ArrayList<String>(); 114 args.addAll(CollectionUtils.toEmptyList(context.getOptions())); 115 args.add(context.getDatabase()); 116 args.addAll(CollectionUtils.toEmptyList(context.getTables())); 117 return args; 118 } 119 120 /** 121 * Create (or update) the list of options for this context 122 */ 123 protected void fillInOptions(MySqlDumpContext context) { 124 // Get a handle to the existing options list, or create new one 125 List<String> options = context.getOptions() == null ? new ArrayList<String>() : context.getOptions(); 126 // Insert the options we are explicitly managing at the front of the list 127 options.add(0, "--port=" + context.getPort()); 128 if (!StringUtils.isBlank(context.getHostname())) { 129 options.add(0, "--host=" + context.getHostname()); 130 } 131 if (!StringUtils.isBlank(context.getPassword())) { 132 options.add(0, "--password=" + context.getPassword()); 133 } 134 if (!StringUtils.isBlank(context.getUsername())) { 135 options.add(0, "--user=" + context.getUsername()); 136 } 137 // Just in case there were no options to begin with 138 context.setOptions(options); 139 } 140 }