/* * Copyright 2008 COMMSEN International * * This file is part of APropOS. * * APropOS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * APropOS 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with APropOS. If not, see <http://www.gnu.org/licenses/>. */ package com.commsen.apropos.core; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import com.thoughtworks.xstream.XStream; /** * The class provides static synchronized methods for common operations on {@link Property} and * {@link PropertyPackage} like add, delete, update, etc. It is also responsible for persisting data * after any change and loading data form storage on startup. * * @author Milen Dyankov * */ public class PropertiesManager { /** * A folder to persists data into */ private static final File storage = new File(SystemUtils.getUserHome(), ".apropos"); /** * The name of the file containing data */ private static final File dataFile = new File(storage, "data.xml"); /** * */ private static XStream xStream = new XStream(); /** * Map of all packages. Key is package name */ private static Map<String, PropertyPackage> allPackages = new HashMap<String, PropertyPackage>(); /** * The singletone instance used to persist data into file */ private static PropertiesManager instance = new PropertiesManager(); static { xStream.alias("package", PropertyPackage.class); try { FileUtils.forceMkdir(storage); } catch (IOException e) { } load(); } /** * List of all top level packages */ private List<PropertyPackage> rootPackages = new LinkedList<PropertyPackage>(); /** * Private constructor. */ private PropertiesManager() { // private constructor } /** * Loads properties form {@value #dataFile} and fills {@link #rootPackages} and * {@value #allPackages}. This method is called only once - during class initialization (from a * static block) */ private static void load() { if (dataFile.exists() && dataFile.isFile()) { FileInputStream dataStream = null; try { dataStream = new FileInputStream(dataFile); instance = (PropertiesManager) xStream.fromXML(dataStream); } catch (FileNotFoundException e) { throw new InternalError(e.getMessage()); } finally { if (dataStream != null) try { dataStream.close(); } catch (IOException e) { // oops failed to close stream } } for (PropertyPackage rootPackage : instance.rootPackages) { addToAllPackages(rootPackage); } } } /** * Adds a package and all of it's children to {@link #allPackages}. * * @param propertyPackage the package to add */ private static void addToAllPackages(PropertyPackage propertyPackage) { if (propertyPackage == null) return; allPackages.put(propertyPackage.getName(), propertyPackage); if (propertyPackage.getChildren() != null) { for (PropertyPackage child : propertyPackage.getChildren()) { addToAllPackages(child); } } } /** * Deletes a package and all of it's children from {@link #allPackages}. * * @param propertyPackage the package to add */ private static void deleteFromAllPackages(PropertyPackage propertyPackage) { if (propertyPackage == null) return; if (propertyPackage.getChildren() != null) { for (PropertyPackage child : propertyPackage.getChildren()) { deleteFromAllPackages(child); } } allPackages.remove(propertyPackage.getName()); } /** * Saves data into {@link #dataFile}. This method is called by other static methods of this * class after modifying any data. */ private static void save() { FileOutputStream fileStream = null; try { fileStream = new FileOutputStream(dataFile); xStream.toXML(instance, fileStream); } catch (FileNotFoundException e) { throw new InternalError(e.getMessage()); } finally { if (fileStream != null) try { fileStream.close(); } catch (IOException e) { // oops failed to close stream } } } /** * Adds new {@link PropertyPackage}. NOTE: The object passed is cloned internally. * * @param propertyPackage the {@link PropertyPackage} to be added. Can not be null. * @throws PropertiesException if package with that name already exists or if circular * parent/child relation is detected. * @throws IllegalArgumentException if <code>propertyPackage</code> is null */ public static synchronized void addPropertyPackage(PropertyPackage propertyPackage) throws PropertiesException { if (propertyPackage == null) throw new IllegalArgumentException("propertySet can not be null"); String key = propertyPackage.getName(); if (allPackages.containsKey(key)) throw new PropertiesException("Configuration called " + key + " already exists"); String parentPackageName = null; if (propertyPackage.getParent() != null) { parentPackageName = propertyPackage.getParent().getName(); propertyPackage.setParent(null); } PropertyPackage clonedPackage = null; try { clonedPackage = (PropertyPackage) propertyPackage.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } clonedPackage.setParent(allPackages.get(parentPackageName)); allPackages.put(key, clonedPackage); if (clonedPackage.getParent() == null) { instance.rootPackages.add(clonedPackage); } // packages.add(propertyPackage); save(); } /** * Returns unmodifiable (read only) list of all top level packages * * @return unmodifiable (read only) list of all top level packages */ public static List<PropertyPackage> getRootPropertyPackages() { return Collections.unmodifiableList(instance.rootPackages); } /** * Returns unmodifiable (read only) list of all package names * * @return unmodifiable (read only) list of all package names */ public static List<String> getPropertyPackagesNames() { LinkedList<String> result = new LinkedList<String>(allPackages.keySet()); Collections.sort(result); return Collections.unmodifiableList(result); } /** * Returns a clone of {@link PropertyPackage} called <code>name</code>. If no such * {@link PropertyPackage} exists it will return <code>null</code>. * * @return a {@link PropertyPackage} called <code>name</code> or <code>null</code>. */ public static PropertyPackage getPropertyPackage(String name) { try { PropertyPackage propertyPackage = allPackages.get(name); return propertyPackage == null ? null : (PropertyPackage) propertyPackage.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.getMessage()); } } /** * Deletes the {@link PropertyPackage} specified by <code>packageName</code> and all it's * children. * * @param packageName * @throws PropertiesException */ public static synchronized void deletePropertyPackage(String packageName) throws PropertiesException { if (StringUtils.isBlank(packageName)) throw new IllegalArgumentException("package name can not be null nor empty string"); if (!allPackages.containsKey(packageName)) throw new PropertiesException("No package called " + packageName + " found "); PropertyPackage propertyPackage = allPackages.get(packageName); if (propertyPackage.getParent() != null) { propertyPackage.setParent(null); // to delete it from parent's collection } else { instance.rootPackages.remove(propertyPackage); } deleteFromAllPackages(propertyPackage); save(); } /** * Adds new {@link Property} in {@link PropertyPackage} called <code>packageName</code> by * calling {@link PropertyPackage#addProperty(Property)}. * * @param packageName the name of the package. Can not be <code>null</code> or empty. * @param property the {@link Property} to add. Can not be <code>null</code> * @return a new clone of the {@link PropertyPackage} made after the property is added * @throws PropertiesException if no package called <code>packageName</code> found or if such * {@link Property} already exists in this package * @throws IllegalArgumentException if any of the arguments is <code>null</code> */ public static synchronized PropertyPackage addProperty(String packageName, Property property) throws PropertiesException { if (StringUtils.isBlank(packageName)) throw new IllegalArgumentException("package name can not be null nor empty string"); if (property == null) throw new IllegalArgumentException("property can not be null"); if (!allPackages.containsKey(packageName)) throw new PropertiesException("No package called " + packageName + " found "); PropertyPackage propertyPackage = allPackages.get(packageName); try { propertyPackage.addProperty((Property) property.clone()); save(); return (PropertyPackage) propertyPackage.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } /** * Updates {@link Property} in {@link PropertyPackage} called <code>packageName</code>. The * actual update is handled by {@link PropertyPackage#updateProperty(Property)} * * @param packageName the name of the package. Can not be <code>null</code> or empty. * @param property the {@link Property} to be updated. Can not be <code>null</code> * @return a new clone of the {@link PropertyPackage} made after the property is updated * @throws PropertiesException if no package called <code>packageName</code> found or if no * such {@link Property} exists in this package * @throws IllegalArgumentException if any of the arguments is <code>null</code> */ public static synchronized PropertyPackage updateProperty(String packageName, Property property) throws PropertiesException { if (StringUtils.isBlank(packageName)) throw new IllegalArgumentException("package name can not be null nor empty string"); if (property == null) throw new IllegalArgumentException("property can not be null"); if (!allPackages.containsKey(packageName)) throw new PropertiesException("No package called " + packageName + " found "); PropertyPackage propertyPackage = allPackages.get(packageName); try { propertyPackage.updateProperty((Property) property.clone()); save(); return (PropertyPackage) propertyPackage.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } /** * Updates existing {@link Property} called <code>oldPropertyName</code> in * {@link PropertyPackage} called <code>packageName</code> with values from * <code>property</code>. The actual update is handled by * {@link PropertyPackage#updateProperty(Property)}. * * @param packageName the name of the package. Can not be <code>null</code> or empty. * @param oldPropertyName the name of the property to be updated * @param property the {@link Property} object to get the values from. Can not be * <code>null</code> * @return a new clone of the {@link PropertyPackage} made after the property is updated * @throws PropertiesException if no package called <code>packageName</code> found or if no * such {@link Property} exists in this package * @throws IllegalArgumentException if any of the arguments is <code>null</code> */ public static synchronized PropertyPackage updateProperty(String packageName, String oldPropertyName, Property property) throws PropertiesException { if (StringUtils.isBlank(packageName)) throw new IllegalArgumentException("package name can not be null nor empty string"); if (property == null) throw new IllegalArgumentException("property can not be null"); if (!allPackages.containsKey(packageName)) throw new PropertiesException("No package called " + packageName + " found "); PropertyPackage propertyPackage = allPackages.get(packageName); try { propertyPackage.updateProperty(oldPropertyName, (Property) property.clone()); save(); return (PropertyPackage) propertyPackage.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } /** * Imports external properties into {@link PropertyPackage} called <code>packageName</code> . * The actual import is handled by {@link PropertyPackage#importProperties(Properties, boolean)}. * * @param packageName the name of the package. Can not be <code>null</code> or empty. * @param externalProperties the properties to import. This can not be <code>null</code> * @param overwrite boolean flag indicates whether to override existing properties * @return a new clone of the {@link PropertyPackage} made after the properties are imported * @throws PropertiesException if there is no package called <code>packageName</code> * @throws IllegalArgumentException if any of the arguments is <code>null</code> */ public static synchronized PropertyPackage importProperties(String packageName, Properties externalProperties, boolean overwrite) throws PropertiesException { if (StringUtils.isBlank(packageName)) throw new IllegalArgumentException("package name can not be null nor empty string"); if (!allPackages.containsKey(packageName)) throw new PropertiesException("No package called " + packageName + " found "); PropertyPackage propertyPackage = allPackages.get(packageName); try { propertyPackage.importProperties(externalProperties, overwrite); save(); return (PropertyPackage) propertyPackage.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } /** * Deletes {@link Property} called <code>propertyName</code> from {@link PropertyPackage} * called <code>packageName</code>. The actual deletion is handled by * {@link PropertyPackage#removeProperty(String)}. * * @param packageName the name of the package. Can not be <code>null</code> or empty. * @param propertyName the name of the property to be updated * @return a new clone of the {@link PropertyPackage} made after the property is deleted * @throws PropertiesException if there is no package called <code>packageName</code> or no * property called <code>propertyName</code> * @throws IllegalArgumentException if any of the arguments is <code>null</code> */ public static synchronized PropertyPackage deleteProperty(String packageName, String propertyName) throws PropertiesException { if (StringUtils.isBlank(packageName)) throw new IllegalArgumentException("package name can not be null nor empty string"); if (StringUtils.isBlank(propertyName)) throw new IllegalArgumentException("property can not be null nor empty string"); if (!allPackages.containsKey(packageName)) throw new PropertiesException("No package called " + packageName + " found "); PropertyPackage propertyPackage = allPackages.get(packageName); propertyPackage.removeProperty(propertyName); save(); try { return (PropertyPackage) propertyPackage.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } }