/* * 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.plugins.jbossas.util; 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.Set; import java.util.Stack; import java.util.jar.Attributes; import java.util.jar.Manifest; 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.PackageDetailsKey; 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 */ public class FileContentDelegate { private static final String RHQ_SHA_256 = "RHQ-Sha256"; private static final String MANIFEST_RELATIVE_PATH = "META-INF/MANIFEST.MF"; private Log log = LogFactory.getLog(FileContentDelegate.class); protected File directory; private String fileEnding; private String packageTypeName; public FileContentDelegate(File directory, String fileEnding, String packageTypeName) { this.directory = directory; this.fileEnding = fileEnding; this.packageTypeName = packageTypeName; } public String getFileEnding() { return fileEnding; } public String getPackageTypeName() { return packageTypeName; } public File getDirectory() { return directory; } /** * Creates a new package described by the specified details. The destination of the content in the provided * file will be determined by the package name. * * @param details describes the package being created * @param sourceContentFile content file to be written for the package. * @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 sourceContentFile, boolean unzip, boolean createBackup) { File destinationContentFile = getPath(details); try { if (createBackup) { moveToBackup(destinationContentFile, ".bak"); } if (unzip) { ZipUtil.unzipFile(sourceContentFile, destinationContentFile); String shaString = new MessageDigestGenerator(MessageDigestGenerator.SHA_256) .calcDigestString(sourceContentFile); writeSHAToManifest(destinationContentFile, shaString); } else { FileUtil.copyFile(sourceContentFile, destinationContentFile); } details.setFileName(destinationContentFile.getPath()); } catch (IOException e) { throw new RuntimeException("Error creating artifact from details: " + destinationContentFile, e); } } /** * 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 * @deprecated Method deprecated because of SHA256 computations. Method added only for backwards compatibility with SOA-P plugin. * Replaced by {@link #createContent(PackageDetails, File, boolean, boolean)} */ @Deprecated public void createContent(PackageDetails details, InputStream content, boolean unzip, boolean createBackup) { File destinationContentFile = getPath(details); try { if (createBackup) { moveToBackup(destinationContentFile, ".bak"); } if (unzip) { ZipUtil.unzipFile(content, destinationContentFile); computeAndSaveSHA(destinationContentFile); } else { FileUtil.writeFile(content, destinationContentFile); } details.setFileName(destinationContentFile.getPath()); } catch (IOException e) { throw new RuntimeException("Error creating artifact from details: " + destinationContentFile, e); } } /** * Try to move the passed contentFile to a backup named contentFile + suffix * @param contentFile File object pointing to the original file * @param suffix the suffix to tack on */ private void moveToBackup(File contentFile, String suffix) { File backupFile = new File(contentFile.getAbsolutePath() + suffix); if (backupFile.exists()) { // remove if (!backupFile.delete()) log.warn("Removing of old backup file " + backupFile + " failed "); } if (!contentFile.renameTo(backupFile)) { log.warn("Moving " + contentFile + " to backup " + backupFile + " failed"); } } public File getPath(PackageDetails details) { /* 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. * * jdobies, Sep 20, 2007 */ PackageDetailsKey key = details.getKey(); String fileName = key.getName(); int lastPathStart = fileName.lastIndexOf(File.separatorChar); if (lastPathStart > -1) { fileName = fileName.substring(lastPathStart + 1); } if (!fileName.endsWith(fileEnding)) { fileName = fileName + fileEnding; } return new File(this.directory, fileName); } /** * 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); } } public Set<ResourcePackageDetails> discoverDeployedPackages() { /* * This is a stub implementation, you need to implement a * discovery for artifacts of your particular content type */ return null; } /** * Retrieves the SHA256 for a deployed application. * 1) If the app is exploded then return RHQ-Sha256 manifest attribute. * 1.1) If RHQ-Sha256 is missing then compute it, save it and return the result. * 2) If the app is an archive then compute SHA256 on fly and return it. * * @param deploymentFile deployment file * @return */ public String getSHA(File deploymentFile) { String sha = null; try { if (deploymentFile.isDirectory()) { File manifestFile = new File(deploymentFile.getAbsolutePath(), MANIFEST_RELATIVE_PATH); if (manifestFile.exists()) { InputStream manifestStream = new FileInputStream(manifestFile); Manifest manifest = null; try { manifest = new Manifest(manifestStream); sha = manifest.getMainAttributes().getValue(RHQ_SHA_256); } finally { manifestStream.close(); } } if (sha == null || sha.trim().isEmpty()) { sha = computeAndSaveSHA(deploymentFile); } } else { sha = new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(deploymentFile); } } catch (IOException ex) { throw new RuntimeException("Problem calculating digest of package [" + deploymentFile.getPath() + "].", ex); } return sha; } /** * Compute SHA256 for the content of an exploded war deployment. This method should be used to * compute the SHA256 for content deployed outside RHQ or for the initial content delivered * with the server. * * @param deploymentDirectory app deployment folder * @return */ private String computeAndSaveSHA(File deploymentDirectory) { String sha = null; 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(); } } } } } sha = messageDigest.getDigestString(); writeSHAToManifest(deploymentDirectory, sha); } } catch (IOException e) { throw new RuntimeException("Error creating artifact for contentFile: " + deploymentDirectory, e); } return sha; } /** * Write the SHA256 to the manifest using the RHQ-Sha256 attribute tag. * * @param deploymentFolder app deployment folder * @param sha SHA256 * @throws IOException */ private void writeSHAToManifest(File deploymentFolder, String sha) throws IOException { File manifestFile = new File(deploymentFolder, MANIFEST_RELATIVE_PATH); Manifest manifest; if (manifestFile.exists()) { FileInputStream inputStream = new FileInputStream(manifestFile); try { manifest = new Manifest(inputStream); } finally { inputStream.close(); } } else { manifest = new Manifest(); manifestFile.getParentFile().mkdirs(); manifestFile.createNewFile(); } Attributes attribs = manifest.getMainAttributes(); //The main section of the manifest file does not get saved if both of //these two attributes are missing. Please see Attributes implementation. if (!attribs.containsKey(Attributes.Name.MANIFEST_VERSION.toString()) && !attribs.containsKey(Attributes.Name.SIGNATURE_VERSION.toString())) { attribs.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0"); } attribs.putValue(RHQ_SHA_256, sha); FileOutputStream outputStream = new FileOutputStream(manifestFile); try { manifest.write(outputStream); } finally { outputStream.close(); } } }