/*
* eXist Open Source Native XML Database
* Copyright (C) 2006-2011 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.storage;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.backup.Backup;
import org.exist.util.Configuration;
import org.exist.xmldb.XmldbURI;
import org.xml.sax.SAXException;
import org.xmldb.api.base.XMLDBException;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
/**
* BackupSystemTask creates an XML backup of the current database into a directory
* or zip file. Running the backup as a system task guarantees a consistent backup. No
* other transactions will be allowed while the backup is in progress.
*
* The following properties can be used to configure the backup task if passed to the
* {@link #configure(org.exist.util.Configuration, java.util.Properties)} method:
*
* <table>
* <tr>
* <td>collection</td>
* <td>the collection to backup, specified as an absolute path into the db, e.g. /db/back-me-up</td>
* </tr>
* <tr>
* <td>user</td>
* <td>a valid user for writing the backup. Usually, this needs to be a user in the dba
* database admin group.</td>
* </tr>
* <tr>
* <td>password</td>
* <td>the password for the user</td>
* </tr>
* <tr>
* <td>dir</td>
* <td>the output directory where the backup will be written</td>
* </tr>
* <tr>
* <td>prefix</td>
* <td>a prefix for the generated file name. the final file name will consist of
* prefix + current-dateTime + suffix</td>
* </tr>
* <tr>
* <td>suffix</td>
* <td>a suffix for the generated file name. If it ends with .zip, BackupSystemTask will
* directly write the backup into a zip file. Otherwise, it will write into a plain directory.</td>
* </tr>
* </table>
*/
public class BackupSystemTask implements SystemTask {
private static final Logger LOG = Logger.getLogger(BackupSystemTask.class);
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HHmm");
private String user;
private String password;
private File directory;
private String suffix;
private XmldbURI collection;
private String prefix;
// purge old zip backup files
private int zipFilesMax = -1;
public void configure(Configuration config, Properties properties) throws EXistException {
user = properties.getProperty("user", "guest");
password = properties.getProperty("password", "guest");
String collName = properties.getProperty("collection", "xmldb:exist:///db");
if (!collName.startsWith("xmldb:exist:"))
collName = "xmldb:exist://" + collName;
collection = XmldbURI.create(collName);
LOG.debug("Collection to backup: " + collection.toString() + ". User: " + user);
suffix = properties.getProperty("suffix", "");
prefix = properties.getProperty("prefix", "");
String dir = properties.getProperty("dir", "backup");
directory = new File(dir);
if (!directory.isAbsolute()) {
dir = (String)config.getProperty(BrokerPool.PROPERTY_DATA_DIR) +
File.separatorChar + dir;
directory = new File(dir);
}
directory.mkdirs();
// check for max zip files
String filesMaxStr = properties.getProperty("zip-files-max");
if (LOG.isDebugEnabled()) LOG.debug("zip-files-max: " + filesMaxStr);
if (null != filesMaxStr)
try
{
zipFilesMax = new Integer(filesMaxStr).intValue();
}
catch (NumberFormatException e) {LOG.debug("zip-files-max property error", e);}
}
public void execute(DBBroker broker) throws EXistException {
String dateTime = df.format(new Date());
String dest = directory.getAbsolutePath() + File.separatorChar + prefix + dateTime + suffix;
Backup backup = new Backup(user, password, dest, collection);
try {
backup.backup(false, null);
} catch (XMLDBException e) {
LOG.debug(e.getMessage(), e);
throw new EXistException(e.getMessage(), e);
} catch (IOException e) {
LOG.debug(e.getMessage(), e);
throw new EXistException(e.getMessage(), e);
} catch (SAXException e) {
LOG.debug(e.getMessage(), e);
throw new EXistException(e.getMessage(), e);
}
// see if old zip files need to be purged
if (suffix.equals(".zip") && zipFilesMax > 0) purgeZipFiles();
}
public void purgeZipFiles()
{
if (LOG.isDebugEnabled()) LOG.debug("starting purgeZipFiles()");
// get all files in target directory
File[] files = directory.listFiles();
if (files.length > 0)
{
Map sorted = new TreeMap();
for (int i=0; i < files.length; i++)
{
//check for prefix and suffix match
if (files[i].getName().startsWith(prefix) && files[i].getName().endsWith(suffix))
{
sorted.put(Long.toString(files[i].lastModified()), files[i]);
}
}
if (sorted.size() > zipFilesMax)
{
Set keys = sorted.keySet();
Iterator ki = keys.iterator();
int i = sorted.size() - zipFilesMax;
while (ki.hasNext())
{
File f = (File) sorted.get(ki.next());
if (i > 0)
{
if (LOG.isDebugEnabled()) LOG.debug("Purging backup : " + f.getName());
f.delete();
}
i--;
}
}
}
}
public boolean afterCheckpoint() {
return false;
}
}