/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.properties; import java.util.Collections; import java.util.HashSet; import java.util.Properties; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Provides access to properties. * * <p>It is important to understand that usage of this class is different from what you might be * used to in java.util.Properties. Specifically, when you get a Properties property, if that * property is not set, the return value is NULL. However, when you call the basic getters here, if * the property is not set, a RuntimeException is thrown. These methods will never return null * (except if you pass in null as the default return value for the methods that take a default). * * <p>There are methods to get properties as various primitive types, int, double, float, etc. When * you invoke one of these methods on a property that is found but cannot be parsed as your desired * type, a RuntimeException is thrown. * * <p>There are corresponding methods which take as a second parameter a default value. These * methods, instead of throwing a RuntimeException when the property cannot be found, return the * default value. You can use the default value "null" to invoke getProperty() with semantics more * like the java.util.Properties object. These augmented accessors which take defaults will be, I * hope, especially useful in static initializers. Providing a default in your static initializer * will keep your class from blowing up at initialization when your property cannot be found. This * seems especially advantageous when there is a plausible default value. * * <p>This class has a comprehensive JUnit testcase. Please keep the testcase up to date with any * changes you make to this class. * * @since 2.4, this class existed in the main package since uPortal 2.0 */ public class PropertiesManager { protected static final Log log = LogFactory.getLog(PropertiesManager.class); public static final String PORTAL_PROPERTIES_FILE_SYSTEM_VARIABLE = "portal.properties"; private static final String PORTAL_PROPERTIES_FILE_NAME = "/properties/portal.properties"; private static Properties props = null; /** * A set of the names of properties that clients of this class attempt to access but which were * not set in the properties file. This Set allows this class to report about missing properties * and to log each missing property only the first time it is requested. */ private static final Set missingProperties = Collections.synchronizedSet(new HashSet()); /** * Setter method to set the underlying Properties. This is a public method to allow poor-man's * static dependency injection of the Properties from wherever you want to get them. If * Properties have not been injected before any accessor method is invoked, PropertiesManager * will invoke loadProperties() to attempt to load its own properties. You might call this from * a context listener, say. If Properties have already been loaded or injected, this method will * overwrite them. * * @param props - Properties to be injected. */ public static synchronized void setProperties(Properties props) { PropertiesManager.props = props; } /** * Load up the portal properties. Right now the portal properties is a simple .properties file * with name value pairs. It may evolve to become an XML file later on. */ protected static void loadProps() { PropertiesManager.props = new Properties(); try { String pfile = System.getProperty(PORTAL_PROPERTIES_FILE_SYSTEM_VARIABLE); if (pfile == null) { pfile = PORTAL_PROPERTIES_FILE_NAME; } PropertiesManager.props.load(PropertiesManager.class.getResourceAsStream(pfile)); } catch (Throwable t) { log.error("Unable to read portal.properties file.", t); } } /** * Returns the value of a property for a given name. Any whitespace is trimmed off the beginning * and end of the property value. Note that this method will never return null. If the requested * property cannot be found, this method throws an UndeclaredPortalException. * * @param name the name of the requested property * @return value the value of the property matching the requested name * @throws MissingPropertyException - if the requested property cannot be found */ public static String getProperty(String name) throws MissingPropertyException { if (log.isTraceEnabled()) { log.trace("entering getProperty(" + name + ")"); } if (PropertiesManager.props == null) loadProps(); String val = getPropertyUntrimmed(name); val = val.trim(); if (log.isTraceEnabled()) { log.trace("returning from getProperty(" + name + ") with return value [" + val + "]"); } return val; } /** * Returns the value of a property for a given name including whitespace trailing the property * value, but not including whitespace leading the property value. An UndeclaredPortalException * is thrown if the property cannot be found. This method will never return null. * * @param name the name of the requested property * @return value the value of the property matching the requested name * @throws MissingPropertyException - (undeclared) if the requested property is not found */ public static String getPropertyUntrimmed(String name) throws MissingPropertyException { if (PropertiesManager.props == null) loadProps(); if (props == null) { boolean alreadyReported = registerMissingProperty(name); throw new MissingPropertyException(name, alreadyReported); } String val = props.getProperty(name); if (val == null) { boolean alreadyReported = registerMissingProperty(name); throw new MissingPropertyException(name, alreadyReported); } return val; } /** * Returns the value of a property for a given name. This method can be used if the property is * boolean in nature and you want to make sure that <code>true</code> is returned if the * property is set to "true", "yes", "y", or "on" (regardless of case), and <code>false</code> * is returned in all other cases. * * @param name the name of the requested property * @return value <code>true</code> if property is set to "true", "yes", "y", or "on" regardless * of case, otherwise <code>false</code> * @throws MissingPropertyException - when no property of the given name is declared. */ public static boolean getPropertyAsBoolean(String name) throws MissingPropertyException { if (PropertiesManager.props == null) loadProps(); boolean retValue = false; String value = getProperty(name); if (value != null) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("y") || value.equalsIgnoreCase("on")) { retValue = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("n") || value.equalsIgnoreCase("off")) { retValue = false; } else { // this method's historical behavior, maintained here, is to return false // for all values that did not match on of the true values above. log.error( "property [" + name + "] is being accessed as a boolean " + "but had non-canonical value [" + value + "]. Returning it as false, " + "but this may be a property misconfiguration."); } } else { log.fatal( "property [" + name + "] is being accessed as a boolean " + "but was null. Returning false. However, it should not have been " + "possible to get here because getProperty() throws a runtime " + "exception or returns a non-null value."); } return retValue; } /** * Returns the value of a property for a given name as a <code>byte</code> * * @param name the name of the requested property * @return value the property's value as a <code>byte</code> * @throws MissingPropertyException - if the property is not set * @throws BadPropertyException - if the property cannot be parsed as a byte */ public static byte getPropertyAsByte(String name) throws MissingPropertyException, BadPropertyException { if (PropertiesManager.props == null) loadProps(); try { return Byte.parseByte(getProperty(name)); } catch (NumberFormatException nfe) { throw new BadPropertyException(name, getProperty(name), "byte"); } } /** * Returns the value of a property for a given name as a <code>short</code> * * @param name the name of the requested property * @return value the property's value as a <code>short</code> * @throws MissingPropertyException - if the property is not set * @throws BadPropertyException - if the property cannot be parsed as a short or is not set. */ public static short getPropertyAsShort(String name) throws MissingPropertyException, BadPropertyException { if (PropertiesManager.props == null) loadProps(); try { return Short.parseShort(getProperty(name)); } catch (NumberFormatException nfe) { throw new BadPropertyException(name, getProperty(name), "short"); } } /** * Returns the value of a property for a given name as an <code>int</code> * * @param name the name of the requested property * @return value the property's value as an <code>int</code> * @throws MissingPropertyException - if the property is not set * @throws BadPropertyException - if the property cannot be parsed as an int */ public static int getPropertyAsInt(String name) throws MissingPropertyException, BadPropertyException { if (PropertiesManager.props == null) loadProps(); try { return Integer.parseInt(getProperty(name)); } catch (NumberFormatException nfe) { throw new BadPropertyException(name, getProperty(name), "int"); } } /** * Returns the value of a property for a given name as a <code>long</code> * * @param name the name of the requested property * @return value the property's value as a <code>long</code> * @throws MissingPropertyException - if the property is not set * @throws BadPropertyException - if the property cannot be parsed as a long */ public static long getPropertyAsLong(String name) throws MissingPropertyException, BadPropertyException { if (PropertiesManager.props == null) loadProps(); try { return Long.parseLong(getProperty(name)); } catch (NumberFormatException nfe) { throw new BadPropertyException(name, getProperty(name), "long"); } } /** * Returns the value of a property for a given name as a <code>float</code> * * @param name the name of the requested property * @return value the property's value as a <code>float</code> * @throws MissingPropertyException - if the property is not set * @throws BadPropertyException - if the property cannot be parsed as a float */ public static float getPropertyAsFloat(String name) throws MissingPropertyException, BadPropertyException { if (PropertiesManager.props == null) loadProps(); try { return Float.parseFloat(getProperty(name)); } catch (NumberFormatException nfe) { throw new BadPropertyException(name, getProperty(name), "float"); } } /** * Returns the value of a property for a given name as a <code>long</code> * * @param name the name of the requested property * @return value the property's value as a <code>double</code> * @throws MissingPropertyException - if the property has not been set * @throws BadPropertyException - if the property cannot be parsed as a double or is not set. */ public static double getPropertyAsDouble(String name) throws MissingPropertyException, BadPropertyException { if (PropertiesManager.props == null) loadProps(); try { return Double.parseDouble(getProperty(name)); } catch (NumberFormatException nfe) { throw new BadPropertyException(name, getProperty(name), "double"); } } /** * Registers that a given property was sought but not found. Currently adds the property to the * set of missing properties and logs if this is the first time the property has been requested. * * @param name - the name of the missing property * @return true if the property was previously registered, false otherwise */ private static boolean registerMissingProperty(String name) { final boolean previouslyReported = !PropertiesManager.missingProperties.add(name); if (!previouslyReported && log.isInfoEnabled()) { log.info("Property [" + name + "] was requested but not found."); } return previouslyReported; } /** * Get the value of the property with the given name. If the named property is not found, * returns the supplied default value. This error handling behavior makes this method attractive * for use in static initializers. * * @param name - the name of the property to be retrieved. * @param defaultValue - a fallback default value which will be returned if the property cannot * be found. * @return the value of the requested property, or the supplied default value if the named * property cannot be found. * @since 2.4 */ public static String getProperty(String name, String defaultValue) { if (PropertiesManager.props == null) loadProps(); String returnValue = defaultValue; try { returnValue = getProperty(name); } catch (MissingPropertyException mpe) { // Do nothing, since we have already recorded and logged the missing property. } return returnValue; } /** * Get a property as a boolean, specifying a default value. If for any reason we are unable to * lookup the desired property, this method returns the supplied default value. This error * handling behavior makes this method suitable for calling from static initializers. * * @param name - the name of the property to be accessed * @param defaultValue - default value that will be returned in the event of any error * @return the looked up property value, or the defaultValue if any problem. * @since 2.4 */ public static boolean getPropertyAsBoolean(final String name, final boolean defaultValue) { if (PropertiesManager.props == null) loadProps(); boolean returnValue = defaultValue; try { returnValue = getPropertyAsBoolean(name); } catch (MissingPropertyException mpe) { // do nothing, since we already logged the missing property } return returnValue; } /** * Get the value of the given property as a byte, specifying a fallback default value. If for * any reason we are unable to lookup the desired property, this method returns the supplied * default value. This error handling behavior makes this method suitable for calling from * static initializers. * * @param name - the name of the property to be accessed * @param defaultValue - the default value that will be returned in the event of any error * @return the looked up property value, or the defaultValue if any problem. * @since 2.4 */ public static byte getPropertyAsByte(final String name, final byte defaultValue) { if (PropertiesManager.props == null) loadProps(); byte returnValue = defaultValue; try { returnValue = getPropertyAsByte(name); } catch (Throwable t) { log.error( "Could not retrieve or parse as byte property [" + name + "], defaulting to [" + defaultValue + "]", t); } return returnValue; } /** * Returns the value of a property for a given name as a short. If for any reason the property * cannot be looked up as a short, returns the supplied default value. This error handling makes * this method a good choice for static initializer calls. * * @param name - the name of the requested property * @param defaultValue - a default value that will be returned in the event of any error * @return the property value as a short or the default value in the event of any error * @since 2.4 */ public static short getPropertyAsShort(String name, short defaultValue) { if (PropertiesManager.props == null) loadProps(); short returnValue = defaultValue; try { returnValue = getPropertyAsShort(name); } catch (Throwable t) { log.error( "Could not retrieve or parse as short property [" + name + "], defaulting to given value [" + defaultValue + "]", t); } return returnValue; } /** * Get the value of a given property as an int. If for any reason the property cannot be looked * up as an int, returns the supplied default value. This error handling makes this method a * good choice for static initializer calls. * * @param name - the name of the requested property * @param defaultValue - a fallback default value for the property * @return the value of the property as an int, or the supplied default value in the event of * any problem. * @since 2.4 */ public static int getPropertyAsInt(String name, int defaultValue) { if (PropertiesManager.props == null) loadProps(); int returnValue = defaultValue; try { returnValue = getPropertyAsInt(name); } catch (Throwable t) { log.error( "Could not retrieve or parse as int the property [" + name + "], defaulting to " + defaultValue, t); } return returnValue; } /** * Get the value of the given property as a long. If for any reason the property cannot be * looked up as a long, returns the supplied default value. This error handling makes this * method a good choice for static initializer calls. * * @param name - the name of the requested property * @param defaultValue - a fallback default value that will be returned if there is any problem * @return the value of the property as a long, or the supplied default value if there is any * problem. * @since 2.4 */ public static long getPropertyAsLong(String name, long defaultValue) { if (PropertiesManager.props == null) loadProps(); long returnValue = defaultValue; try { returnValue = getPropertyAsLong(name); } catch (Throwable t) { log.error( "Could not retrieve or parse as long property [" + name + "], defaulting to " + defaultValue, t); } return returnValue; } /** * Get the value of the given property as a float. If for any reason the property cannot be * looked up as a float, returns the supplied default value. This error handling makes this * method a good choice for static initializer calls. * * @param name - the name of the requested property * @param defaultValue - a fallback default value that will be returned if there is any problem * @return the value of the property as a float, or the supplied default value if there is any * problem. * @since 2.4 */ public static float getPropertyAsFloat(String name, float defaultValue) { if (PropertiesManager.props == null) loadProps(); float returnValue = defaultValue; try { returnValue = getPropertyAsFloat(name); } catch (Throwable t) { log.error( "Could not retrieve or parse as float property [" + name + "], defaulting to " + defaultValue, t); } return returnValue; } /** * Get the value of the given property as a double. If for any reason the property cannot be * looked up as a double, returns the specified default value. This error handling makes this * method a good choice for static initializer calls. * * @param name - the name of the requested property * @param defaultValue - a fallback default value that will be returned if there is any problem * @return the value of the property as a double, or the supplied default value if there is any * problem. * @since 2.4 */ public static double getPropertyAsDouble(String name, double defaultValue) { if (PropertiesManager.props == null) loadProps(); double returnValue = defaultValue; try { returnValue = getPropertyAsDouble(name); } catch (Throwable t) { log.error( "Could not retrieve or parse as double property [" + name + "], defaulting to " + defaultValue, t); } return returnValue; } /** * Get a Set of the names of properties that have been requested but were not set. * * @return a Set of the String names of missing properties. * @since 2.4 */ public static Set getMissingProperties() { return PropertiesManager.missingProperties; } }