/* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.impl.configuration; import java.io.*; import java.util.*; import javax.xml.parsers.*; import net.java.sip.communicator.impl.configuration.xml.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.configuration.event.*; import net.java.sip.communicator.service.fileaccess.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.xml.*; import org.osgi.framework.*; import org.w3c.dom.*; import org.xml.sax.*; /** * A straight forward implementation of the ConfigurationService using an xml * file for storing properties. Currently only String properties are * meaningfully saved (we should probably consider how and whether we should * take care of the rest). * * @author Emil Ivov * @author Damian Minkov * @author Lubomir Marinov */ public class ConfigurationServiceImpl implements ConfigurationService { private final Logger logger = Logger.getLogger(ConfigurationServiceImpl.class); /** * The XML Document containing the configuration file this service loaded. */ private Document propertiesDocument = null; /** Name of the xml attribute containing property values */ private static final String ATTRIBUTE_VALUE = "value"; /** * Name of the xml attribute indicating that a property is to be resolved * in the system properties */ private static final String SYSTEM_ATTRIBUTE_NAME = "system"; /** The value of the Name of the xml attribute containing property values */ private static final String SYSTEM_ATTRIBUTE_TRUE = "true"; /** * The name of the system property that stores the name of the configuration * file. */ private static final String FILE_NAME_PROPERTY = "net.java.sip.communicator.CONFIGURATION_FILE_NAME"; private static final String SYS_PROPS_FILE_NAME_PROPERTY = "net.java.sip.communicator.SYS_PROPS_FILE_NAME"; /** * A reference to the currently used configuration file. */ private File configurationFile = null; /** * Our event dispatcher. */ private final ChangeEventDispatcher changeEventDispatcher = new ChangeEventDispatcher(this); /** * The list of properties currently registered in the configuration service. */ private Map<String, Object> properties = new Hashtable<String, Object>(); /** * Contains the properties that were initially loaded from the configuration * file or (if the properties have been modified and saved since initially * loaded) those that were last written to the file.We use the property so * that we could determine which properties are new and do not have a * corresponding node in the XMLDocument object. */ private Map<String, Object> fileExtractedProperties = new Hashtable<String, Object>(); /** * Indicates whether the service is started or stopped. */ private boolean started = false; /** * a reference to the FileAccessService */ private FileAccessService faService = null; /** * Sets the property with the specified name to the specified value. Calling * this method would first trigger a PropertyChangeEvent that will * be dispatched to all VetoableChangeListeners. In case no complaints * (PropertyVetoException) have been received, the property will be actually * changed and a PropertyChangeEvent will be dispatched. * <p> * @param propertyName String * @param property Object * @throws PropertyVetoException in case the changed has been refused by * at least one propertychange listener. */ public void setProperty(String propertyName, Object property) throws PropertyVetoException { setProperty(propertyName, property, false); } /** * Sets the property with the specified name to the specified. Calling * this method would first trigger a PropertyChangeEvent that will * be dispatched to all VetoableChangeListeners. In case no complaints * (PropertyVetoException) have been received, the property will be actually * changed and a PropertyChangeEvent will be dispatched. This method also * allows the caller to specify whether or not the specified property is a * system one. * <p> * @param propertyName the name of the property to change. * @param property the new value of the specified property. * @param isSystem specifies whether or not the property being is a System * property and should be resolved against the system * property set. If the property has previously been * specified as system then this value is inteernally forced * to true. * @throws PropertyVetoException in case the changed has been refused by * at least one propertychange listener. */ public void setProperty(String propertyName, Object property, boolean isSystem) throws PropertyVetoException { Object oldValue = getProperty(propertyName); //first check whether the change is ok with everyone if (changeEventDispatcher.hasVetoableChangeListeners(propertyName)) changeEventDispatcher.fireVetoableChange( propertyName, oldValue, property); //no exception was thrown - lets change the property and fire a //change event logger.trace(propertyName + "( oldValue=" + oldValue + ", newValue=" + property + "."); //once set system, a property remains system event if the user //specified sth else if (isSystem(propertyName)) isSystem = true; if (property == null) { properties.remove(propertyName); fileExtractedProperties.remove(propertyName); if (isSystem) { //we can't remove or nullset a sys prop so let's "empty" it. System.setProperty(propertyName, ""); } } else { if (isSystem) { //in case this is a system property, we must only store it //in the System property set and keep only a ref locally. System.setProperty(propertyName, property.toString()); properties.put(propertyName, new PropertyReference(propertyName)); } else { properties.put(propertyName, property); } } if (changeEventDispatcher.hasPropertyChangeListeners(propertyName)) changeEventDispatcher.firePropertyChange( propertyName, oldValue, property); try { storeConfiguration(); } catch (IOException ex) { logger.error("Failed to store configuration after " + "a property change"); } } /** * Removes the property with the specified name. Calling * this method would first trigger a PropertyChangeEvent that will * be dispatched to all VetoableChangeListeners. In case no complaints * (PropertyVetoException) have been received, the property will be actually * changed and a PropertyChangeEvent will be dispatched. * All properties with prefix propertyName will also be removed. * <p> * @param propertyName the name of the property to change. * @param property the new value of the specified property. * @throws PropertyVetoException in case the changed has been refused by * at least one propertychange listener. */ public void removeProperty(String propertyName) throws PropertyVetoException { List<String> childPropertyNames = getPropertyNamesByPrefix(propertyName, false); //remove all properties for (String pName : childPropertyNames) { removeProperty(pName); } Object oldValue = getProperty(propertyName); //first check whether the change is ok with everyone if (changeEventDispatcher.hasVetoableChangeListeners(propertyName)) changeEventDispatcher.fireVetoableChange( propertyName, oldValue, null); //no exception was thrown - lets change the property and fire a //change event logger.trace("Will remove prop: " + propertyName + "."); properties.remove(propertyName); fileExtractedProperties.remove(propertyName); if (changeEventDispatcher.hasPropertyChangeListeners(propertyName)) changeEventDispatcher.firePropertyChange( propertyName, oldValue, null); try { storeConfiguration(); } catch (IOException ex) { logger.error("Failed to store configuration after " + "a property change"); } } /** * Returns the value of the property with the specified name or null if no * such property exists. * @param propertyName the name of the property that is being queried. * @return the value of the property with the specified name. */ public Object getProperty(String propertyName) { Object value = properties.get(propertyName); //if this is a property reference make sure we return the referenced //value and not the reference itself if(value instanceof PropertyReference) return ((PropertyReference)value).getValue(); else return value; } /** * Returns a <tt>java.util.List</tt> of <tt>String</tt>s containing the * all property names that have the specified prefix. Depending on the value * of the <tt>exactPrefixMatch</tt> parameter the method will (when false) * or will not (when exactPrefixMatch is true) include property names that * have prefixes longer than the specified <tt>prefix</tt> param. * <p> * Example: * <p> * Imagine a configuration service instance containing 2 properties only:<br> * <code> * net.java.sip.communicator.PROP1=value1<br> * net.java.sip.communicator.service.protocol.PROP1=value2 * </code> * <p> * A call to this method with a prefix="net.java.sip.communicator" and * exactPrefixMatch=true would only return the first property - * net.java.sip.communicator.PROP1, whereas the same call with * exactPrefixMatch=false would return both properties as the second prefix * includes the requested prefix string. * <p> * @param prefix a String containing the prefix (the non dotted non-caps * part of a property name) that we're looking for. * @param exactPrefixMatch a boolean indicating whether the returned * property names should all have a prefix that is an exact match of the * the <tt>prefix</tt> param or whether properties with prefixes that * contain it but are longer than it are also accepted. * @return a <tt>java.util.List</tt>containing all property name String-s * matching the specified conditions. */ public List<String> getPropertyNamesByPrefix(String prefix, boolean exactPrefixMatch) { List<String> resultKeySet = new LinkedList<String>(); for (String key : properties.keySet()) { int ix = key.lastIndexOf('.'); if(ix == -1) continue; String keyPrefix = key.substring(0, ix); if(exactPrefixMatch) { if(prefix.equals(keyPrefix)) resultKeySet.add(key); } else { if(keyPrefix.startsWith(prefix)) resultKeySet.add(key); } } return resultKeySet; } /** * Adds a PropertyChangeListener to the listener list. * * @param listener the PropertyChangeListener to be added */ public void addPropertyChangeListener(PropertyChangeListener listener) { changeEventDispatcher.addPropertyChangeListener(listener); } /** * Removes a PropertyChangeListener from the listener list. * * @param listener the PropertyChangeListener to be removed */ public void removePropertyChangeListener(PropertyChangeListener listener) { changeEventDispatcher.removePropertyChangeListener(listener); } /** * Adds a PropertyChangeListener to the listener list for a specific * property. * * @param propertyName one of the property names listed above * @param listener the PropertyChangeListener to be added */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { changeEventDispatcher. addPropertyChangeListener(propertyName, listener); } /** * Removes a PropertyChangeListener from the listener list for a specific * property. * * @param propertyName a valid property name * @param listener the PropertyChangeListener to be removed */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { changeEventDispatcher. removePropertyChangeListener(propertyName, listener); } /** * Adds a VetoableChangeListener to the listener list. * * @param listener the VetoableChangeListener to be added */ public void addVetoableChangeListener(VetoableChangeListener listener) { changeEventDispatcher.addVetoableChangeListener(listener); } /** * Removes a VetoableChangeListener from the listener list. * * @param listener the VetoableChangeListener to be removed */ public void removeVetoableChangeListener(VetoableChangeListener listener) { changeEventDispatcher.removeVetoableChangeListener(listener); } /** * Adds a VetoableChangeListener to the listener list for a specific * property. * * @param propertyName one of the property names listed above * @param listener the VetoableChangeListener to be added */ public void addVetoableChangeListener(String propertyName, VetoableChangeListener listener) { changeEventDispatcher.addVetoableChangeListener(propertyName, listener); } /** * Removes a VetoableChangeListener from the listener list for a specific * property. * * @param propertyName a valid property name * @param listener the VetoableChangeListener to be removed */ public void removeVetoableChangeListener(String propertyName, VetoableChangeListener listener) { changeEventDispatcher.removeVetoableChangeListener(propertyName, listener); } /** * Called on service stop. */ void stop() { this.started = false; } /** * Initializes the configuration service impl and makes it load an initial * configuration from the conf file. */ void start() { this.started = true; // retrieve a reference to the FileAccessService BundleContext bc = ConfigurationActivator.bundleContext; ServiceReference faServiceReference = bc.getServiceReference( FileAccessService.class.getName()); this.faService = (FileAccessService) bc.getService(faServiceReference); try { debugPrintSystemProperties(); preloadSystemPropertyFiles(); reloadConfiguration(); } catch (XMLException ex) { logger.error("Failed to parse the configuration file.", ex); } catch (IOException ex) { logger.error("Failed to load the configuration file", ex); } } public void reloadConfiguration() throws IOException, XMLException { properties = new Hashtable<String, Object>(); this.configurationFile = null; fileExtractedProperties = loadConfiguration(getConfigurationFile()); this.properties.putAll(fileExtractedProperties); } /** * Loads the contents of the specified configuration file into the local * properties object. * @param file a reference to the configuration file to load. * @return a hashtable containing all properties extracted from the * specified file. * * @throws IOException if the specified file does not exist * @throws XMLException if there is a problem with the file syntax. */ Map<String, Object> loadConfiguration(File file) throws IOException, XMLException { // restore the file if needed FailSafeTransaction trans = this.faService .createFailSafeTransaction(file); try { trans.restoreFile(); } catch (Exception e) { logger.error("can't restore the configuration file before loading" + " it", e); } try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Map<String, Object> properties = new Hashtable<String, Object>(); //if the file is empyt (or contains only sth insignificant) //ifnore it and create a new document. if(file.length() < "<sip-communicator>".length()*2) propertiesDocument = createPropertiesDocument(); else propertiesDocument = builder.parse(file); Node root = propertiesDocument.getFirstChild(); Node currentNode = null; NodeList children = root.getChildNodes(); for(int i = 0; i < children.getLength(); i++) { currentNode = children.item(i); if(currentNode.getNodeType() == Node.ELEMENT_NODE) { StringBuffer propertyNameBuff = new StringBuffer(); propertyNameBuff.append(currentNode.getNodeName()); loadNode(currentNode, propertyNameBuff, properties); } } return properties; } catch(SAXException ex) { logger.error("Error parsing configuration file", ex); throw new XMLException(ex.getMessage(), ex); } catch(ParserConfigurationException ex) { //it is not highly probable that this might happen - so lets just //log it. logger.error("Error finding configuration for default parsers", ex); return new Hashtable<String, Object>(); } } public synchronized void storeConfiguration() throws IOException { storeConfiguration(getConfigurationFile()); } private Document createPropertiesDocument() { if(propertiesDocument == null) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = null; try { builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException ex) { logger.error("Failed to create a DocumentBuilder", ex); return null; } propertiesDocument = builder.newDocument(); propertiesDocument.appendChild( propertiesDocument.createElement("sip-communicator")); } return propertiesDocument; } /** * Stores local properties in the specified configuration file. * @param file a reference to the configuration file where properties should * be stored. * @throws IOException if there was a problem writing to the specified file. */ private void storeConfiguration(File file) throws IOException { if(!started) throw new IllegalStateException("Service is stopped or has not been started"); //resolve the properties that were initially in the file - back to //the document. if (propertiesDocument == null) propertiesDocument = createPropertiesDocument(); Node root = propertiesDocument.getFirstChild(); Node currentNode = null; NodeList children = root.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { currentNode = children.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE) { StringBuffer propertyNameBuff = new StringBuffer(); propertyNameBuff.append(currentNode.getNodeName()); updateNode(currentNode, propertyNameBuff, properties); } } //create in the document the properties that were added by other //bundles after the initial property load. Map<String, Object> newlyAddedProperties = cloneProperties(); //remove those that were originally there; for (String propName : fileExtractedProperties.keySet()) newlyAddedProperties.remove(propName); this.processNewProperties(propertiesDocument, newlyAddedProperties); //write the file. File config = getConfigurationFile(); FailSafeTransaction trans = this.faService .createFailSafeTransaction(config); try { trans.beginTransaction(); OutputStream stream = new FileOutputStream(config); XMLUtils.indentedWriteXML( propertiesDocument, stream); stream.close(); trans.commit(); } catch (Exception e) { logger.error("can't write data in the configuration file", e); trans.rollback(); } } /** * Loads the contents of the specified node and its children into the local * properties. Any nodes marked as "system" will also be resolved in the * system properties. * @param node the root node that we shold load together with its children * @param propertyNameBuff a StringBuffer containing the prefix describing * the route to the specified node including its one name * @param properties the dictionary object where all properties extracted * from this node and its children should be recorded. */ private void loadNode(Node node, StringBuffer propertyNameBuff, Map<String, Object> properties) { Node currentNode = null; NodeList children = node.getChildNodes(); for(int i = 0; i < children.getLength(); i++) { currentNode = children.item(i); if(currentNode.getNodeType() == Node.ELEMENT_NODE) { StringBuffer newPropBuff = new StringBuffer(propertyNameBuff + "." +currentNode.getNodeName()); String value = XMLConfUtils.getAttribute( currentNode, ATTRIBUTE_VALUE); String propertyType = XMLConfUtils.getAttribute(currentNode, SYSTEM_ATTRIBUTE_NAME); // the value attr is present we must handle the desired property if(value != null) { //if the property is marked as "system", we should resolve //it against the system properties and only store a //reference locally. this is normally done for properties //that are supposed to configure underlying libraries. if(propertyType != null && propertyType.equals(SYSTEM_ATTRIBUTE_TRUE)) { properties.put( newPropBuff.toString(), new PropertyReference(newPropBuff.toString())); System.setProperty(newPropBuff.toString(), value); } else { properties.put(newPropBuff.toString(), value); } } //load child nodes loadNode(currentNode, newPropBuff, properties); } } } /** * Updates the value of the specified node and its children to reflect those * in the properties file. Nodes marked as "system" will be updated from * the specified properties object and not from the system properties since * if any intentional change (through a configuration form) has occurred * it will have been made there. * * @param node the root node that we shold update together with its children * @param propertyNameBuff a StringBuffer containing the prefix describing * the dot separated route to the specified node including its one name * @param properties the dictionary object where the up to date values of * the node should be queried. */ private void updateNode(Node node, StringBuffer propertyNameBuff, Map<String, Object> properties) { Node currentNode = null; NodeList children = node.getChildNodes(); for(int i = 0; i < children.getLength(); i++) { currentNode = children.item(i); if(currentNode.getNodeType() == Node.ELEMENT_NODE) { StringBuffer newPropBuff = new StringBuffer(propertyNameBuff.toString()).append(".") .append(currentNode.getNodeName()); Attr attr = ((Element)currentNode).getAttributeNode(ATTRIBUTE_VALUE); if(attr != null) { //update the corresponding node Object value = properties.get(newPropBuff.toString()); if(value == null) { node.removeChild(currentNode); continue; } boolean isSystem = value instanceof PropertyReference; String prop = isSystem ?((PropertyReference)value).getValue().toString() :value.toString(); attr.setNodeValue(prop); //in case the property has changed to system since the last //load - update the conf file accordingly. if(isSystem) ((Element)currentNode).setAttribute( SYSTEM_ATTRIBUTE_NAME, SYSTEM_ATTRIBUTE_TRUE); else ((Element)currentNode).removeAttribute( SYSTEM_ATTRIBUTE_NAME); } //update child nodes updateNode(currentNode, newPropBuff, properties); } } } /** * Returns the configuration file currently used by the implementation. * If there is no such file or this is the first time we reference it * a new one is created. * @return the configuration File currently used by the implementation. */ private File getConfigurationFile() { if ( configurationFile == null ) configurationFile = createConfigurationFile(); return configurationFile; } /** * Returns the location of the directory where SIP Communicator is to store * user specific data such as configuration files, message and call history * as well as is bundle repository. * * @return the location of the directory where SIP Communicator is to store * user specific data such as configuration files, message and call history * as well as is bundle repository. */ public String getScHomeDirLocation() { //first let's check whether we already have the name of the directory //set as a configuration property String scHomeDirLocation = getString(PNAME_SC_HOME_DIR_LOCATION); if (scHomeDirLocation == null) { //no luck, check whether user has specified a custom name in the //system properties scHomeDirLocation = getSystemProperty(PNAME_SC_HOME_DIR_LOCATION); if (scHomeDirLocation == null) { scHomeDirLocation = getSystemProperty("user.home"); } //now save all this as a configuration property so that we don't //have to look for it in the sys props next time and so that it is //available for other bundles to consult. properties.put(PNAME_SC_HOME_DIR_LOCATION, scHomeDirLocation); } return scHomeDirLocation; } /** * Returns the name of the directory where SIP Communicator is to store user * specific data such as configuration files, message and call history * as well as is bundle repository. * * @return the name of the directory where SIP Communicator is to store * user specific data such as configuration files, message and call history * as well as is bundle repository. */ public String getScHomeDirName() { //first let's check whether we already have the name of the directory //set as a configuration property String scHomeDirName = getString(PNAME_SC_HOME_DIR_NAME); if (scHomeDirName == null) { //no luck, check whether user has specified a custom name in the //system properties scHomeDirName = getSystemProperty(PNAME_SC_HOME_DIR_NAME); if (scHomeDirName == null) { scHomeDirName = ".sip-communicator"; } //now save all this as a configuration property so that we don't //have to look for it in the sys props next time and so that it is //available for other bundles to consult. properties.put(PNAME_SC_HOME_DIR_NAME, scHomeDirName); } return scHomeDirName; } /** * Returns a reference to the configuration file that the service should * load. The method would try to load a file with the name * sip-communicator.xml unless a different one is specified in the system * property net.java.sip.communicator.PROPERTIES_FILE_NAME . The method * would first try to load the file from the current directory if it exists * this is not the case a load would be attempted from the * $HOME/.sip-communicator directory. In case it was not found there either * we'll look for it in all locations currently present in the $CLASSPATH. * In case we find it in there we will copy it to the * $HOME/.sip-communicator directory in case it was in a jar archive and * return the reference to the newly created file. In case the file is * to be found noweher - a new empty file in the user home directory and * returns a link to that one. * * * @return the configuration file currently used by the implementation. */ File createConfigurationFile() { try { //see whether we have a user specified name for the conf file String pFileName = getSystemProperty( FILE_NAME_PROPERTY); if (pFileName == null) { pFileName = "sip-communicator.xml"; } // try to open the file in current directory File configFileInCurrentDir = new File(pFileName); if (configFileInCurrentDir.exists()) { logger.debug("Using config file in current dir: " + configFileInCurrentDir.getCanonicalPath()); return configFileInCurrentDir; } // we didn't find it in ".", try the SIP Communicator home directory // first check whether a custom SC home directory is specified //name of the sip-communicator home directory String scHomeDirName = getScHomeDirName(); //location of the sip-communicator home directory String scHomeDirLocation = getScHomeDirLocation(); File configDir = new File( scHomeDirLocation + File.separator + scHomeDirName); File configFileInUserHomeDir = new File(configDir, pFileName); if (configFileInUserHomeDir.exists()) { logger.debug("Using config file in $HOME/.sip-communicator: " + configFileInCurrentDir.getCanonicalPath()); return configFileInUserHomeDir; } // If we are in a jar - copy config file from jar to user home. logger.trace("Copying config file."); configDir.mkdirs(); InputStream in = getClass().getClassLoader(). getResourceAsStream(pFileName); //Return an empty file if there wasn't any in the jar //null check report from John J. Barton - IBM if (in == null) { configFileInUserHomeDir.createNewFile(); logger.debug("Created an empty file in $HOME: " + configFileInCurrentDir.getCanonicalPath()); return configFileInUserHomeDir; } BufferedReader reader = new BufferedReader(new InputStreamReader(in)); PrintWriter writer = new PrintWriter(new FileWriter( configFileInUserHomeDir)); String line = null; logger.debug("Copying properties file:"); while ( (line = reader.readLine()) != null) { writer.println(line); logger.debug(line); } writer.flush(); return configFileInUserHomeDir; } catch (IOException ex) { logger.error("Error creating config file", ex); return null; } } /** * Creates new entries in the XML <tt>doc</tt> for every element in the * <tt>newProperties</tt> table. * * @param doc the XML <tt>Document</tt> where the new entries should be * created * @param newProperties the table containing the properties that are to be * introduced in the document. */ private void processNewProperties(Document doc, Map<String, Object> newProperties) { for (Map.Entry<String, Object> entry : newProperties.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); boolean isSystem = value instanceof PropertyReference; value = isSystem ?((PropertyReference)value).getValue() :value; processNewProperty(doc, key, value.toString(), isSystem); } } /** * Creates an entry in the xml <tt>doc</tt> for the specified key value * pair. * @param doc the XML <tt>document</tt> to update. * @param key the value of the <tt>name</tt> attribute for the new entry * @param value the value of the <tt>value</tt> attribue for the new * @param isSystem specifies whether this is a system property (system * attribute will be set to true). * entry. */ private void processNewProperty(Document doc, String key, String value, boolean isSystem) { StringTokenizer tokenizer = new StringTokenizer(key, "."); String[] toks = new String[tokenizer.countTokens()]; int i = 0; while(tokenizer.hasMoreTokens()) toks[i++] = tokenizer.nextToken(); String[] chain = new String[toks.length - 1]; for (int j = 0; j < chain.length; j++) { chain[j] = toks[j]; } String nodeName = toks[toks.length - 1]; Element parent = XMLConfUtils.createLastPathComponent(doc, chain); Element newNode = XMLConfUtils.findChild(parent, nodeName); if (newNode == null) { newNode = doc.createElement(nodeName); parent.appendChild(newNode); } newNode.setAttribute("value", value); if(isSystem) newNode.setAttribute(SYSTEM_ATTRIBUTE_NAME, SYSTEM_ATTRIBUTE_TRUE); } /** * Returns the value of the specified java system property. In case the * value was a zero length String or one that only contained whitespaces, * null is returned. This method is for internal use only. Users of the * configuration service are to use the getProperty() or getString() methods * which would automatically determine whether a property is system or not. * @param propertyName the name of the property whose value we need. * @return the value of the property with name propertyName or null if * the value had length 0 or only contained spaces tabs or new lines. */ private static String getSystemProperty(String propertyName) { String retval = System.getProperty(propertyName); if (retval == null){ return retval; } if (retval.trim().length() == 0){ return null; } return retval; } /** * Returns the String value of the specified property (minus all * encompasssing whitespaces)and null in case no property value was mapped * against the specified propertyName, or in case the returned property * string had zero length or contained whitespaces only. * * @param propertyName the name of the property that is being queried. * @return the result of calling the property's toString method and null in * case there was no vlaue mapped against the specified * <tt>propertyName</tt>, or the returned string had zero length or * contained whitespaces only. */ public String getString(String propertyName) { Object propValue = getProperty(propertyName); if (propValue == null) return null; String propStrValue = propValue.toString().trim(); return (propStrValue.length() > 0) ? propStrValue : null; } public boolean getBoolean(String propertyName, boolean defaultValue) { String stringValue = getString(propertyName); return (stringValue == null) ? defaultValue : Boolean .parseBoolean(stringValue); } public int getInt(String propertyName, int defaultValue) { String stringValue = getString(propertyName); int intValue = defaultValue; if (stringValue != null) { try { intValue = Integer.parseInt(stringValue); } catch (NumberFormatException ex) { logger.error(propertyName + " does not appear to be an integer. " + "Defaulting to " + defaultValue + ".", ex); } } return intValue; } /** * We use property references when we'd like to store system properties. * Simply storing System properties in our properties Map would not be * enough since it will lead to mismatching values for the same property in * the System property set and in our local set of properties. Storing them * only in the System property set OTOH is a bit clumsy since it obliges * bundles to use to different configuration property sources. For that * reason, every time we get handed a property labeled as System, in stead * of storing its actual value in the local property set we store a * PropertyReference instance that will retrive it from the system * properties when necessary. */ private class PropertyReference { private String propertyName = null; PropertyReference(String propertyName) { this.propertyName = propertyName; } /** * Return the actual value of the property as recorded in the System * properties. * @return the valued of the property as recorded in the System props. */ public Object getValue() { return System.getProperty(propertyName); } } /** * Returns a copy of the Map containing all configuration properties * @return a Map clone of the current configuration property set. */ private Map<String, Object> cloneProperties() { // at the time I'm writing this method we're implementing the // configuration service through the use of a hashtable. this may very // well change one day so let's not be presumptuous if (properties instanceof Hashtable) return (Map<String, Object>) ((Hashtable) properties).clone(); if (properties instanceof HashMap) return (Map<String, Object>) ((HashMap) properties).clone(); if (properties instanceof TreeMap) return (Map<String, Object>) ((TreeMap) properties).clone(); // well you can't say that I didn't try!!! return new Hashtable<String, Object>(properties); } /** * Determines whether the property with the specified * <tt>propertyName</tt> has been previously declared as System * * @param propertyName the name of the property to verify * @return true if someone at some point specified that property to be * system. (This could have been either through a call to * setProperty(string, true)) or by setting the system attribute in the * xml conf file to true. */ private boolean isSystem(String propertyName) { return properties.containsKey(propertyName) && properties.get(propertyName) instanceof PropertyReference; } /** * Deletes the configuration file currently used by this implementation. */ public void purgeStoredConfiguration() { if (this.configurationFile != null) { configurationFile.delete(); configurationFile = null; } } /** * Goes over all system properties and outputs their names and values for * debug purposes. The method has no effect if the logger is at a log level * other than DEBUG or TRACE (FINE or FINEST). */ private void debugPrintSystemProperties() { if (logger.isDebugEnabled()) { Properties pValues = System.getProperties(); for (Map.Entry<Object, Object> entry : pValues.entrySet()) logger.debug(entry.getKey() + "=" + entry.getValue()); } } /** * The method scans the contents of the SYS_PROPS_FILE_NAME_PROPERTY where * it expects to find a comma separated list of names of files that should * be loaded as system properties. The method then parses these files and * loads their contents as system properties. All such files have to be in * a location that's in the classpath. */ public void preloadSystemPropertyFiles() { String propertyFilesListStr = System.getProperty( SYS_PROPS_FILE_NAME_PROPERTY ); if(propertyFilesListStr == null || propertyFilesListStr.trim().length() == 0) return; StringTokenizer tokenizer = new StringTokenizer(propertyFilesListStr, ";,", false); while( tokenizer.hasMoreTokens()) { String fileName = tokenizer.nextToken(); try { fileName = fileName.trim(); Properties fileProps = new Properties(); fileProps.load(ClassLoader.getSystemResourceAsStream(fileName)); // now set all of this file's properties as system properties for (Map.Entry<Object, Object> entry : fileProps.entrySet()) System.setProperty((String) entry.getKey(), (String) entry .getValue()); } catch (Exception ex) { //this is an insignificant method that should never affect //the rest of the application so we'll afford ourselves to //kind of silence all possible exceptions (which would most //often be IOExceptions). We will however log them in case //anyone would be interested. logger.error("Failed to load property file: " + fileName , ex); } } } }