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