001/**
002 * Copyright 2010-2014 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 */
016package org.kuali.common.util.service;
017
018import java.io.File;
019import java.io.IOException;
020import java.io.PrintStream;
021import java.util.ArrayList;
022import java.util.List;
023
024import org.apache.commons.io.IOUtils;
025import org.apache.commons.lang3.StringUtils;
026import org.kuali.common.util.Assert;
027import org.kuali.common.util.CollectionUtils;
028import org.kuali.common.util.FormatUtils;
029import org.kuali.common.util.LocationUtils;
030import org.kuali.common.util.PrintlnStreamConsumer;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034public 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}