/**
* Abiquo community edition
* cloud management application for hybrid clouds
* Copyright (C) 2008-2010 - Abiquo Holdings S.L.
*
* This application 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 under
* version 3 of the License
*
* This software 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 v.3 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package com.abiquo.util.resources;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Class ResourceManager.
*
* @author abiquo
* <p>
* A manager for property files and resource bundles that have the same baseName Structure a
* of resources are as follows resources properties txt - properties files as plain text
* files having a .properties suffix xml - properties files as xml file locale txt -
* resourcebundle as properties text file xml - resourcebundle as a properties xml file For
* example a base name of MyResource could have the following variants of property and
* resource bundle files. <br>
* Property files <br>=
* ===========================================================
* <ol>
* <li>MyResource.properties</li>
* <li>MyResource.[SUBSECTION].properties</li>
* <li>MyResource.xml</li>
* <li>MyResource.[SUBSECTION].xml</li>
* </ol>
* <br>
* Resource files <br>=
* ============================================================
* <li>MyResource.properties</li>
* <li>MyResource.[SUBSECTION].properties (rare cases)
* <li>MyResource_LANGUAGE.properties</li>
* <li>MyResource.[SUBSECTION]_LANGUAGE.properties</li> (Rare cases)
* <li>MyResource_LANGUAGE_COUNTRY.properties</li>
* <li>MyResource.[SUBSECTION]_LANGUAGE_COUNTRY.properties</li>
* <li>MyResource.xml</li>
* <li>MyResource.[SUBSECTION].xml (rare cases)
* <li>MyResource_LANGUAGE.xml</li>
* <li>MyResource.[SUBSECTION]_LANGUAGE.xml</li> (Rare cases)
* <li>MyResource_LANGUAGE_COUNTRY.xml</li>
* <li>MyResource.[SUBSECTION]_LANGUAGE_COUNTRY.xml</li>
*
* <pre>
* Example
* The class com.foo.Foo should could have the following properties files (as .properties and .xml)
* and resource bundles in the resources directory as so:
* src/main/resources/
* locale/
* txt/
* com/
* foo/
* Foo.properties
* Foo_es.properties
* properties/
* txt/
* com/
* foo/
* Foo.properties
* xml/
* com/
* foo/
* Foo.xml
* To get the resource manager for this class do the following
* <code>ResourceManager resourceManager = new ResourceManager(Foo.class); </code>
*
* The following can hence be done:
* 1. Retrieve the properties files that
* <code>Properties p = resourceManager.getProperties();</code>
* 2. Retrieve the properties file in xml format
* <code>Properties p = resourceManager.getPropertiesFromXML();</code>
*
* 3. Retrieve the resource bundle by one of the following ways
* <code>PropertyResourceBundle bundle = (PropertyResourceBundle) BasicCommand.resourceManager.getResourceBundle();</code>
* <br>
* <code>XMLResourceBundle bundle = (XMLResourceBundle) BasicCommand.resourceManager.getResourceBundle();</code>
*
* </pre>
*/
public class ResourceManager
{
/** The Constant logger. */
private static final Logger logger = LoggerFactory.getLogger(ResourceManager.class);
/** The control. */
private final ResourceBundle.Control control;
/** The class loader. */
private final ClassLoader classLoader;
/** The locale. */
private Locale locale;
/**
* Base name of the properties file or resource bundle (or both) that is managed by this
* ResourceManage object.
*/
private String baseName;
/**
* Instantiates a new resource manager.
*/
public ResourceManager()
{
classLoader = this.getClass().getClassLoader();
control = new ResourceBundleControl();
locale = Locale.getDefault();
}
/**
* The Constructor.
*
* @param baseName baseName of the property file or ResourceBundle (if it serves as one).
*
* <pre>
* The file(s) must be located somewhere in the src/main/resources/SUB_DIRECTORY where
* SUB_DIRECTORY could be
* properties - location of property or Resource bundle files stored as .properties files
* xml - location of property or Resource bundle files stored as in .xml files
* </pre>
*/
public ResourceManager(String baseName)
{
this();
// Check if someone supplied a baseName with an .xml or .properties extension, if so remove
// the extension
if (baseName.endsWith(".properties") || baseName.endsWith(".xml"))
{
baseName = baseName.substring(0, baseName.lastIndexOf("."));
}
this.baseName = baseName;
}
/**
* The Constructor.
*
* @param cl reference to the a <code>Class</code> representing the class use properties file or
* resource bundle will be handled by the instance created. The fully qualified name
* of the class represented by <code>cl</code> is used to obtain the base name of the
* .properties file(s) or resource bundle(s)- <br>
*
* <pre>
* For example if cl holds information on the class com.foo.Foo then the baseName will be com/foo/Foo - from which the following files can be obtained
* com/foo/Foo.properties
* com/foo/Foo_es.properties
* com/foo/Foo_en_UK.properties
* com/foo/Foo_ca_ES.properties
* com/foo/Foo_es.xml
* com/foo/Foo.1.properties
* com/foo/Foo.2.properties
* </br>
*/
public ResourceManager(Class< ? > cl)
{
this();
this.setBaseName(cl);
}
/**
* Sets the basename of this object.
*
* @param baseName <code>String</code> containing the relative path of the baseName
*/
public final void setBaseName(String baseName)
{
this.baseName = baseName;
}
/**
* Sets the <code>basename</code> of this ResourceManager object to a relativefile path that
* corresponds the full package name of class represented by the argument that passed to it. for
* example if the class represented by argument is com.foo.Foo then the <code>basename</code>
* will be com/foo/Foo
*
* @param cl <code>Class</code> object from which the basename will be obtained.
*/
public final void setBaseName(Class< ? > cl)
{
baseName = cl.getName().replace(".", "/");
}
/**
* Sets the locale.
*
* @param locale the new locale
*/
public final void setLocale(Locale locale)
{
this.locale = locale;
}
/**
* Sets the locale.
*
* @param localeStr a string representeation of the Locale to be created e.g es_ES
*/
public final void setLocale(String localeStr)
{
this.setLocale(createLocaleFromString(localeStr));
}
public Locale getLocale()
{
return locale;
}
/**
* Creates the <code>Locale</code> from a <code>String</code> representation of a
* <code>Locale</code>.
*
* <pre>
* For example: the String es_ES will produce a Locale for spanish from Spain.
* </pre>
*
* @param localeStr <code>String</code> representation of the <code>Locale</code> to be created.
* @return a <code>Locale</code> object
*/
private Locale createLocaleFromString(String localeStr)
{
String[] strs = localeStr.split("_");
Locale locale;
if (strs.length == 1)
{
locale = new Locale(strs[0]);
}
else if (strs.length == 2)
{
locale = new Locale(strs[0], strs[1]);
}
else if (strs.length == 3)
{
locale = new Locale(strs[0], strs[1], strs[2]);
}
else
{
locale = Locale.getDefault();
}
return locale;
}
/**
* Retrieves properties from a simple .properties text file
*
* @param args the args
* @return a reference to a Properties object
*/
public Properties getProperties(String... args)
{
return this.getProperties(null, args);
}
/**
* Gets the properties.
*
* @param p the p
* @param args the args
* @return the properties
*/
public Properties getProperties(Properties p, String... args)
{
return this.getProperties(p, args, false);
}
/**
* Retrieves properties from a simple XML file.
*
* @param args the args
* @return a reference to a Properties object
*/
public Properties getPropertiesFromXML(String... args)
{
return this.getProperties(null, args);
}
/**
* Gets the properties from xml.
*
* @param p the p
* @param args the args
* @return the properties from xml
*/
public Properties getPropertiesFromXML(Properties p, String... args)
{
return this.getProperties(p, args, true);
}
/**
* Gets the properties.
*
* @param p the p
* @param args and array of Objects with the following order [String,Boolean]
* @return the properties
*/
private Properties getProperties(Properties p, Object... args)
{
if (p == null)
{
p = new Properties();
}
String[] strs = (String[]) args[0];
String subSection = strs.length > 0 ? strs[0] : "";
boolean isXML = (Boolean) args[1];
String fileName = subSection.length() > 0 ? baseName + "." + subSection : baseName;
try
{
if (isXML)
{
p.loadFromXML(classLoader
.getResourceAsStream(ResourceConstants.RESOURCES_PROPERTIES_XML_ROOT_DIR
+ fileName + ".xml"));
}
else
{
p.load(classLoader
.getResourceAsStream(ResourceConstants.RESOURCES_PROPERTIES_TXT_ROOT_DIR
+ fileName + ".properties"));
}
}
catch (NullPointerException e)
{
logger.warn("File Name: " + fileName, e);
}
catch (IOException e)
{
logger.error("File Name: " + fileName, e);
}
catch (Exception e)
{
logger.error("File Name: " + fileName, e);
}
return p;
}
/**
* Gets the resource bundle.
*
* <pre>
* It call getResourceBundle(Locale locale,String ... args)
* passing the Locale of the current ResourceManagerInstance.
* </pre>
*
* @param args the args
* @return the resource bundle
*/
public ResourceBundle getResourceBundle(String... args)
{
return this.getResourceBundle(locale, args);
}
/**
* Gets the resource bundle according to the Locale passed to it as a argument.
*
* @param locale the locale
* @param args the args
* @return a reference to a ResourceBundle
*/
public ResourceBundle getResourceBundle(Locale locale, String... args)
{
String bundleBaseName = baseName;
ResourceBundle resourceBundle = null;
try
{
String subSection = args.length > 0 ? args[0] : "";
if (subSection.length() > 0)
{
bundleBaseName += "." + subSection;
}
resourceBundle = ResourceBundle.getBundle(bundleBaseName, locale, control);
}
catch (NullPointerException e)
{
logger.warn("The basename is null - a null value will be returned");
}
catch (MissingResourceException e)
{
logger.warn("No Resource matching the bundleBaseName. " + bundleBaseName
+ " has been found");
}
finally
{
// If an exception has occurred - an empty ResourceBundle is returned whose
// handleGetObject returns an empty String
if (resourceBundle == null)
{
resourceBundle = new ResourceBundle()
{
@Override
public Enumeration<String> getKeys()
{
return null;
}
@Override
protected Object handleGetObject(String key)
{
return new String();
}
};
}
}
return resourceBundle;
}
/**
* Get the <code>ResourceBundle</code> object according to the <code>String</code>
* representation of the locale passed as an argument, and extra optional arguments. The locale
* created from a string representation of the locale
*
* @param localeStr <code>String</code> from which the <code>Locale</code> will be created. The
* created <code>Locale</code> will then be used to be determine which
* <code>ResourceBundle</code> to return. This argument can have the following format
* <ul>
* <li>[LANGUAGE] : two letter representation of the language in lowercase e.g
* <code>es</es> for spanish</li>
* <li>[LANGUAGE_COUNTRY]: two letter representation of the language in lowercase
* followed by an underscore and a 2 letter abbreviation of the country it belongs to
* for example es_ES for the Spanish language in Spain</li>
* <li>[LANGUAGE_COUNTRY_VARIANT]: 2 letter representation of the language in
* lowercase followed by an underscore and a 2 letter abbreviation of the country, an
* underscore and a variant of the language in lowercase
* </ul>
* @param args the args
* @return <code>ResourceBundle</code> object according to the <code>String</code>
* representation of the locale passed as an argument, and extra optional arguments
*/
public ResourceBundle getResourceBundle2(String localeStr, String... args)
{
return this.getResourceBundle(createLocaleFromString(localeStr), args);
}
/**
* Gets the message.
*
* @param key the key
* @param args the args
* @return the message
*/
public final String getMessage(String key, String... args)
{
// Gets the message via a PropertyResourceBundle - another method should use
// XMLResourceBundle
// Need to check if it is an XMLResourceBundle
PropertyResourceBundle bundle = (PropertyResourceBundle) this.getResourceBundle();
bundle.handleGetObject(key);
return this.getBundleNameValue(bundle, key, args);
}
/**
* Gets the bundle name value.
*
* @param bundle the bundle
* @param bundleName the bundle name
* @param args the args
* @return the bundle name value
*/
private final <T extends ResourceBundle> String getBundleNameValue(T bundle, String bundleName,
Object[] args)
{
String msg = "";
try
{
msg = bundle.getObject(bundleName).toString();
msg = MessageFormat.format(msg, args);
}
catch (NullPointerException e)
{
logger.warn("The bundlename supplied is {" + bundleName
+ "} an empty string will be returned");
}
catch (MissingResourceException e)
{
logger.warn("No entry found for the bundle name : [" + bundleName
+ "} an empty string will be returned");
}
return msg;
}
/**
* @param p reference to a a
* <code>Properties</object> from which a set of key/value pairs will be obtained .
* @param propertyNames a <code>String[]</code> containing a list of keys in the Properties
* arguments, using this list a new Properties element is created containing only the
* keys that correspond each element in this array. The resulting
* <code>Properties</code> is then save to a file.
* @param args (optional) this is an <code>String[]</code> which has only on element - the name
* of the [SUBSECTION] part as described in the class description. TODO put link to
* part of the documentation
*/
public void saveProperties(Properties p, String[] propertyNames, String... args)
{
Properties p2 = new Properties();
for (String propertyName : propertyNames)
{
p2.setProperty(propertyName, p.getProperty(propertyName));
}
this.saveProperties(p2, args);
}
/**
* Save properties.
*
* @param p the p
* @param args (optional) an array of Strings composing of only element which will be string
* appended to the base name of the properties file to be create <br>
* For example if the baseName is "foo" and args[0] is "xx" then the file created
* will be foo.xx.properties
*/
public void saveProperties(Properties p, String... args)
{
File file =
createPropertiesFile(args, ResourceConstants.RESOURCES_PROPERTIES_TXT_ROOT_DIR,
".properties");
try
{
logger.debug("Saving the properties file : " + file + " ... ");
p.store(new PrintWriter(file), null);
logger.debug("Properties file: " + file + " saved ");
}
catch (Exception e)
{
logger.error("Unable to write the properties to the file: " + file, e);
}
}
/**
* Save properties to xml.
*
* @param p the p
* @param args the args
*/
public void savePropertiesToXML(Properties p, String... args)
{
File file =
createPropertiesFile(args, ResourceConstants.RESOURCES_PROPERTIES_XML_ROOT_DIR, ".xml");
try
{
p.storeToXML(new FileOutputStream(file), null, "UTF-8");
}
catch (Exception e)
{
logger.error("Unable to write the properties to the file: " + file, e);
}
}
/**
* Gets the properties file name.
*
* @param obj the obj
* @return the properties file name
*/
private File createPropertiesFile(Object obj, String rootDir, String fileExtension)
{
String subSection;
if (obj instanceof String)
{
subSection = (String) obj;
}
else
{
String[] args = (String[]) obj;
subSection = args.length > 0 ? args[0] : "";
}
String fileName = subSection.length() > 0 ? baseName + "." + subSection : baseName;
fileName = fileName.replaceAll("/$", "") + fileExtension;
fileName =
classLoader.getResource("").getPath().replaceAll(File.separator + "$", "")
+ File.separator + rootDir + fileName;
// Check if the root directory of the file exists
File file = new File(fileName);
if (!file.exists())
{
if (!file.getParentFile().exists())
{
file.getParentFile().mkdirs();
}
}
return file;
}
}