/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.util.db.management.jmx; import java.io.File; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import com.google.common.collect.Sets; /** * JMX management of a HSQL database. */ @ManagedResource(description = "Basic housekeeping operations on HSQL that can be managed via JMX") public class HSQLDatabaseMBean extends DatabaseMBean { private static final Logger s_logger = LoggerFactory.getLogger(HSQLDatabaseMBean.class); /** * All active instances - used to generate meaningful names. */ private static final Set<HSQLDatabaseMBean> s_instances = Sets.newSetFromMap(new WeakHashMap<HSQLDatabaseMBean, Boolean>()); /* package */static void flush() { s_instances.clear(); } /** * Name of the driver class used in configuration files. */ protected static final String DRIVER_CLASS = "org.hsqldb.jdbcDriver"; public HSQLDatabaseMBean() { super("HSQLDB"); synchronized (s_instances) { s_instances.add(this); } } /** * Find the shortest unique name among the active instances. * <p> * Working from the right hand end of the local JDBC string the shortest form is found that uniquely identifies the database. * * @return a name, not containing slashes, that can be used for the backup set. */ protected String createBackupName() { final Integer one = 1; final Map<String, Integer> nameCount = new HashMap<String, Integer>(); synchronized (s_instances) { for (HSQLDatabaseMBean instance : s_instances) { String jdbc = instance.getLocalJdbc(); if (nameCount.put(jdbc, one) != null) { // Another MBean already has this JDBC string -- ignore continue; } String shortName = null; int slash = jdbc.lastIndexOf('/'); while (slash >= 0) { if (shortName == null) { shortName = jdbc.substring(slash + 1); } else { shortName = jdbc.substring(slash + 1) + "-" + shortName; } jdbc = jdbc.substring(0, slash); slash = jdbc.lastIndexOf('/'); final Integer count = nameCount.get(shortName); if (count == null) { s_logger.debug("Found {} for {}", shortName, instance); nameCount.put(shortName, one); } else { s_logger.debug("Name collision on {} for {}", shortName, instance); nameCount.put(shortName, count + 1); } } } } String jdbc = getLocalJdbc(); String shortName = null; int slash = jdbc.lastIndexOf('/'); while (slash >= 0) { if (shortName == null) { shortName = jdbc.substring(slash + 1); } else { shortName = jdbc.substring(slash + 1) + "-" + shortName; } if (one.equals(nameCount.get(shortName))) { s_logger.info("Using backup set {} for {}", shortName, this); return shortName; } jdbc = jdbc.substring(0, slash); slash = jdbc.lastIndexOf('/'); } throw new UnsupportedOperationException("Can't generate short name for '" + getLocalJdbc() + "'"); } protected String createBackupPath() { String backupPath = System.getProperty("backup.dir"); if (backupPath == null) { throw new UnsupportedOperationException("backup.dir system property is not set"); } backupPath = backupPath + File.separatorChar + "hsqldb" + File.separatorChar + createBackupName(); final File backup = new File(backupPath); if (!backup.exists()) { backup.mkdirs(); if (!backup.exists()) { throw new UnsupportedOperationException("Can't create folder " + backup); } } if (!backup.isDirectory()) { throw new UnsupportedOperationException("Can't create folder " + backup); } s_logger.info("Writing backup to {}", backupPath); return backupPath + File.separatorChar; } @ManagedOperation(description = "Performs an on-line, checkpointed, backup.") public String onlineBackup() { final String path = createBackupPath(); try (Connection connection = getDataSource().getConnection()) { final PreparedStatement statement = connection.prepareStatement("BACKUP DATABASE TO '" + path + "' BLOCKING"); statement.execute(); s_logger.info("Checkpoint backup written to {}", path); } catch (SQLException e) { s_logger.error("Caught exception", e); throw new UnsupportedOperationException("SQL error attempting backup: " + e.getMessage()); } return "Files backed up to:\n" + path; } @ManagedOperation(description = "Performs an on-line, hot, backup.") public String hotBackup() { final String path = createBackupPath(); try (Connection connection = getDataSource().getConnection()) { final PreparedStatement statement = connection.prepareStatement("BACKUP DATABASE TO '" + path + "' NOT BLOCKING"); statement.execute(); s_logger.info("Hot backup written to {}", path); } catch (SQLException e) { s_logger.error("Caught exception", e); throw new UnsupportedOperationException("SQL error attempting backup: " + e.getMessage()); } return "Files backed up to:\n" + path; } }