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}