/* * RHQ Management Platform * Copyright (C) 2005-2010 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.util.updater; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.rhq.core.util.file.FileUtil; /** * This loads and stores metadata about installs of a particular bundle deployment. * * @author John Mazzitelli */ public class DeploymentsMetadata { public static final String METADATA_DIR = ".rhqdeployments"; public static final String CURRENT_DEPLOYMENT_FILE = "current-deployment.properties"; public static final String PREVIOUS_DEPLOYMENT_FILE = "previous-deployment.properties"; public static final String DEPLOYMENT_FILE = "deployment.properties"; public static final String HASHCODES_FILE = "file-hashcodes.dat"; public static final String BACKUP_DIR = "backup"; public static final String EXT_BACKUP_DIR = "ext-backup"; private final File rootDirectory; /** * Creates the metadata object given the root directory where the bundle deployment is installed. * * @param rootDirectory the location where the bundle deployments will go and where the metadata directory is located */ public DeploymentsMetadata(File rootDirectory) { if (rootDirectory == null) { throw new NullPointerException("rootDirectory == null"); } this.rootDirectory = rootDirectory.getAbsoluteFile(); // ensure it has an absolute path } @Override public String toString() { return "DeploymentMetadata [" + getRootDirectory() + "]"; } /** * @return the root directory where the bundle deployments are and where the metadata directory is located. * The returned File will have an absolute path. */ public File getRootDirectory() { return rootDirectory; } /** * If this object's {@link #getRootDirectory()} refers to a directory containing managed deployments, * this returns <code>true</code>. If that root location is not managed, <code>false</code> is returned. * * @return indication if the root directory has deployments that are managed */ public boolean isManaged() { File metaDir = getMetadataDirectory(); if (!metaDir.isDirectory()) { return false; // there isn't even a top level metadata directory, we can't be managing deployments here } try { File currentMetaDir = getCurrentDeploymentMetadataDirectory(); if (!currentMetaDir.isDirectory()) { return false; // strange, why is this missing? } } catch (Exception e) { return false; } return true; } /** * @return the directory where the metadata does or should exist */ public File getMetadataDirectory() { return new File(getRootDirectory(), METADATA_DIR); } /** * Same as {@link #getMetadataDirectory()}, however, if the directory doesn't exist, an exception is thrown. * @return the directory where the metadata exists * @throws Exception if the directory does not exist */ public File getMetadataDirectoryOnlyIfExists() throws Exception { File metaDir = getMetadataDirectory(); if (!metaDir.isDirectory()) { throw new IllegalStateException("Not a managed deployment location: " + getRootDirectory()); } return metaDir; } /** * Returns information about the current deployment. * * @return props with current deployment information * @throws Exception if could not determine the current deployment */ public DeploymentProperties getCurrentDeploymentProperties() throws Exception { try { File metaDir = getMetadataDirectoryOnlyIfExists(); File propertiesFile = new File(metaDir, CURRENT_DEPLOYMENT_FILE); DeploymentProperties props = DeploymentProperties.loadFromFile(propertiesFile); return props; } catch (Exception e) { throw new IllegalStateException("Cannot determine current deployment", e); } } /** * Returns information about the deployment with the given ID. * * @param id identifies which deployment the caller wants information on * @return props with deployment information * @throws Exception if could not find the deployment information */ public DeploymentProperties getDeploymentProperties(int deploymentId) throws Exception { try { File deploymentSubdir = getDeploymentMetadataDirectory(deploymentId); File propertiesFile = new File(deploymentSubdir, DEPLOYMENT_FILE); DeploymentProperties props = DeploymentProperties.loadFromFile(propertiesFile); return props; } catch (Exception e) { throw new IllegalStateException("Cannot get deployment info", e); } } /** * Returns information about the previous deployment given a specific deployment ID. * * @param id identifies which deployment whose previous deployment props are to be returned * @return props with previous deployment information, or <code>null</code> if there was no previous deployment * @throws Exception if could not load the previous deployment information or the given deployment ID is invalid */ public DeploymentProperties getPreviousDeploymentProperties(int deploymentId) throws Exception { try { File deploymentSubdir = getDeploymentMetadataDirectory(deploymentId); File propertiesFile = new File(deploymentSubdir, PREVIOUS_DEPLOYMENT_FILE); DeploymentProperties props = null; if (propertiesFile.exists()) { props = DeploymentProperties.loadFromFile(propertiesFile); } return props; } catch (Exception e) { throw new IllegalStateException("Cannot get deployment info", e); } } /** * Returns the files and their hashcodes for the current deployment. This does not * perform live computations of the file hashcodes, instead it reads the data out of * the metadata file from a previous computation when the files were initially * deployed. In other words, if someone has recently changed a file after it was * initially deployed, the returned map will not know about it. * * @return map of files/hashcodes when the current deployment was initially deployed * * @throws Exception */ public FileHashcodeMap getCurrentDeploymentFileHashcodes() throws Exception { try { File dir = getCurrentDeploymentMetadataDirectory(); File hashcodesFile = new File(dir, HASHCODES_FILE); FileHashcodeMap map = FileHashcodeMap.loadFromFile(hashcodesFile); return map; } catch (Exception e) { throw new IllegalStateException("Cannot determine current deployment", e); } } /** * Returns the files and their hashcodes for the given deployment. This does not * perform live computations of the file hashcodes, instead it reads the data out of * the metadata file for the given deployment. * * @param deploymentId the ID of the deployment whose files/hashcodes are to be returned * @return map of files/hashcodes when the current deployment was initially deployed * * @throws Exception */ public FileHashcodeMap getDeploymentFileHashcodes(int deploymentId) throws Exception { try { File dir = getDeploymentMetadataDirectory(deploymentId); File hashcodesFile = new File(dir, HASHCODES_FILE); FileHashcodeMap map = FileHashcodeMap.loadFromFile(hashcodesFile); return map; } catch (Exception e) { throw new IllegalStateException("Cannot determine deployment file hashcodes for [" + deploymentId + "]", e); } } /** * Returns a metadata directory that is appropriate to place backup files for the deployment. * Files placed here are files found in the deployment directory that needed to be backed up * before being overwritten or deleted. * * @param deploymentId the ID of the deployment whose backup directory is to be returned * @return backup directory for the deployment */ public File getDeploymentBackupDirectory(int deploymentId) throws Exception { try { File dir = getDeploymentMetadataDirectory(deploymentId); File backupDir = new File(dir, BACKUP_DIR); if (!backupDir.isDirectory()) { if (!backupDir.exists()) { if (!backupDir.mkdirs()) { throw new IllegalStateException("Failed to create backup directory: " + backupDir); } } else { throw new IllegalStateException("backup is a file but should be a directory: " + backupDir); } } return backupDir; } catch (Exception e) { throw new IllegalStateException("Cannot determine deployment backup dir for [" + deploymentId + "]", e); } } /** * Returns a metadata directory that is appropriate to place backup files for the deployment. * Files placed here are files found in external directories (i.e. outside the deployment directory) * that needed to be backed up before being overwritten or deleted. * * @param deploymentId the ID of the deployment whose backup directory is to be returned * @return backup directory for the deployment's external files */ public File getDeploymentExternalBackupDirectory(int deploymentId) throws Exception { try { File dir = getDeploymentMetadataDirectory(deploymentId); File backupDir = new File(dir, EXT_BACKUP_DIR); if (!backupDir.isDirectory()) { if (!backupDir.exists()) { if (!backupDir.mkdirs()) { throw new IllegalStateException("Failed to create external backup directory: " + backupDir); } } else { throw new IllegalStateException("ext backup is a file but should be a directory: " + backupDir); } } return backupDir; } catch (Exception e) { throw new IllegalStateException("Cannot determine deployment external backup dir for [" + deploymentId + "]", e); } } /** * Returns all the metadata directories that contain backup files for external directories * (i.e. outside the deployment directory). The returned map has driver letter root directories * as their keys (e.g. "C:\"); the values are the backup directories that contain files that were stored on their * associated drive. * * Obviously, this method is only appropriate to be called on Windows platforms. If this method is * called while the Java VM is running in a non-Windows environment, <code>null</code> is returned. * * @param deploymentId the ID of the deployment whose backup directories are to be returned * @return backup directories for the deployment's external files, keyed on the drive letter root directory */ public Map<String, File> getDeploymentExternalBackupDirectoriesForWindows(int deploymentId) throws Exception { boolean isWindows = (File.separatorChar == '\\'); if (!isWindows) { return null; } try { // find all the direct children directories of the main external backup directory; if any of them // have the name "_X" where "X" is a letter from A to Z, that denotes a drive letter and // that directory contains all the backup files for that drive. Map<String, File> backupDirs = new HashMap<String, File>(); Pattern driveLetterPattern = Pattern.compile("_([a-zA-Z])"); File dir = getDeploymentExternalBackupDirectory(deploymentId); File[] children = dir.listFiles(); if (children != null) { for (File child : children) { if (child.isDirectory()) { String dirName = child.getName(); Matcher m = driveLetterPattern.matcher(dirName); if (m.matches()) { String driveLetter = m.group(1).toUpperCase(); backupDirs.put(driveLetter + ":\\", child); } } } } return backupDirs; } catch (Exception e) { throw new IllegalStateException("Cannot determine deployment external backup dir for [" + deploymentId + "]", e); } } /** * Given a Windows drive letter, this will return the name of the external backup directory * where all backups for external files should be copied to. This method involves no relative or * absolute paths, this only returns the short name of the directory. The returned name should * be appended to the end of a {@link #getDeploymentExternalBackupDirectory(int) external backup directory} * to obtain the full path. * * @param driveLetter * @return the name of the directory that should be used when storing external files for backup. */ public String getExternalBackupDirectoryNameForWindows(String driveLetter) { return "_" + driveLetter.toUpperCase(); } /** * Call this when you already know the properties, and files/hashcodes for the current live deployment. * This method will also mark this initialized, live deployment as the "current" deployment. * In addition, if <code>rememberPrevious</code> is <code>true</code>, this will take the previous * deployment information and backup that data - pass <code>false</code> if you merely want to update * the current deployment properties without affecting anything else, specifically the backed up * previous deployment data. * * @param deploymentProps identifies the deployment information for the live deployment * @param fileHashcodeMap the files and their hashcodes of the current live deployment * @param rememberPrevious if <code>true</code>, this will create a backup of the previous deployment data * @throws Exception if failed to write out the necessary metadata about the given live data information */ public void setCurrentDeployment(DeploymentProperties deploymentProps, FileHashcodeMap fileHashcodeMap, boolean rememberPrevious) throws Exception { // determine where we need to put the metadata and create its empty directory // Don't worry if the directory already exists, we probably backed up files there ahead of time. getMetadataDirectory().mkdirs(); File deploymentMetadataDir = getDeploymentMetadataDirectory(deploymentProps.getDeploymentId()); deploymentMetadataDir.mkdirs(); if (!deploymentMetadataDir.isDirectory()) { throw new Exception("Failed to create deployment metadata directory: " + deploymentMetadataDir); } // store the deployment properties so we know what deployment this metadata belongs to deploymentProps.saveToFile(new File(deploymentMetadataDir, DEPLOYMENT_FILE)); // write the files/hashcodes data to the proper file fileHashcodeMap.storeToFile(new File(deploymentMetadataDir, HASHCODES_FILE)); // since we are being told this is the live deployment, this deployment should be considered the current one File currentDeploymentPropertiesFile = new File(getMetadataDirectory(), CURRENT_DEPLOYMENT_FILE); if (rememberPrevious) { File previousDeploymentPropertiesFile = new File(deploymentMetadataDir, PREVIOUS_DEPLOYMENT_FILE); if (currentDeploymentPropertiesFile.exists()) { FileUtil.copyFile(currentDeploymentPropertiesFile, previousDeploymentPropertiesFile); } } deploymentProps.saveToFile(currentDeploymentPropertiesFile); return; } /** * Looks at the live deployment and takes a snapshot of it and stores its metadata in its appropriate * deployment metadata directory. The "live deployment" means the actual files in the root directory. * This method will also mark the live deployment as the "current" deployment. * * @param deploymentProps identifies the deployment information for the live data * @param ignoreRegex the live files/directories to ignore * @param ignored a set that will contain those files/directories that were ignored while scanning the deployment * @return the map of the files/hashcodes * @throws Exception if failed to calculate and store the metadata */ public FileHashcodeMap snapshotLiveDeployment(DeploymentProperties deploymentProps, Pattern ignoreRegex, Set<String> ignored) throws Exception { // calculate the hashcodes from the live files and write the data to the proper file FileHashcodeMap map = FileHashcodeMap.generateFileHashcodeMap(getRootDirectory(), ignoreRegex, ignored); setCurrentDeployment(deploymentProps, map, true); return map; } private File getCurrentDeploymentMetadataDirectory() throws Exception { DeploymentProperties currentDeploymentProps = getCurrentDeploymentProperties(); return getDeploymentMetadataDirectory(currentDeploymentProps.getDeploymentId()); } private File getDeploymentMetadataDirectory(int deploymentId) throws Exception { File metaDir = getMetadataDirectoryOnlyIfExists(); File deploymentSubdir = new File(metaDir, Integer.toString(deploymentId)); return deploymentSubdir; } }