/******************************************************************************* * Copyright (c) 2015 Development Gateway, Inc and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the MIT License (MIT) * which accompanies this distribution, and is available at * https://opensource.org/licenses/MIT * * Contributors: * Development Gateway - initial API and implementation *******************************************************************************/ package org.devgateway.toolkit.forms.service; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.CallableStatement; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.zeroturnaround.zip.ZipUtil; /** * @author mpostelnicu Provides built-in backup services. Defaults to the * database location derby.system.home. Currently works only for Derby. * Runs 9PM daily (good backup time for both EST and CET) */ @Service public class DerbyDatabaseBackupService { private static final Logger LOGGER = Logger.getLogger(DerbyDatabaseBackupService.class); public static final String DATABASE_PRODUCT_NAME_APACHE_DERBY = "Apache Derby"; public static final String ARCHIVE_SUFFIX = ".zip"; @Autowired private DataSource datasource; private String lastBackupURL; protected String databaseName = "sample"; /** * Invokes backup database. This is invoked by Spring {@link Scheduled} We * use a cron format and invoke it every day at 21:00 server time. That * should be a good time for backup for both EST and CET */ @Scheduled(cron = "0 0 21 * * ?") public void backupDatabase() { String databaseProductName; try { databaseProductName = datasource.getConnection().getMetaData().getDatabaseProductName(); } catch (SQLException e) { LOGGER.error("Cannot read databaseProductName from Connection!" + DerbyDatabaseBackupService.class.getCanonicalName() + " cannot continue!" + e); return; } if (DATABASE_PRODUCT_NAME_APACHE_DERBY.equals(databaseProductName)) { backupDerbyDatabase(); } else { throw new RuntimeException( "Scheduled database backup for unsupported database type " + databaseProductName); } } /** * Gets the URL (directory/file) of the backupPath. Adds as prefixes the * last leaf of backup's location parent directory + {@link #databaseName} * If the backupPath does not have a parent, it uses the host name from * {@link InetAddress#getLocalHost()} * * @param backupPath * the parent directory for the backup * @return the backup url to be used by the backup procedure * @throws UnknownHostException */ private String createBackupURL(final String backupPath) { java.text.SimpleDateFormat todaysDate = new java.text.SimpleDateFormat("yyyyMMdd-HHmmss"); String parent = null; Path originalPath = Paths.get(backupPath); Path filePath = originalPath.getFileName(); if (filePath != null) { parent = filePath.toString(); } else { try { // fall back to hostname instead parent = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { LOGGER.debug("Cannot get localhost/hostname! " + e); return null; } } String backupURL = backupPath + "/" + parent + "-" + databaseName + "-" + todaysDate.format((java.util.Calendar.getInstance()).getTime()); return backupURL; } /** * Use backup.home system variable, if exists, as homedir for backups If * backup.home does not exist try using derby.system.home If that is also * null, use user.dir * * @return the backupURL */ private String createBackupURL() { String backupHomeString = System.getProperty("backup.home"); if (backupHomeString == null) { backupHomeString = System.getProperty("derby.system.home") != null ? System.getProperty("derby.system.home") : System.getProperty("user.dir"); } String backupURL = createBackupURL(backupHomeString); return backupURL; } /** * Backup the On-Line Derby database. This temporarily locks the db in * readonly mode * * Invokes SYSCS_BACKUP_DATABASE and dumps the database to the temporary * directory Use {@link ZipUtil#pack(File, File)} to zip the directory * Deletes the temporary directory * * @see #createBackupURL(String) */ private void backupDerbyDatabase() { lastBackupURL = createBackupURL(); CallableStatement cs = null; try { cs = datasource.getConnection().prepareCall("CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE(?)"); cs.setString(1, lastBackupURL); cs.execute(); } catch (SQLException e) { LOGGER.error("Cannot perform database backup!", e); return; } finally { try { cs.close(); } catch (SQLException e) { LOGGER.error("Error closing backup connection ", e); return; } } File backupURLFile = new File(lastBackupURL); // zip the contents and delete the dir ZipUtil.pack(backupURLFile, new File(lastBackupURL + ARCHIVE_SUFFIX)); // delete the backup directory that we just zipped try { FileUtils.deleteDirectory(backupURLFile); } catch (IOException e) { LOGGER.error("Cannot delete temporary backup directory", e); } LOGGER.info("Backed up database to " + lastBackupURL + ARCHIVE_SUFFIX); } public String getLastBackupURL() { return lastBackupURL; } }