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    }