/* * Jopr 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.content; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Comparator; import java.util.Properties; import java.util.Set; import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.content.PackageDetails; import org.rhq.core.domain.content.transfer.ResourcePackageDetails; import org.rhq.core.pluginapi.util.FileUtils; import org.rhq.core.util.MessageDigestGenerator; import org.rhq.core.util.ZipUtil; import org.rhq.core.util.file.FileUtil; /** * Delegate class used for manipulating artifacts in a JON plugin. * * @author Greg Hinkle * @author Jason Dobies * @author Stefan Negrea */ public class FileContentDelegate { private static final String RHQ_SHA_256 = "RHQ-Sha256"; private static final String SHA256_FILE = "application.sha256"; private final Log log = LogFactory.getLog(FileContentDelegate.class); private final String fileEnding; private final File directory; /** * Default constructor. */ public FileContentDelegate() { this.fileEnding = null; this.directory = null; } public FileContentDelegate(File directory, String fileEnding) { this.directory = directory; this.fileEnding = fileEnding; } public String getFileEnding() { return fileEnding; } public File getDirectory() { return directory; } /** * Creates a new package described by the specified details. The destination of the content in the provided input * stream will be determined by the package name. * * @param details describes the package being created * @param content content to be written for the package. NOTE this Stream will be closed by this method. * @param unzip if <code>true</code>, the content stream will be treated like a ZIP file and be unzipped as * it is written, using the package name as the base directory; if <code>false</code> the * @param createBackup If <code>true</code>, the original file will be backed up to file.bak */ public void createContent(PackageDetails details, File content, boolean unzip) { File destination = getPath(details); try { if (unzip) { ZipUtil.unzipFile(content, destination); } else { FileUtil.copyFile(content, destination); } details.setFileName(destination.getPath()); } catch (IOException e) { throw new RuntimeException("Error creating artifact from details: " + destination, e); } } /** * Returns a stream from which the content of the specified package can be read. * * @param details package being loaded * * @return buffered input stream containing the contents of the package; will not be <code>null</code>, an * exception is thrown if the content cannot be loaded */ public InputStream getContent(PackageDetails details) { File contentFile = getPath(details); try { return new BufferedInputStream(new FileInputStream(contentFile)); } catch (FileNotFoundException e) { throw new RuntimeException("Package content not found for package " + contentFile, e); } } /** * Deletes the underlying file for the specified package. * * @param details package to delete */ public void deleteContent(PackageDetails details) { File contentFile = getPath(details); if (!contentFile.exists()) return; try { FileUtils.purge(contentFile, true); } catch (IOException e) { throw new RuntimeException("Failed to delete underlying file [" + contentFile + "] for " + details + ".", e); } } /** * This is a stub implementation, you need to implement a * discovery for artifacts of your particular content type. * * @return */ public Set<ResourcePackageDetails> discoverDeployedPackages() { throw new UnsupportedOperationException("This method is not implemented!"); } /** * Retrieves SHA256 for the deployment. If this is an exploded deployment * and SHA256 is missing from the data directory then compute the SHA256 * and save in the data directory. * * @param deployment deployment location * @param resourceId resource id * @param dataDirectory data directory * @return SHA256 of the package */ public String retrieveDeploymentSHA(File deployment, File resourceDataDirectory) { String sha = null; if (deployment.isDirectory()) { File propertiesFile = new File(resourceDataDirectory, SHA256_FILE); if (propertiesFile.exists()) { FileInputStream propertiesInputStream = null; try { propertiesInputStream = new FileInputStream(propertiesFile); Properties prop = new Properties(); prop.load(propertiesInputStream); sha = prop.getProperty(RHQ_SHA_256); } catch (IOException e) { throw new RuntimeException("Error retrieving artifact's SHA256.", e); } finally { if (propertiesInputStream != null) { try { propertiesInputStream.close(); } catch (IOException e) { log.error("Failed to close input stream.", e); } } } } if (sha == null) { sha = this.saveDeploymentSHA(deployment, resourceDataDirectory); } } else { sha = this.computeSHAForArchivedContent(deployment); } return sha; } /** * Save the SHA256 for a deployment with knowledge of the original archived content. * If the deployment is archived, do not save anything. Just return the SHA256 * of the content. * * @param originalArchive * @param deployment * @param resourceId * @param dataDirectory * @return */ public String saveDeploymentSHA(File originalArchive, File deployment, File resourceDataDirectory) { String sha = null; if (deployment.isDirectory()) { sha = this.computeSHAForArchivedContent(originalArchive); this.saveDeploymentSHA(sha, resourceDataDirectory); } else { sha = this.computeSHAForArchivedContent(deployment); } return sha; } /** * Save the SHA256 for a deployment without knowing its original archived content. * If the deployment is archived, do not save anything. Just return the SHA256 * of the content. * * @param deployment deployment * @param resourceId resource id * @param dataDirectory data directory * @return the saved SHA256 */ public String saveDeploymentSHA(File deployment, File resourceDataDirectory) { String sha = null; if (deployment.isDirectory()) { sha = this.computeSHAForExplodedContent(deployment); this.saveDeploymentSHA(sha, resourceDataDirectory); } else { sha = this.computeSHAForArchivedContent(deployment); } return sha; } /** * Persist the SHA256 value in data directory in a properties file. * File name format is "[Uuid of Resource].sha" * * @param sha SHA256 of the content * @param resourceId resource Uuid * @param dataDirectory data directory */ private void saveDeploymentSHA(String sha, File resourceDataDirectory) { Properties prop = new Properties(); prop.setProperty(RHQ_SHA_256, sha); File propertiesFile = new File(resourceDataDirectory, SHA256_FILE); FileOutputStream propertiesOutputStream = null; try { propertiesOutputStream = new FileOutputStream(propertiesFile); prop.store(propertiesOutputStream, null); } catch (FileNotFoundException e) { throw new RuntimeException("Error saving artifact's SHA256.", e); } catch (IOException e) { throw new RuntimeException("Error saving artifact's SHA256.", e); } finally { if (propertiesOutputStream != null) { try { propertiesOutputStream.close(); } catch (IOException e) { throw new RuntimeException("Error saving artifact's SHA256.", e); } } } } /** * Computes SHA256 for an archived content. * * @param contentFile content archive * @return SHA256 of the archive */ private String computeSHAForArchivedContent(File contentFile) { if (!contentFile.isDirectory()) { try { MessageDigestGenerator messageDigest = new MessageDigestGenerator(MessageDigestGenerator.SHA_256); return messageDigest.calcDigestString(contentFile); } catch (Exception ex) { log.error("Not able to compute SHA256 for " + contentFile.getPath() + " ."); } } return null; } /** * Computes SHA256 for exploded content as the aggregate SHA256 * of all the files in the deployment * * @param deploymentDirectory deployment directory * @return SHA256 of the content */ private String computeSHAForExplodedContent(File deploymentDirectory) { try { if (deploymentDirectory.isDirectory()) { MessageDigestGenerator messageDigest = new MessageDigestGenerator(MessageDigestGenerator.SHA_256); Stack<File> unvisitedFolders = new Stack<File>(); unvisitedFolders.add(deploymentDirectory); while (!unvisitedFolders.empty()) { File[] files = unvisitedFolders.pop().listFiles(); Arrays.sort(files, new Comparator<File>() { public int compare(File f1, File f2) { try { return f1.getCanonicalPath().compareTo(f2.getCanonicalPath()); } catch (IOException e) { //do nothing if the sort fails at this point } return 0; } }); for (File file : files) { if (file.isDirectory()) { unvisitedFolders.add(file); } else { FileInputStream inputStream = null; try { inputStream = new FileInputStream(file); messageDigest.add(inputStream); } finally { if (inputStream != null) { inputStream.close(); } } } } } return messageDigest.getDigestString(); } } catch (IOException e) { throw new RuntimeException("Error creating artifact for contentFile: " + deploymentDirectory, e); } return null; } /** * JBNADM-2022 - It still needs to be determined if it is the responsibility of the plugin container or the * plugin to be concerned with path information in the package name. For now, it's the plugin's * responsibility. We strip out the path information to keep control of where the JARs are * deployed to. Note: when we add support for more package types, we'll need to refactor this * out on a package type basis. * @param details package details * @return destination path */ private File getPath(PackageDetails details) { String fileName = details.getKey().getName(); int lastPathStart = fileName.lastIndexOf(File.separatorChar); if (lastPathStart > -1) { fileName = fileName.substring(lastPathStart + 1); } if (this.fileEnding != null && !fileName.endsWith(this.fileEnding)) { fileName = fileName + this.fileEnding; } return new File(this.directory, fileName); } }