KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > continuent > sequoia > controller > backup > backupers > MySQLBackuper


1 /**
2  * Sequoia: Database clustering technology.
3  * Copyright (C) 2006 Continuent Inc.
4  * Contact: sequoia@continuent.org
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * Initial developer(s): Mykola Paliyenko.
19  * Contributor(s): Emmanuel Cecchet, Stephane Giron
20  */

21
22 package org.continuent.sequoia.controller.backup.backupers;
23
24 import java.io.BufferedReader JavaDoc;
25 import java.io.File JavaDoc;
26 import java.io.FileInputStream JavaDoc;
27 import java.io.FileOutputStream JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.io.InputStreamReader JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.Date JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.regex.Matcher JavaDoc;
34 import java.util.regex.Pattern JavaDoc;
35
36 import org.continuent.sequoia.common.exceptions.BackupException;
37 import org.continuent.sequoia.common.log.Trace;
38 import org.continuent.sequoia.controller.backend.DatabaseBackend;
39
40 /**
41  * MySQL backuper inspired from the PostgreSQL backuper.
42  * <p>
43  * Options for this backuper are:
44  *
45  * <pre>
46  * - bindir: path to mysqldump binary
47  * - dumpServer: address to bind the dump server
48  * </pre>
49  *
50  * @author <a HREF="mailto:mpaliyenko@gmail.com">Mykola Paliyenko</a>
51  * @author <a HREF="mailto:emmanuel.cecchet@continuent.com">Emmanuel Cecchet</a>
52  * @author <a HREF="mailto:stephane.giron@continuent.com">Stephane Giron</a>
53  */

54 public class MySQLBackuper extends AbstractBackuper
55 {
56   private static final String JavaDoc DEFAULT_MYSQL_PORT = "3306";
57
58   private static final String JavaDoc DEFAULT_MYSQL_HOST = "localhost";
59
60   static Trace logger = Trace
61                                                      .getLogger(MySQLBackuper.class
62                                                          .getName());
63   /** end user logger */
64   static Trace endUserLogger = Trace
65                                                      .getLogger("org.continuent.sequoia.enduser");
66
67   /** CommandExec instance for running native commands. * */
68   protected NativeCommandExec nativeCmdExec = new NativeCommandExec();
69
70   /**
71    * Creates a new <code>MySQLBackuper</code> object
72    */

73   public MySQLBackuper()
74   {
75   }
76
77   /**
78    * @see org.continuent.sequoia.controller.backup.Backuper#getDumpFormat()
79    */

80   public String JavaDoc getDumpFormat()
81   {
82     return "MySQL raw dump";
83   }
84
85   /**
86    * @see org.continuent.sequoia.controller.backup.Backuper#backup(org.continuent.sequoia.controller.backend.DatabaseBackend,
87    * java.lang.String, java.lang.String, java.lang.String,
88    * java.lang.String, java.util.ArrayList)
89    */

90   public Date JavaDoc backup(DatabaseBackend backend, String JavaDoc login, String JavaDoc password,
91       String JavaDoc dumpName, String JavaDoc path, ArrayList JavaDoc tables) throws BackupException
92   {
93     String JavaDoc url = backend.getURL();
94     if (!url.startsWith("jdbc:mysql:"))
95     {
96       throw new BackupException("Unsupported db url " + url);
97     }
98     MySQLUrlInfo info = new MySQLUrlInfo(url);
99
100     try
101     {
102       File JavaDoc pathDir = new File JavaDoc(path);
103       if (!pathDir.exists())
104       {
105         pathDir.mkdirs();
106         pathDir.mkdir();
107       }
108       String JavaDoc dumpPath = getDumpPhysicalPath(path, dumpName);
109       if (logger.isDebugEnabled())
110       {
111         logger.debug("Dumping " + backend.getURL() + " in " + dumpPath);
112       }
113
114       String JavaDoc executablePath = (optionsMap.containsKey("bindir")
115           ? (String JavaDoc) optionsMap.get("bindir") + File.separator
116           : "")
117           + "mysqldump";
118
119       String JavaDoc addDropDBOption = getAddDropDatabaseOption(executablePath);
120
121       int exitValue = safelyExecuteNativeCommand(executablePath
122           + getBackupStoredProceduresOption(executablePath) + addDropDBOption
123           + " -h " + info.getHost() + " --port=" + info.getPort() + " -u"
124           + login + " --password=" + password + " --databases "
125           + info.getDbName(), dumpPath, true);
126
127       if (exitValue != 0)
128       {
129         printErrors();
130         throw new BackupException(
131             "mysqldump execution did not complete successfully!");
132       }
133     }
134     catch (Exception JavaDoc e)
135     {
136       String JavaDoc msg = "Error while performing backup";
137       logger.error(msg, e);
138       throw new BackupException(msg, e);
139     }
140
141     return new Date JavaDoc();
142   }
143
144   /**
145    * Returns the option to supply to mysql so that it backups stored-procedures
146    * and functions in the dump. This can be either "" (mysql versions prior to
147    * 5.0.13 do not know about stored procedures) or " --routines" (mysql 5.0.13
148    * and above).
149    *
150    * @param executablePath the path to the mysql utility used for making dumps
151    * @return the option to supply to mysql so that it backups strored-procedures
152    * and functions in the dump
153    * @throws IOException in case of error when communicating with the
154    * sub-process.
155    * @throws IllegalArgumentException if no version information can be found in
156    * 'executablePath'
157    */

158   private static String JavaDoc getBackupStoredProceduresOption(String JavaDoc executablePath)
159       throws IOException JavaDoc, IllegalArgumentException JavaDoc
160   {
161     int majorVersion = Integer.parseInt(getMajorVersion(executablePath));
162     if (majorVersion < 5)
163       return "";
164     else if (majorVersion == 5)
165     {
166       String JavaDoc minorVersionString = getMinorVersion(executablePath);
167       float minorVersion = Float.parseFloat(minorVersionString);
168       if (minorVersion < 1)
169       {
170         // --routines supported from v5.0.13 upwards
171
// Need to check for extra value (i.e. 0.9 < 0.13)
172
String JavaDoc extraVersionString;
173         try
174         {
175           extraVersionString = minorVersionString.substring(minorVersionString
176               .indexOf('.') + 1);
177         }
178         catch (IndexOutOfBoundsException JavaDoc e)
179         { // No minor version number
180
return "";
181         }
182         float extraVersion = Float.parseFloat(extraVersionString);
183         if (extraVersion < 13)
184           return "";
185       }
186     }
187
188     return " --routines";
189   }
190
191   /**
192    * Returns "--add-drop-database" option if mysql version is prior to 4.1.13 or ""
193    * for mysql 4.1.13 and above.
194    *
195    * @param executablePath the path to the mysql utility used for making dumps
196    * @return the add-drop-database option
197    * @throws IOException in case of error when communicating with the
198    * sub-process.
199    * @throws IllegalArgumentException if no version information can be found in
200    * 'executablePath'
201    */

202   private static String JavaDoc getAddDropDatabaseOption(String JavaDoc executablePath)
203       throws IOException JavaDoc, IllegalArgumentException JavaDoc
204   {
205     int majorVersion = Integer.parseInt(getMajorVersion(executablePath));
206     if (majorVersion < 4)
207       return "";
208     else if (majorVersion == 4)
209     {
210       String JavaDoc minorVersionString = getMinorVersion(executablePath);
211       float minorVersion = Float.parseFloat(minorVersionString);
212       if (minorVersion < 1)
213         return "";
214       else if (minorVersion < 2)
215       {
216         // --add-drop-database supported from v1.13 upwards
217
// Need to check for extra value (i.e. 1.9 < 1.13)
218
String JavaDoc extraVersionString;
219         try
220         {
221           extraVersionString = minorVersionString.substring(minorVersionString
222               .indexOf('.') + 1);
223         }
224         catch (IndexOutOfBoundsException JavaDoc e)
225         { // No minor version number
226
return "";
227         }
228         float extraVersion = Float.parseFloat(extraVersionString);
229         if (extraVersion < 13)
230           return "";
231       }
232     }
233     return " --add-drop-database";
234   }
235
236   /**
237    * Returns the major version number for specified mysql native utility.
238    *
239    * @param executablePath the path to the mysql native utility
240    * @return the major version number for specified mysql native utility. *
241    * @throws IOException in case of error when communicating with the
242    * sub-process.
243    * @throws IllegalArgumentException if no version information can be found in
244    * 'executablePath'
245    */

246   private static String JavaDoc getMajorVersion(String JavaDoc executablePath)
247       throws IOException JavaDoc, IllegalArgumentException JavaDoc
248   {
249     Process JavaDoc p = Runtime.getRuntime().exec(executablePath + " --version");
250     BufferedReader JavaDoc pout = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(p
251         .getInputStream()));
252
253     String JavaDoc versionString = pout.readLine();
254     // sample version string:
255
// "/usr/bin/mysql Ver 14.12 Distrib 5.0.18, for pc-linux-gnu (i686) using
256
// readline 5.0"
257
Pattern JavaDoc regex = Pattern
258         .compile("mysql.*Ver ([0-9.]*) Distrib ([0-9])\\.([0-9.]*)");
259     Matcher JavaDoc m = regex.matcher(versionString);
260     if (!m.find())
261       throw new IllegalArgumentException JavaDoc(
262           "Can not find version information for " + executablePath);
263
264     return m.group(2);
265   }
266
267   /**
268    * Returns the minor version number for specified mysql native utility.
269    *
270    * @param executablePath the path to the mysql native utility
271    * @return the minor version number for specified mysql native utility. *
272    * @throws IOException in case of error when communicating with the
273    * sub-process.
274    * @throws IllegalArgumentException if no version information can be found in
275    * 'executablePath'
276    */

277   private static String JavaDoc getMinorVersion(String JavaDoc executablePath)
278       throws IOException JavaDoc, IllegalArgumentException JavaDoc
279   {
280     Process JavaDoc p = Runtime.getRuntime().exec(executablePath + " --version");
281     BufferedReader JavaDoc pout = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(p
282         .getInputStream()));
283
284     String JavaDoc versionString = pout.readLine();
285     // sample version string:
286
// "/usr/bin/mysql Ver 14.12 Distrib 5.0.18, for pc-linux-gnu (i686) using
287
// readline 5.0"
288
Pattern JavaDoc regex = Pattern
289         .compile("mysql.*Ver ([0-9.]*) Distrib ([0-9])\\.([0-9.]*)");
290     Matcher JavaDoc m = regex.matcher(versionString);
291     if (!m.find())
292       throw new IllegalArgumentException JavaDoc(
293           "Can not find version information for " + executablePath);
294
295     return m.group(3);
296   }
297
298   /**
299    * @see org.continuent.sequoia.controller.backup.Backuper#restore(org.continuent.sequoia.controller.backend.DatabaseBackend,
300    * java.lang.String, java.lang.String, java.lang.String,
301    * java.lang.String, java.util.ArrayList)
302    */

303   public void restore(DatabaseBackend backend, String JavaDoc login, String JavaDoc password,
304       String JavaDoc dumpName, String JavaDoc path, ArrayList JavaDoc tables) throws BackupException
305   {
306     String JavaDoc url = backend.getURL();
307     if (!url.startsWith("jdbc:mysql:"))
308     {
309       throw new BackupException("Unsupported db url " + url);
310     }
311     MySQLUrlInfo info = new MySQLUrlInfo(url);
312     try
313     {
314       File JavaDoc pathDir = new File JavaDoc(path);
315       if (!pathDir.exists())
316       {
317         pathDir.mkdirs();
318         pathDir.mkdir();
319       }
320       String JavaDoc dumpPath = getDumpPhysicalPath(path, dumpName);
321       if (logger.isDebugEnabled())
322       {
323         logger.debug("Restoring " + backend.getURL() + " from " + dumpPath);
324       }
325
326       String JavaDoc executablePath;
327       executablePath = (optionsMap.containsKey("bindir") ? (String JavaDoc) optionsMap
328           .get("bindir")
329           + File.separator : "")
330           + "mysql";
331
332       boolean addDropDatabaseOptionSupported = !""
333           .equals(getAddDropDatabaseOption(executablePath));
334
335       if (!addDropDatabaseOptionSupported)
336       {
337         // Drop the database if it already exists
338
if (logger.isDebugEnabled())
339           logger.debug("Dropping database '" + info.getDbName() + "'");
340
341         String JavaDoc mysqladminExecutablePath = (optionsMap.containsKey("bindir")
342             ? (String JavaDoc) optionsMap.get("bindir") + File.separator
343             : "")
344             + "mysqladmin";
345         if (executeNativeCommand(mysqladminExecutablePath + " -h "
346             + info.getHost() + " --port=" + info.getPort() + " -f -u" + login
347             + " --password=" + password + " drop " + info.getDbName()) != 0)
348         {
349           // Errors can happen there, e.g. if the database does not exist yet.
350
// Just log them, and carry-on...
351
printErrors();
352         }
353       }
354
355       int exitValue = safelyExecuteNativeCommand(executablePath + " -h "
356           + info.getHost() + " --port=" + info.getPort() + " -u" + login
357           + " --password=" + password
358           + (addDropDatabaseOptionSupported ? " " + info.getDbName() : ""),
359           dumpPath, false);
360
361       if (exitValue != 0)
362       {
363         printErrors();
364         throw new BackupException(
365             "mysql execution did not complete successfully!");
366       }
367     }
368     catch (Exception JavaDoc e)
369     {
370       String JavaDoc msg = "Error while performing restore";
371       logger.error(msg, e);
372       throw new BackupException(msg, e);
373     }
374   }
375
376   /**
377    * @see org.continuent.sequoia.controller.backup.Backuper#deleteDump(java.lang.String,
378    * java.lang.String)
379    */

380   public void deleteDump(String JavaDoc path, String JavaDoc dumpName) throws BackupException
381   {
382     File JavaDoc toRemove = new File JavaDoc(getDumpPhysicalPath(path, dumpName));
383     if (logger.isDebugEnabled())
384       logger.debug("Deleting compressed dump " + toRemove);
385     toRemove.delete();
386   }
387
388   /**
389    * Get the dump physical path from its logical name
390    *
391    * @param path the path where the dump is stored
392    * @param dumpName dump logical name
393    * @return path to zip file
394    */

395   private String JavaDoc getDumpPhysicalPath(String JavaDoc path, String JavaDoc dumpName)
396   {
397     return path + File.separator + dumpName;
398   }
399
400   /**
401    * Allow to parse MySQL URL.
402    */

403   protected class MySQLUrlInfo
404   {
405     private boolean isLocal;
406
407     private String JavaDoc host;
408
409     private String JavaDoc port;
410
411     private String JavaDoc dbName;
412
413     // Used to parse url
414
private Pattern JavaDoc pattern = Pattern
415                                 .compile("jdbc:mysql:((//([a-zA-Z0-9_\\-.]+|\\[[a-fA-F0-9:]+])((:(\\d+))|))/|)([a-zA-Z][a-zA-Z0-9_\\-]*)(\\?.*)?");
416
417     Matcher JavaDoc matcher;
418
419     /**
420      * Creates a new <code>MySQLUrlInfo</code> object, used to parse the
421      * postgresql jdbc options. If host and/or port aren't specified, will
422      * default to localhost:3306. Note that database name must be specified.
423      *
424      * @param url the MySQL JDBC url to parse
425      */

426     public MySQLUrlInfo(String JavaDoc url)
427     {
428       matcher = pattern.matcher(url);
429
430       if (matcher.matches())
431       {
432         if (matcher.group(3) != null)
433           host = matcher.group(3);
434         else
435           host = DEFAULT_MYSQL_HOST;
436
437         if (matcher.group(6) != null)
438           port = matcher.group(6);
439         else
440           port = DEFAULT_MYSQL_PORT;
441
442         dbName = matcher.group(7);
443       }
444     }
445
446     /**
447      * Gets the HostParameters of this postgresql jdbc url as a String that can
448      * be used to pass into cmd line/shell calls.
449      *
450      * @return a string that can be used to pass into a cmd line/shell call.
451      */

452     public String JavaDoc getHostParametersString()
453     {
454       if (isLocal)
455       {
456         return "";
457       }
458       else
459       {
460         return "-h " + host + " --port=" + port;
461       }
462     }
463
464     /**
465      * Gets the database name part of this postgresql jdbc url.
466      *
467      * @return the database name part of this postgresql jdbc url.
468      */

469     public String JavaDoc getDbName()
470     {
471       return dbName;
472     }
473
474     /**
475      * Gets the host part of this postgresql jdbc url.
476      *
477      * @return the host part of this postgresql jdbc url.
478      */

479     public String JavaDoc getHost()
480     {
481       return host;
482     }
483
484     /**
485      * Gets the port part of this postgresql jdbc url.
486      *
487      * @return the port part of this postgresql jdbc url.
488      */

489     public String JavaDoc getPort()
490     {
491       return port;
492     }
493
494     /**
495      * Checks whether this postgresql jdbc url refers to a local db or not, i.e.
496      * has no host specified, e.g. jdbc:postgresql:myDb.
497      *
498      * @return true if this postgresql jdbc url has no host specified, i.e.
499      * refers to a local db.
500      */

501     public boolean isLocal()
502     {
503       return isLocal;
504     }
505
506   }
507
508   /**
509    * Executes a native operating system command. Output of these commands is
510    * captured and logged.
511    *
512    * @param command String of command to execute
513    * @return 0 if successful, any number otherwise
514    * @throws IOException
515    * @throws InterruptedException
516    */

517   protected int executeNativeCommand(String JavaDoc command) throws IOException JavaDoc,
518       InterruptedException JavaDoc
519   {
520     return nativeCmdExec.executeNativeCommand(command, null, null, 0,
521         getIgnoreStdErrOutput());
522   }
523
524   /**
525    * Executes a native operating system dump or restore command. Output of these
526    * commands is carefully captured and logged.
527    *
528    * @param command String of command to execute
529    * @param dumpPath path to the dump file (either source or dest, depending on
530    * specified 'backup' parameter.
531    * @param backup specifies whether we are doing a backup (true) or a restore
532    * (false). This sets the semantics of the dumpPath parameter to
533    * resp. dest or source file name.
534    * @return 0 if successful, 1 otherwise
535    */

536   protected int safelyExecuteNativeCommand(String JavaDoc command, String JavaDoc dumpPath,
537       boolean backup) throws IOException JavaDoc
538   {
539     if (backup)
540       return nativeCmdExec.safelyExecNativeCommand(command, null,
541           new FileOutputStream JavaDoc(dumpPath), 0, getIgnoreStdErrOutput()) ? 0 : 1;
542     else
543     {
544       FileInputStream JavaDoc dumpStream = new FileInputStream JavaDoc(dumpPath);
545       return nativeCmdExec.safelyExecNativeCommand(command,
546           new NativeCommandInputSource(dumpStream), null, 0,
547           getIgnoreStdErrOutput()) ? 0 : 1;
548     }
549   }
550
551   protected void printErrors()
552   {
553     ArrayList JavaDoc errors = nativeCmdExec.getStderr();
554     Iterator JavaDoc it = errors.iterator();
555     while (it.hasNext())
556     {
557       String JavaDoc msg = (String JavaDoc) it.next();
558       logger.info(msg);
559       endUserLogger.error(msg);
560     }
561   }
562 }
Popular Tags