/* * 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, 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.version; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Access to the persistent storage of package versions. This class provides the ability to save and load the versions * to disk, along with the basic retrieval and set calls for the versions themselves. * * @author Jason Dobies */ public class PackageVersions { /** * Name of the file used to persist the version data. */ public static final String FILENAME = "package-versions.dat"; /** * Name of the file used in JON 2.1 and earlier to persist the version data. */ // TODO: check for this public static final String LEGACY_FILENAME = "application-versions.dat"; /** * Version data storage. This is loaded through the {@link #loadFromDisk()} method which must be called prior * to any version manipulation calls. */ private static PackageVersionData data; private static ReentrantReadWriteLock dataLock = new ReentrantReadWriteLock(true); /** * Plugin in which this instance lives. This will be used in the naming of the persistent store, thus allowing * plugins that share application component code to not run into issues about file conflicts. */ private String pluginName; /** * Data directory of the agent. This is where the versions will be persisted and is provided by the plugin * container. */ private String dataDirectory; private final Log log = LogFactory.getLog(this.getClass()); /** * Creates a new entry point to the persisted version data. Multiple instances of this class may be instantiated; * the storage of the data itself does not live in any particular instance of this class. This call will not * load the data from disk, it must explicitly be done by the user through a call to {@link #loadFromDisk()}. * * @param pluginName plugin loading the version data * @param dataDirectory directory into which to persist the versions */ public PackageVersions(String pluginName, String dataDirectory) { this.pluginName = pluginName; this.dataDirectory = dataDirectory; } /** * Loads the last known application (package) versions for the plugin this instance is scoped to. This method will * absorb and log any errors that occur while saving; this call represents a best-effort to save the values. If * a data store for the versions has already been found, this method will not reload it from disk. Thus it is * safe to make this call multiple times not only per instance, but per class loader, as the data is stored as * a static variable across all instances of this class. */ public void loadFromDisk() { WriteLock lock = dataLock.writeLock(); lock.lock(); try { ObjectInputStream ois = null; try { // All instances of this class will use the same store. If one loaded it already, there's nothing to do. if (data != null) { return; } log.debug("Loading package versions from storage for plugin [" + pluginName + "]"); File file = new File(dataDirectory, FILENAME); // If there's no package-versions.dat, check for the old filename, application-versions.dat. if (!file.exists()) { File legacyFile = new File(dataDirectory, LEGACY_FILENAME); if (legacyFile.exists()) { log.info("Found legacy package versions data file [" + legacyFile + "] - renaming to [" + file + "]..."); legacyFile.renameTo(file); } } // There will be no data file after a clean or on the first run, so create an empty one if (!file.exists()) { log.debug("No package versions found for plugin [" + pluginName + "]. This will be the case if the Agent was cleaned or on the first run."); data = new PackageVersionData(); } else { FileInputStream fis = new FileInputStream(file); ois = new ObjectInputStream(fis); data = (PackageVersionData) ois.readObject(); } } catch (Exception e) { log.error("Could not load persistent version data from disk for plugin [" + pluginName + "]. Package version values will be reset.", e); data = new PackageVersionData(); } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { log.error("Error closing input stream for persistent version data for plugin [" + pluginName + "]", e); } } } } finally { lock.unlock(); } } /** * Saves the current state of the application (package) versions to disk. The values will be saved scoped to the * plugin specified in the constructor. This method will absorb and log any errors that occur while saving; * this call represents a best-effort to save the values. */ public void saveToDisk() { WriteLock lock = dataLock.writeLock(); lock.lock(); try { if (data == null) { throw new IllegalStateException("Data has not been loaded prior to saving for plugin [" + pluginName + "]"); } ObjectOutputStream oos = null; try { File file = new File(dataDirectory, FILENAME); FileOutputStream fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); oos.writeObject(data); } catch (Exception e) { log.error("Error saving persistent version data for plugin [" + pluginName + "]. Package versions may not be maintained between agent restarts.", e); } finally { if (oos != null) { try { oos.close(); } catch (IOException e) { log.error("Error closing output stream for plugin [" + pluginName + "].", e); } } } } finally { lock.unlock(); } } /** * Retrieves the version for the package identified by the supplied package key. This method will synchronize * against the data store, thus callers need not worry about handling calls across multiple discovery threads. * * @param packageKey identifies the package * * @return version of the package if it is known; <code>null</code> otherwise */ public String getVersion(String packageKey) { if (packageKey == null) { throw new IllegalArgumentException("packageKey cannot be null"); } String version; ReadLock lock = dataLock.readLock(); lock.lock(); try { checkLoaded(); version = data.getVersion(packageKey); } finally { lock.unlock(); } return version; } /** * For internal use only - Removes the static data object to simulate the first load in the system. This should * not be called by production code and is only for testing purposes. */ public void unload() { data = null; } /** * Updates the store with a new version for the package identified by the specified key. If there is an existing * version in the store, it will be overwritten. This method will synchronize on against the data store, thus * callers need not worry about handling calls across multiple deploy threads. * * @param packageKey identifies the package * @param version version of the package */ public void putVersion(String packageKey, String version) { if (packageKey == null) { throw new IllegalArgumentException("packageKey cannot be null"); } ReadLock lock = dataLock.readLock(); lock.lock(); try { checkLoaded(); data.setVersion(packageKey, version); } finally { lock.unlock(); } } /** * Called by the get/set version methods to ensure the caller has properly initialized the data storage. */ private void checkLoaded() { if (data == null) throw new IllegalStateException("Attempt to access package versions without loading the data. " + "Call loadFromDisk() before attempting these operations."); } }