/* * 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 as published by * the Free Software Foundation version 2 of the License. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.plugins.disk; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertyMap; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.util.MessageDigestGenerator; import org.rhq.core.util.file.ContentFileInfo; import org.rhq.core.util.file.ContentFileInfoFactory; import org.rhq.enterprise.server.plugin.pc.content.ContentProvider; import org.rhq.enterprise.server.plugin.pc.content.ContentProviderPackageDetails; import org.rhq.enterprise.server.plugin.pc.content.ContentProviderPackageDetailsKey; import org.rhq.enterprise.server.plugin.pc.content.PackageSource; import org.rhq.enterprise.server.plugin.pc.content.PackageSyncReport; import org.rhq.enterprise.server.plugin.pc.content.RepoDetails; import org.rhq.enterprise.server.plugin.pc.content.RepoImportReport; import org.rhq.enterprise.server.plugin.pc.content.RepoSource; import org.rhq.enterprise.server.plugin.pc.content.SyncException; import org.rhq.enterprise.server.plugin.pc.content.SyncProgressWeight; /** * This is the most basic <i>reference</i> implementation of a content source. It provides primative package * synchronization with file-system based source. It is anticipated that more content aware subclasses will provide more * useful functionality. * * @author jortel * @author John Mazzitelli */ public class DiskSource implements ContentProvider, PackageSource, RepoSource { /** * The root path (directory) from which to synchronize content. */ private File rootDirectory; /** * We cache the root dir's absolute path as a string since we need it often. */ private String rootDirectoryAbsolutePath; /** * Map of all supported package types keyed on filename filter regex's that define * which files match to which package types. */ private Map<String, SupportedPackageType> supportedPackageTypes; /** * Configuration for this instance of the plugin. */ private Configuration configuration; /** * Indicates if the repo source functionality of this instance is enabled or disabled. */ private boolean isRepoSource; /** * Indicates if the package source functionality of this instance is enabled or disabled. */ private boolean isPackageSource; public void initialize(Configuration configuration) throws Exception { this.configuration = configuration; isPackageSource = ((PropertySimple) configuration.get("packageSourceEnabled")).getBooleanValue(); isRepoSource = ((PropertySimple) configuration.get("repoSourceEnabled")).getBooleanValue(); initializePackageTypes(); String pathString = configuration.getSimpleValue("rootDirectory", null); setRootDirectory(new File(pathString)); testConnection(); } public void shutdown() { this.rootDirectory = null; this.rootDirectoryAbsolutePath = null; this.supportedPackageTypes = null; } public RepoImportReport importRepos() throws Exception { RepoImportReport report = new RepoImportReport(); if (!isRepoSource) { return report; } File directory = getRootDirectory(); generateRepoDetails(report, directory, null); return report; } public void synchronizePackages(String repoName, PackageSyncReport report, Collection<ContentProviderPackageDetails> existingPackages) throws SyncException, InterruptedException { if (!isPackageSource) { return; } // put all existing packages in a "to be deleted" list. As we sync, we will remove // packages from this list that still exist on the file system. Any leftover in the list // are packages that no longer exist on the file system and should be removed from the server inventory. List<ContentProviderPackageDetails> deletedPackages = new ArrayList<ContentProviderPackageDetails>(); deletedPackages.addAll(existingPackages); // sync now long before = System.currentTimeMillis(); syncPackages(report, repoName, deletedPackages, getRootDirectory()); long elapsed = System.currentTimeMillis() - before; // if there are packages that weren't found on the file system, tell server to remove them from inventory for (ContentProviderPackageDetails p : deletedPackages) { report.addDeletePackage(p); } report.setSummary("Synchronized [" + getRootDirectory() + "]. Elapsed time=[" + elapsed + "] ms"); } public void testConnection() throws Exception { File root = getRootDirectory(); if (!root.exists()) { throw new Exception("Disk source [" + root + "] does not exist"); } if (!root.canRead()) { throw new Exception("Not permitted to read disk source [" + root + "] "); } if (!root.isDirectory()) { throw new Exception("Disk source [" + root + "] is not a directory"); } } public InputStream getInputStream(String location) throws Exception { return new FileInputStream(new File(getRootDirectory(), location)); } protected File getRootDirectory() { return this.rootDirectory; } protected void setRootDirectory(File path) { this.rootDirectory = path; this.rootDirectoryAbsolutePath = this.rootDirectory.getAbsolutePath(); } protected Map<String, SupportedPackageType> getSupportedPackageTypes() { return this.supportedPackageTypes; } protected void setSupportedPackageTypes(Map<String, SupportedPackageType> supportedPackageTypes) { this.supportedPackageTypes = supportedPackageTypes; } /** * Recursive function that drills down into subdirectories and builds up the report * of packages for all files found. As files are found, their associated packages * are removed from <code>packages</code> if they exist - leaving only packages * remaining that do not exist on the file system. * * @param report the report that we are building up * @param packages existing packages not yet found on the file system but exist in server inventory * @param directory the directory (and its subdirectories) to scan * @throws Exception if the sync fails */ protected void syncPackages(PackageSyncReport report, String repoName, List<ContentProviderPackageDetails> packages, File directory) throws SyncException { for (File file : directory.listFiles()) { if (file.isDirectory()) { if (file.getName().equals(repoName)) { for (File filePackage : file.listFiles()) { if (!filePackage.isDirectory()) { ContentProviderPackageDetails details = createPackage(filePackage); if (details != null) { ContentProviderPackageDetails existing = findPackage(packages, details); if (existing == null) { report.addNewPackage(details); } else { packages.remove(existing); // it still exists, remove it from our list if (details.getFileCreatedDate().compareTo(existing.getFileCreatedDate()) > 0) { report.addUpdatedPackage(details); } } } else { // file does not match any filter and is therefore an unknown type - ignore it } } } break; } // Otherwise, keep searching recursively for a directory with the same name syncPackages(report, repoName, packages, file); } } } protected ContentProviderPackageDetails createPackage(File file) throws SyncException { SupportedPackageType supportedPackageType = determinePackageType(file); if (supportedPackageType == null) { return null; // we can't handle this file - it is an unknown/unsupported package type } ContentFileInfo fileInfo = ContentFileInfoFactory.createContentFileInfo(file); String sha256; try { sha256 = new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(file); } catch (IOException e) { throw new SyncException("Error digesting file", e); } String name = file.getName(); String version = "[sha256=" + sha256 + "]"; String displayVersion = fileInfo.getVersion(null); String packageTypeName = supportedPackageType.packageTypeName; String architectureName = supportedPackageType.architectureName; String resourceTypeName = supportedPackageType.resourceTypeName; String resourceTypePluginName = supportedPackageType.resourceTypePluginName; ContentProviderPackageDetailsKey key = new ContentProviderPackageDetailsKey(name, version, packageTypeName, architectureName, resourceTypeName, resourceTypePluginName); ContentProviderPackageDetails pkg = new ContentProviderPackageDetails(key); pkg.setDisplayName(name); pkg.setFileName(name); pkg.setFileCreatedDate(file.lastModified()); pkg.setFileSize(file.length()); pkg.setSHA256(sha256); pkg.setDisplayVersion(displayVersion); pkg.setLocation(getRelativePath(file)); pkg.setShortDescription(fileInfo.getDescription(null)); return pkg; } protected ContentProviderPackageDetails findPackage(List<ContentProviderPackageDetails> packages, ContentProviderPackageDetails pkg) { for (ContentProviderPackageDetails p : packages) { if (p.equals(pkg)) { return p; } } return null; } protected String getRelativePath(File file) { String relativePath; String fileAbsolutePath = file.getAbsolutePath(); int idx = fileAbsolutePath.indexOf(this.rootDirectoryAbsolutePath); if (idx > -1) { // this should always be the case, in fact, it should always be 0 relativePath = fileAbsolutePath.substring(idx + this.rootDirectoryAbsolutePath.length()); if (relativePath.startsWith(File.separator)) { relativePath = relativePath.substring(1); } } else { relativePath = fileAbsolutePath; // this should never happen, but, just in case, default to the full path } return relativePath; } protected void initializePackageTypes() { Map<String, SupportedPackageType> supportedPackageTypes = new HashMap<String, SupportedPackageType>(); /* TODO: THE UI CURRENTLY DOES NOT SUPPORT ADDING/EDITING LIST OF MAPS, SO WE CAN ONLY SUPPORT ONE PACKAGE TYPE * SO FOR NOW, JUST SUPPORT A FLAT SET OF SIMPLE PROPS - WE WILL RE-ENABLE THIS CODE LATER */ if (false) { // All of these properties must exist, any nulls should trigger runtime exceptions which is what we want // because if the configuration is bad, this content source should not initialize. List<Property> packageTypesList = configuration.getList("packageTypes").getList(); for (Property property : packageTypesList) { PropertyMap pkgType = (PropertyMap) property; SupportedPackageType supportedPackageType = new SupportedPackageType(); supportedPackageType.packageTypeName = pkgType.getSimpleValue("packageTypeName", null); supportedPackageType.architectureName = pkgType.getSimpleValue("architectureName", null); supportedPackageType.resourceTypeName = pkgType.getSimpleValue("resourceTypeName", null); supportedPackageType.resourceTypePluginName = pkgType.getSimpleValue("resourceTypePluginName", null); String filenameFilter = pkgType.getSimpleValue("filenameFilter", null); supportedPackageTypes.put(filenameFilter, supportedPackageType); } } else { /* THIS CODE IS THE FLAT SET OF PROPS - USE UNTIL WE CAN EDIT MAPS AT WHICH TIME DELETE THIS ELSE CLAUSE */ SupportedPackageType supportedPackageType = new SupportedPackageType(); supportedPackageType.packageTypeName = configuration.getSimpleValue("packageTypeName", null); supportedPackageType.architectureName = configuration.getSimpleValue("architectureName", null); String resourceAndPlugin = configuration.getSimpleValue("resourceType", null); String resourceType = resourceAndPlugin.substring((resourceAndPlugin.indexOf('-') + 1)); String pluginType = resourceAndPlugin.substring(0, resourceAndPlugin.indexOf('-')); supportedPackageType.resourceTypeName = resourceType; supportedPackageType.resourceTypePluginName = pluginType; String filenameFilter = configuration.getSimpleValue("filenameFilter", null); supportedPackageTypes.put(filenameFilter, supportedPackageType); } setSupportedPackageTypes(supportedPackageTypes); return; } protected SupportedPackageType determinePackageType(File file) { String absolutePath = file.getAbsolutePath(); for (Map.Entry<String, SupportedPackageType> entry : getSupportedPackageTypes().entrySet()) { if (absolutePath.matches(entry.getKey())) { return entry.getValue(); } } return null; // the file doesn't match any known types for this content source } protected void generateRepoDetails(RepoImportReport report, File base, String parentName) { for (File file : base.listFiles()) { if (file.isDirectory()) { // Add this as a new repo RepoDetails repo = new RepoDetails(file.getName(), parentName); report.addRepo(repo); // Check to see if there are any child repos generateRepoDetails(report, file, repo.getName()); } } } protected class SupportedPackageType { public String packageTypeName; public String architectureName; public String resourceTypeName; public String resourceTypePluginName; } public SyncProgressWeight getSyncProgressWeight() { return new SyncProgressWeight(10, 1, 0, 0, 0); } }