/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* 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 General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and 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.
*/
package org.rhq.core.pluginapi.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.util.stream.StreamUtil;
/**
* A common snapshot report that allows you to prepare a zip'ed file containing
* a snapshot of content belonging to a resource. This report contains a snapshot
* configuration, log and data files along with a customizable set of additional files.
* Users of this object can pick and choose which types of files to snapshot and filter
* the files of those types.
*
* This class can be subclassed if you wish to alter the way it collects and stores
* the snapshot content. For example, if the configuration is not stored as files, but instead
* stored in memory, you can subclass this to obtain the configuration by reading memory, writing
* that data to a temporary file and then having this class add that temporary file to the
* snapshot zip file.
*
* @author John Mazzitelli
*/
public class SnapshotReport {
private static final Log log = LogFactory.getLog(SnapshotReport.class);
/**
* The relative directory under the report where the config files can be found. This
* is the directory that will be found inside the zip file.
*/
public static final String REPORT_CONFIG_DIRECTORY = "config";
/**
* The relative directory under the report where the log files can be found. This
* is the directory that will be found inside the zip file.
*/
public static final String REPORT_LOG_DIRECTORY = "log";
/**
* The relative directory under the report where the data files can be found. This
* is the directory that will be found inside the zip file.
*/
public static final String REPORT_DATA_DIRECTORY = "data";
/**
* A boolean config property that dictates if a snapshot of config files should be in the report.
*/
public static final String PROP_SNAPSHOT_CONFIG_FILES = "snapshotConfigEnabled";
/**
* A config property whose value is the directory relative to the base directory where the
* configuration files are located.
*/
public static final String PROP_CONFIG_DIRECTORY = "snapshotConfigDirectory";
/**
* A config property whose value is a regular expression that matches all config files
* that are to be snapshotted. This regex must match files under the config directory.
*/
public static final String PROP_CONFIG_REGEX = "snapshotConfigRegex";
/**
* A config property whose value is the boolean flag to indicate if
* the snapshot should include files found in subdirectories of the config directory.
*/
public static final String PROP_CONFIG_RECURSIVE = "snapshotConfigRecursive";
/**
* A boolean config property that dictates if a snapshot of log files should be in the report.
*/
public static final String PROP_SNAPSHOT_LOG_FILES = "snapshotLogEnabled";
/**
* A config property whose value is the directory relative to the base directory where the
* log files are located.
*/
public static final String PROP_LOG_DIRECTORY = "snapshotLogDirectory";
/**
* A config property whose value is a regular expression that matches all log files
* that are to be snapshotted. This regex must match files under the log directory.
*/
public static final String PROP_LOG_REGEX = "snapshotLogRegex";
/**
* A config property whose value is the boolean flag to indicate if
* the snapshot should include files found in subdirectories of the log directory.
*/
public static final String PROP_LOG_RECURSIVE = "snapshotLogRecursive";
/**
* A boolean config property that dictates if a snapshot of data files should be in the report.
*/
public static final String PROP_SNAPSHOT_DATA_FILES = "snapshotDataEnabled";
/**
* A config property whose value is the directory relative to the base directory where the
* data files are located.
*/
public static final String PROP_DATA_DIRECTORY = "snapshotDataDirectory";
/**
* A config property whose value is a regular expression that matches all data files
* that are to be snapshotted. This regex must match files under the data directory.
*/
public static final String PROP_DATA_REGEX = "snapshotDataRegex";
/**
* A config property whose value is the boolean flag to indicate if
* the snapshot should include files found in subdirectories of the data directory.
*/
public static final String PROP_DATA_RECURSIVE = "snapshotDataRecursive";
/**
* A boolean config property that dictates if a snapshot of the additional files should be in the report.
*/
public static final String PROP_SNAPSHOT_ADDITIONAL_FILES = "snapshotAdditionalFilesEnabled";
/**
* The property name for the list of of additional files.
*/
public static final String PROP_ADDITIONAL_FILES_LIST = "snapshotAdditionalFilesList";
/**
* A config property whose value is a directory relative to the base directory where
* additional files are located.
* This should be a property inside a map where that map is a list item within a additional files list.
*/
public static final String PROP_ADDITIONAL_FILES_DIRECTORY = "snapshotAdditionalFilesDirectory";
/**
* A config property whose value is a regular expression that matches all additional files
* that are to be snapshotted. This regex must match files under its associated
* additional files directory.
* This should be a property inside a map where that map is a list item within a additional files list.
*/
public static final String PROP_ADDITIONAL_FILES_REGEX = "snapshotAdditionalFilesRegex";
/**
* A config property whose value is the boolean flag to indicate if
* the snapshot should include files found in subdirectories of the additional files directory.
*/
public static final String PROP_ADDITIONAL_FILES_RECURSIVE = "snapshotAdditionalFilesRecursive";
/**
* A config property whose value is the base directory where all config, logs and data files are under.
* If a config, log or data directory was already specified as an absolute directory, this base directory
* will not be used. Only when a config, log or data directory is relative will it be assumed to be under
* the base directory.
*/
public static final String PROP_BASE_DIRECTORY = "snapshotBaseDirectory";
/**
* Optional property that can be specified to define where to store the output snapshot report.
* If not specified, the platform's tmp directory will be used.
*/
public static final String PROP_REPORT_OUTPUT_DIRECTORY = "snapshotReportOutputDirectory";
private final String name;
private final String description;
private final Configuration configuration;
public SnapshotReport(String name, String description, Configuration config) {
this.name = name;
this.description = description;
this.configuration = config;
}
public File generate() throws Exception {
File outputFile = getSnapshotReportFile();
if (log.isDebugEnabled()) {
log.debug("Generating snapshot [" + outputFile + "]");
}
FileOutputStream fos = new FileOutputStream(outputFile);
ZipOutputStream zip = new ZipOutputStream(fos);
try {
// generate the file that will contain some general info about the snapshot report
ZipEntry zipEntry = new ZipEntry("snapshot.properties");
zip.putNextEntry(zipEntry);
Properties properties = new Properties();
properties.setProperty("name", getName());
properties.setProperty("description", getDescription());
properties.setProperty("epochmillis", Long.toString(System.currentTimeMillis()));
properties.store(zip, null);
// now store a snapshot of each file to the report
Map<String, URL> allFiles = getAllFilesToSnapshot();
for (Map.Entry<String, URL> snapshotFileEntry : allFiles.entrySet()) {
zipEntry = new ZipEntry(snapshotFileEntry.getKey());
zip.putNextEntry(zipEntry);
InputStream input = snapshotFileEntry.getValue().openStream();
try {
StreamUtil.copy(input, zip, false);
} finally {
input.close();
}
}
} finally {
zip.close();
}
if (log.isDebugEnabled()) {
log.debug("Generated snapshot [" + outputFile + "] of size [" + outputFile.length() + "]");
}
return outputFile;
}
protected String getName() {
return this.name;
}
protected String getDescription() {
return this.description;
}
protected Configuration getConfiguration() {
return this.configuration;
}
/**
* This only returns the file where the snapshot will be stored. This method does NOT
* generate the actual snapshot report. Call {@link #generate()} to get the full snapshot content.
*
* @return the file where the snapshot report will be stored when it is generated
*
* @throws Exception if the file could not be determined for some reason
*/
protected File getSnapshotReportFile() throws Exception {
String dirString = getConfiguration().getSimpleValue(PROP_REPORT_OUTPUT_DIRECTORY, null);
File dir = null;
if (dirString != null) {
dir = new File(dirString);
dir.mkdirs();
}
File outputFile = File.createTempFile(getName(), ".zip", dir);
return outputFile;
}
protected Map<String, URL> getAllFilesToSnapshot() throws Exception {
Map<String, URL> allFiles = new HashMap<String, URL>();
Map<String, URL> configFiles = getConfigFilesToSnapshot();
if (configFiles != null) {
allFiles.putAll(configFiles);
}
Map<String, URL> logFiles = getLogFilesToSnapshot();
if (logFiles != null) {
allFiles.putAll(logFiles);
}
Map<String, URL> dataFiles = getDataFilesToSnapshot();
if (dataFiles != null) {
allFiles.putAll(dataFiles);
}
Map<String, URL> additionalFiles = getAdditionalFilesToSnapshot();
if (additionalFiles != null) {
allFiles.putAll(additionalFiles);
}
return allFiles;
}
protected Map<String, URL> getConfigFilesToSnapshot() throws Exception {
Configuration config = getConfiguration();
if (!"true".equals(config.getSimpleValue(PROP_SNAPSHOT_CONFIG_FILES, "false"))) {
return null; // config files are not to be snapshotted into the report, abort
}
String baseDir = config.getSimpleValue(PROP_BASE_DIRECTORY, null);
String confDir = config.getSimpleValue(PROP_CONFIG_DIRECTORY, "conf");
String confRegex = config.getSimpleValue(PROP_CONFIG_REGEX, null);
String recursive = config.getSimpleValue(PROP_CONFIG_RECURSIVE, "false");
Map<String, URL> filesMap = null;
File confDirFile = new File(confDir);
if (!confDirFile.isAbsolute()) {
confDirFile = new File(baseDir, confDir);
}
RegexFilenameFilter filter = new RegexFilenameFilter(confRegex, recursive);
File[] confFiles = confDirFile.listFiles(filter);
if (confFiles != null) {
filesMap = new HashMap<String, URL>(confFiles.length);
populateFilesMap(confFiles, filesMap, REPORT_CONFIG_DIRECTORY, filter);
} else {
// the directory probably doesn't exist, this may not be fatal so just log and keep going
log.warn("Failed to get list of conf files from [" + confDirFile + "]");
}
return filesMap;
}
protected Map<String, URL> getLogFilesToSnapshot() throws Exception {
Configuration config = getConfiguration();
if (!"true".equals(config.getSimpleValue(PROP_SNAPSHOT_LOG_FILES, "false"))) {
return null; // log files are not to be snapshotted into the report, abort
}
String baseDir = config.getSimpleValue(PROP_BASE_DIRECTORY, null);
String logDir = config.getSimpleValue(PROP_LOG_DIRECTORY, "logs");
String logRegex = config.getSimpleValue(PROP_LOG_REGEX, null);
String recursive = config.getSimpleValue(PROP_LOG_RECURSIVE, "false");
Map<String, URL> filesMap = null;
File logDirFile = new File(logDir);
if (!logDirFile.isAbsolute()) {
logDirFile = new File(baseDir, logDir);
}
RegexFilenameFilter filter = new RegexFilenameFilter(logRegex, recursive);
File[] logFiles = logDirFile.listFiles(filter);
if (logFiles != null) {
filesMap = new HashMap<String, URL>(logFiles.length);
populateFilesMap(logFiles, filesMap, REPORT_LOG_DIRECTORY, filter);
} else {
// the directory probably doesn't exist, this may not be fatal so just log and keep going
log.warn("Failed to get list of log files from [" + logDirFile + "]");
}
return filesMap;
}
protected Map<String, URL> getDataFilesToSnapshot() throws Exception {
Configuration config = getConfiguration();
if (!"true".equals(config.getSimpleValue(PROP_SNAPSHOT_DATA_FILES, "false"))) {
return null; // data files are not to be snapshotted into the report, abort
}
String baseDir = config.getSimpleValue(PROP_BASE_DIRECTORY, null);
String dataDir = config.getSimpleValue(PROP_DATA_DIRECTORY, "data");
String dataRegex = config.getSimpleValue(PROP_DATA_REGEX, null);
String recursive = config.getSimpleValue(PROP_DATA_RECURSIVE, "false");
Map<String, URL> filesMap = null;
File dataDirFile = new File(dataDir);
if (!dataDirFile.isAbsolute()) {
dataDirFile = new File(baseDir, dataDir);
}
RegexFilenameFilter filter = new RegexFilenameFilter(dataRegex, recursive);
File[] dataFiles = dataDirFile.listFiles(filter);
if (dataFiles != null) {
filesMap = new HashMap<String, URL>(dataFiles.length);
populateFilesMap(dataFiles, filesMap, REPORT_DATA_DIRECTORY, filter);
} else {
// the directory probably doesn't exist, this may not be fatal so just log and keep going
log.warn("Failed to get list of data files from [" + dataDirFile + "]");
}
return filesMap;
}
protected Map<String, URL> getAdditionalFilesToSnapshot() throws Exception {
Configuration config = getConfiguration();
if (!"true".equals(config.getSimpleValue(PROP_SNAPSHOT_ADDITIONAL_FILES, "false"))) {
return null; // any additional files are not to be snapshotted into the report, abort
}
String baseDir = config.getSimpleValue(PROP_BASE_DIRECTORY, null);
PropertyList additionalList = config.getList(PROP_ADDITIONAL_FILES_LIST);
if (additionalList == null || additionalList.getList() == null) {
return null; // there are no additional files defined, just skip it
}
Map<String, URL> filesMap = new HashMap<String, URL>();
for (Property property : additionalList.getList()) {
PropertyMap additionalFileMap = (PropertyMap) property; // must be a map, let it throw exception if not
String additionalFilesDir = additionalFileMap.getSimpleValue(PROP_ADDITIONAL_FILES_DIRECTORY, "");
String additionalFilesRegex = additionalFileMap.getSimpleValue(PROP_ADDITIONAL_FILES_REGEX, null);
// it is possible that the user wanted to just disable one of the additional files
String additionalFilesEnabled = additionalFileMap.getSimpleValue(PROP_SNAPSHOT_ADDITIONAL_FILES, "true");
if (!"true".equals(additionalFilesEnabled)) {
continue;
}
File additionalFilesDirFile = new File(additionalFilesDir);
if (!additionalFilesDirFile.isAbsolute()) {
additionalFilesDirFile = new File(baseDir, additionalFilesDir);
}
String recursive = additionalFileMap.getSimpleValue(PROP_ADDITIONAL_FILES_RECURSIVE, "false");
RegexFilenameFilter filter = new RegexFilenameFilter(additionalFilesRegex, recursive);
File[] additionalFiles = additionalFilesDirFile.listFiles(filter);
if (additionalFiles != null) {
populateFilesMap(additionalFiles, filesMap, additionalFilesDir, filter);
} else {
// the directory probably doesn't exist, this may not be fatal so just log and keep going
log.warn("Failed to get list of additional files from [" + additionalFilesDirFile + "]");
}
}
return filesMap;
}
private void populateFilesMap(File[] filesDirectories, Map<String, URL> filesMap, String directoryPrefix,
RegexFilenameFilter filter) throws Exception {
for (File fileDirectory : filesDirectories) {
if (fileDirectory.isDirectory()) {
File[] subDirectoryFiles = fileDirectory.listFiles(filter);
if (subDirectoryFiles != null) {
populateFilesMap(subDirectoryFiles, filesMap, directoryPrefix + "/" + fileDirectory.getName(),
filter);
} else {
log.warn("Failed to get subdirectory files for [" + fileDirectory + "]");
}
} else {
filesMap.put(directoryPrefix + '/' + fileDirectory.getName(), fileDirectory.toURI().toURL());
}
}
return;
}
/**
* Filename filter that matches the filename if it matches a given regular expression.
* If the given regular expression is <code>null</code>, this filter matches everything.
*/
protected static class RegexFilenameFilter implements FilenameFilter {
private String regex;
private boolean recursive;
public RegexFilenameFilter(String regex, String recursive) {
this.regex = regex;
this.recursive = Boolean.parseBoolean(recursive);
}
public boolean accept(File dir, String name) {
if (new File(dir, name).isDirectory()) {
return recursive;
}
return this.regex == null || name.matches(this.regex);
}
}
}