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 }