/*
* RHQ Management Platform
* Copyright (C) 2005-2014 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.Pattern;
import org.rhq.core.template.TemplateEngine;
import org.rhq.core.util.file.FileUtil;
/**
* Data that describes a particular deployment. In effect, this provides the
* data needed to fully deploy something.
*
* @author John Mazzitelli
*/
public class DeploymentData {
// TODO: After removing the deprecated constructors we should go back to making all of these "final", and
// remove the init() method in favor of constructor chaining.
private DeploymentProperties deploymentProps;
private Map<File, File> zipFiles;
private Map<File, File> rawFiles;
private File destinationDir;
private File sourceDir;
private Map<File, Pattern> zipEntriesToRealizeRegex;
private Set<File> rawFilesToRealize;
private TemplateEngine templateEngine;
private Pattern ignoreRegex;
private Map<File, Boolean> zipsExploded;
/**
* Equivalent to calling {@link #DeploymentData(DeploymentProperties, Map, Map, File, File, Map, Set, TemplateEngine, Pattern, boolean, Map)}
* with the zipFiles Map having as a keySet the Set of Files provided here, and null values for each Map entry. Also
* makes a call to to {@link DeploymentProperties#setManageRootDir(boolean)}.
*
* @deprecated use {@link #DeploymentData(DeploymentProperties, File, File, Map, Set, Map, Map, TemplateEngine, Pattern, Map)
*/
@Deprecated
public DeploymentData(DeploymentProperties deploymentProps, Set<File> zipFiles, Map<File, File> rawFiles,
File sourceDir, File destinationDir, Map<File, Pattern> zipEntriesToRealizeRegex, Set<File> rawFilesToRealize,
TemplateEngine templateEngine, Pattern ignoreRegex, boolean manageRootDir, Map<File, Boolean> zipsExploded) {
Map<File, File> zipFilesMap = (null == zipFiles) ? null : new HashMap<File, File>(zipFiles.size());
if (null != zipFiles) {
for (File zipFile : zipFiles) {
zipFilesMap.put(zipFile, null);
}
}
deploymentProps.setManageRootDir(manageRootDir);
init(deploymentProps, zipFilesMap, rawFiles, sourceDir, destinationDir, zipEntriesToRealizeRegex,
rawFilesToRealize, templateEngine, ignoreRegex, zipsExploded);
}
/**
* Equivalent to calling {@link #DeploymentData(DeploymentProperties, Map, Map, File, File, Map, Set, TemplateEngine, Pattern, Map)}
* with the zipFiles map having as a keySet the Set of Files provided here, and null values for each Map entry.
*
* @deprecated use {@link #DeploymentData(DeploymentProperties, File, File, Map, Set, Map, Map, TemplateEngine, Pattern, Map)
*
* @since 4.9.0
*/
@Deprecated
public DeploymentData(DeploymentProperties deploymentProps, Set<File> zipFiles, Map<File, File> rawFiles,
File sourceDir, File destinationDir, Map<File, Pattern> zipEntriesToRealizeRegex, Set<File> rawFilesToRealize,
TemplateEngine templateEngine, Pattern ignoreRegex, Map<File, Boolean> zipsExploded) {
Map<File, File> zipFilesMap = (null == zipFiles) ? null : new HashMap<File, File>(zipFiles.size());
if (null != zipFiles) {
for (File zipFile : zipFiles) {
zipFilesMap.put(zipFile, null);
}
}
init(deploymentProps, zipFilesMap, rawFiles, sourceDir, destinationDir, zipEntriesToRealizeRegex,
rawFilesToRealize, templateEngine, ignoreRegex, zipsExploded);
}
/**
* Constructor that prepares this object with the data that is necessary in order to deploy archive/file content
* a destination directory.
*
* @param deploymentProps metadata about this deployment
* @param destinationDir the root directory where the content is to be deployed
* @param sourceDir the root directory where the source files (zips and raw files) are located
* @param rawFiles files that are to be copied into the destination directory - the keys are the
* current
* locations of the files, the values are where the files should be copied (the
* values may be relative
* in which case they are relative to destDir and can have subdirectories and/or a
* different filename
* than what the file is named currently)
* @param rawFilesToRealize identifies the raw files that need to be realized; note that each item in this
* set
* must match a key to a <code>rawFiles</code> entry
* @param zipFiles the archives containing the content to be deployed - the keys are the current
* locations of the zip files and the values are the destinationDir entries. A null
* value in the Map entry indicates the zip is to be placed in destinationDir.
* @param zipEntriesToRealizeRegex the patterns of files (whose paths are relative to destDir) that
* must have replacement variables within them replaced with values
* obtained via the given template engine. The key is the name of the zip file
* that the regex must be applied to - in other words, the regex value is only
* applied
* to relative file names as found in their associated zip file.
* @param templateEngine if one or more filesToRealize are specified, this template engine is used to
* determine the values that should replace all replacement variables found in those
* files.
* @param ignoreRegex the files/directories to ignore when updating an existing deployment
* @param zipsExploded if not <code>null</code>, this is a map keyed on zip files whose values indicate
* if the zips should be exploded (true) or remain compressed after the deployment
* is finished (false). If a zip file is not found in this map, true is the
* default.
*
* @since 4.11
*/
public DeploymentData(DeploymentProperties deploymentProps, File sourceDir, File destinationDir,
Map<File, File> rawFiles, Set<File> rawFilesToRealize, Map<File, File> zipFiles,
Map<File, Pattern> zipEntriesToRealizeRegex, TemplateEngine templateEngine, Pattern ignoreRegex,
Map<File, Boolean> zipsExploded) {
init(deploymentProps, zipFiles, rawFiles, sourceDir, destinationDir, zipEntriesToRealizeRegex,
rawFilesToRealize, templateEngine, ignoreRegex, zipsExploded);
}
private void init(DeploymentProperties deploymentProps, Map<File, File> zipFiles, Map<File, File> rawFiles,
File sourceDir, File destinationDir, Map<File, Pattern> zipEntriesToRealizeRegex, Set<File> rawFilesToRealize,
TemplateEngine templateEngine, Pattern ignoreRegex, Map<File, Boolean> zipsExploded) {
if (deploymentProps == null) {
throw new IllegalArgumentException("deploymentProps == null");
}
if (destinationDir == null) {
throw new IllegalArgumentException("destinationDir == null");
}
if (sourceDir == null) {
throw new IllegalArgumentException("sourceDir == null");
}
if (zipFiles == null) {
zipFiles = new HashMap<File, File>(0);
}
if (rawFiles == null) {
rawFiles = new HashMap<File, File>(0);
}
if (zipsExploded == null) {
zipsExploded = new HashMap<File, Boolean>(0);
}
this.deploymentProps = deploymentProps;
this.zipFiles = zipFiles;
this.rawFiles = rawFiles;
//specifically do NOT resolve symlinks here. This must be the last thing one needs to do before deploying
//the files. The problem is that we use the destination dir as root for the paths of the individual files to
//lay down. If the destinationDir uses symlinks and the individual paths of the files were relative
// including ..'s, it could happen that the files would be laid down on a different place than expected.
//Consider this scenario:
//destinationDir = /opt/my/destination -> /tmp/deployments
//file = ../conf/some.properties
//One expects the file to end up in /opt/my/conf/some.properties
//but if we canonicalized the destination dir upfront, we'd end up with /tmp/conf/some.properties.
this.destinationDir = FileUtil.normalizePath(destinationDir.getAbsoluteFile());
this.sourceDir = sourceDir;
this.ignoreRegex = ignoreRegex;
this.zipsExploded = zipsExploded;
// if there is nothing to realize or we have no template engine to obtain replacement values, then we null things out
if (templateEngine == null || (zipEntriesToRealizeRegex == null && rawFilesToRealize == null)) {
this.zipEntriesToRealizeRegex = null;
this.rawFilesToRealize = null;
this.templateEngine = null;
} else {
this.zipEntriesToRealizeRegex = zipEntriesToRealizeRegex;
this.rawFilesToRealize = rawFilesToRealize;
this.templateEngine = templateEngine;
}
// We need to "normalize" all file paths (raw and zip) that have ".." in them to ensure everything works properly.
// Note that any pathnames that are relative but have ".." paths that end up taking the file
// above the destination directory need to be normalized and will end up being an absolute path
// (so all log messages will indicate the full absolute path and if the file needs to be backed up it will be
// backed up as if it was an external file that was specified with an absolute path). If the relative path has
// ".." but does not take the file above the destination directory it will simply have its ".."
// normalized out but will still be a relative path (relative to destination directory) (we can't make it absolute
// otherwise Deployer's update will run into errors while backing up and scanning for deleted files).
// See BZs 917085 and 917765.
for (Map.Entry<File, File> entry : this.rawFiles.entrySet()) {
File rawFile = entry.getValue();
if (null != rawFile) {
String rawFilePath = rawFile.getPath();
entry.setValue(getSafeFile(rawFile, rawFilePath));
}
}
for (Map.Entry<File, File> entry : this.zipFiles.entrySet()) {
File zipFile = entry.getValue();
if (null != zipFile) {
String zipFilePath = zipFile.getPath();
entry.setValue(getSafeFile(zipFile, zipFilePath));
}
}
}
private File getSafeFile(File file, String filePath) {
// finds "/.." or "../" in the string
boolean doubledot = filePath.replace('\\', '/').matches(".*((/\\.\\.)|(\\.\\./)).*");
boolean isWindows = (File.separatorChar == '\\');
if (doubledot) {
File fileToNormalize;
if (file.isAbsolute()) {
fileToNormalize = file;
} else {
if (isWindows) {
// of course, Windows has to make it enormously difficult to do this right...
// determine if the windows file relative path specified a drive (e.g. C:\foobar.txt)
StringBuilder filePathBuilder = new StringBuilder(filePath);
String fileDriveLetter = FileUtil.stripDriveLetter(filePathBuilder); // filePathBuilder now has drive letter stripped
// determine what, if any, drive letter is specified in the destination directory
StringBuilder destDirAbsPathBuilder = new StringBuilder(destinationDir.getAbsolutePath());
String destDirDriveLetter = FileUtil.stripDriveLetter(destDirAbsPathBuilder);
// figure out what the absolute, normalized path is for the file
if ((destDirDriveLetter == null || fileDriveLetter == null)
|| fileDriveLetter.equals(destDirDriveLetter)) {
fileToNormalize = new File(destinationDir, filePathBuilder.toString());
} else {
throw new IllegalArgumentException("Cannot normalize relative path [" + filePath
+ "]; its drive letter is different than the destination directory ["
+ destinationDir.getAbsolutePath() + "]");
}
} else {
fileToNormalize = new File(destinationDir, filePath);
}
}
fileToNormalize = getNormalizedFile(fileToNormalize);
if (isPathUnderBaseDir(destinationDir, fileToNormalize)) {
// we can keep file path relative, but we need to normalize out the ".." paths
String baseDir = destinationDir.getAbsolutePath();
String absFilePath = fileToNormalize.getAbsolutePath();
String relativePath = absFilePath.substring(baseDir.length() + 1); // should always return a valid path; if not, let it throw exception (which likely means there is a bug here)
return new File(relativePath);
} else {
// file path has ".." such that the file is really above destination dir - use an absolute, canonical path
return fileToNormalize;
}
} else if (isWindows && file != null && file.isAbsolute()) {
// make sure drive letter is normalized
return getNormalizedFile(file);
}
return file;
}
private static File getNormalizedFile(File fileToNormalize) {
return FileUtil.normalizePath(fileToNormalize);
}
public DeploymentProperties getDeploymentProps() {
return deploymentProps;
}
/**
* @return The Set of zipFiles
* @deprecated Use {@link #getZipFilesMap()} to fully support the destinationDir attribute on archive deployment.
*/
@Deprecated
public Set<File> getZipFiles() {
return zipFiles.keySet();
}
public Map<File, File> getZipFilesMap() {
return zipFiles;
}
public Map<File, File> getRawFiles() {
return rawFiles;
}
public File getDestinationDir() {
return destinationDir;
}
public File getSourceDir() {
return sourceDir;
}
public Map<File, Pattern> getZipEntriesToRealizeRegex() {
return zipEntriesToRealizeRegex;
}
public Set<File> getRawFilesToRealize() {
return rawFilesToRealize;
}
public TemplateEngine getTemplateEngine() {
return templateEngine;
}
public Pattern getIgnoreRegex() {
return ignoreRegex;
}
/**
* As of RHQ 4.9.0, this calls {@link #getDeploymentProps()}.{@link DeploymentProperties#getManageRootDir() getManageRootDir()}
*
* @deprecated use {@link #getDeploymentProps()}.{@link org.rhq.core.util.updater.DeploymentProperties#getDestinationCompliance() getDestinationCompliance()}.
*/
@Deprecated
public boolean isManageRootDir() {
return deploymentProps.getManageRootDir();
}
public Map<File, Boolean> getZipsExploded() {
return zipsExploded;
}
private boolean isPathUnderBaseDir(File base, File path) {
// this method assumes base and path are absolute and canonical
if (base == null) {
return false;
}
while (path != null) {
if (base.equals(path)) {
return true;
}
path = path.getParentFile();
}
return false;
}
}