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.getSkipLinePrefix(), msdc.getSkipLineSuffix());
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 }