diff --git a/src/net/java/sip/communicator/impl/configuration/ConfigurationServiceImpl.java b/src/net/java/sip/communicator/impl/configuration/ConfigurationServiceImpl.java index ee46df178..caf560659 100644 --- a/src/net/java/sip/communicator/impl/configuration/ConfigurationServiceImpl.java +++ b/src/net/java/sip/communicator/impl/configuration/ConfigurationServiceImpl.java @@ -9,9 +9,6 @@ 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.*; @@ -19,8 +16,6 @@ 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 @@ -37,23 +32,6 @@ public class ConfigurationServiceImpl { 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. @@ -75,21 +53,6 @@ public class ConfigurationServiceImpl private final ChangeEventDispatcher changeEventDispatcher = new ChangeEventDispatcher(this); - /** - * The list of properties currently registered in the configuration service. - */ - private Map properties = new Hashtable(); - - /** - * 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 fileExtractedProperties = - new Hashtable(); - /** * Indicates whether the service is started or stopped. */ @@ -100,6 +63,8 @@ public class ConfigurationServiceImpl */ private FileAccessService faService = null; + private final ConfigurationStore store = new PropertyConfigurationStore(); + /** * Sets the property with the specified name to the specified value. Calling * this method would first trigger a PropertyChangeEvent that will @@ -153,7 +118,7 @@ public void setProperty(String propertyName, Object property, logger.trace(propertyName + "( oldValue=" + oldValue + ", newValue=" + property + "."); - //once set system, a property remains system event if the user + //once set system, a property remains system even if the user //specified sth else if (isSystem(propertyName)) @@ -161,9 +126,7 @@ public void setProperty(String propertyName, Object property, if (property == null) { - properties.remove(propertyName); - - fileExtractedProperties.remove(propertyName); + store.removeProperty(propertyName); if (isSystem) { @@ -178,12 +141,11 @@ public void setProperty(String propertyName, Object property, //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)); + store.setSystemProperty(propertyName); } else { - properties.put(propertyName, property); + store.setNonSystemProperty(propertyName, property); } } if (changeEventDispatcher.hasPropertyChangeListeners(propertyName)) @@ -210,7 +172,6 @@ public void setProperty(String propertyName, Object property, * All properties with prefix propertyName will also be removed. *

* @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. */ @@ -237,9 +198,7 @@ public void removeProperty(String propertyName) logger.trace("Will remove prop: " + propertyName + "."); - properties.remove(propertyName); - - fileExtractedProperties.remove(propertyName); + store.removeProperty(propertyName); if (changeEventDispatcher.hasPropertyChangeListeners(propertyName)) changeEventDispatcher.firePropertyChange( @@ -259,19 +218,13 @@ public void removeProperty(String propertyName) /** * 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; + return store.getProperty(propertyName); } /** @@ -308,7 +261,7 @@ public List getPropertyNamesByPrefix(String prefix, boolean exactPrefixM { List resultKeySet = new LinkedList(); - for (String key : properties.keySet()) + for (String key : store.getPropertyNames()) { int ix = key.lastIndexOf('.'); @@ -468,27 +421,10 @@ void start() public void reloadConfiguration() throws IOException, XMLException { - properties = new Hashtable(); this.configurationFile = null; - fileExtractedProperties = - loadConfiguration(getConfigurationFile()); - this.properties.putAll(fileExtractedProperties); - } + File file = getConfigurationFile(); - /** - * 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 loadConfiguration(File file) - throws IOException, XMLException - { // restore the file if needed FailSafeTransaction trans = this.faService .createFailSafeTransaction(file); @@ -498,51 +434,8 @@ Map loadConfiguration(File file) logger.error("can't restore the configuration file before loading" + " it", e); } - - try - { - DocumentBuilderFactory factory = - DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Map properties = new Hashtable(); - - //if the file is empyt (or contains only sth insignificant) - //ifnore it and create a new document. - if(file.length() < "".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(); - } + store.reloadConfiguration(file); } public synchronized void storeConfiguration() @@ -551,214 +444,37 @@ public synchronized void storeConfiguration() 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. + * be stored. * @throws IOException if there was a problem writing to the specified file. */ - private void storeConfiguration(File file) - throws IOException + 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. + if (!started) + throw new IllegalStateException( + "Service is stopped or has not been started"); - Map newlyAddedProperties = cloneProperties(); - - //remove those that were originally there; - for (String propName : fileExtractedProperties.keySet()) - newlyAddedProperties.remove(propName); - - this.processNewProperties(propertiesDocument, - newlyAddedProperties); - - //write the file. + // write the file. File config = getConfigurationFile(); - FailSafeTransaction trans = this.faService - .createFailSafeTransaction(config); - try { + 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 properties) - { - Node currentNode = null; - NodeList children = node.getChildNodes(); - for(int i = 0; i < children.getLength(); i++) - { - currentNode = children.item(i); + store.storeConfiguration(stream); - 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); - } + stream.close(); + trans.commit(); } - } - - /** - * 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 properties) - { - Node currentNode = null; - NodeList children = node.getChildNodes(); - for(int i = 0; i < children.getLength(); i++) + catch (Exception e) { - 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); - } + logger.error("can't write data in the configuration file", e); + trans.rollback(); } } @@ -805,8 +521,9 @@ public String getScHomeDirLocation() //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); + // available for other bundles to consult. + store.setNonSystemProperty(PNAME_SC_HOME_DIR_LOCATION, + scHomeDirLocation); } return scHomeDirLocation; @@ -841,8 +558,8 @@ public String getScHomeDirName() //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); + // available for other bundles to consult. + store.setNonSystemProperty(PNAME_SC_HOME_DIR_NAME, scHomeDirName); } return scHomeDirName; @@ -931,7 +648,7 @@ File createConfigurationFile() PrintWriter writer = new PrintWriter(new FileWriter( configFileInUserHomeDir)); - String line = null; + String line; logger.debug("Copying properties file:"); while ( (line = reader.readLine()) != null) { @@ -948,73 +665,6 @@ File createConfigurationFile() } } - /** - * Creates new entries in the XML doc for every element in the - * newProperties table. - * - * @param doc the XML Document 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 newProperties) - { - for (Map.Entry 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 doc for the specified key value - * pair. - * @param doc the XML document to update. - * @param key the value of the name attribute for the new entry - * @param value the value of the value 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, @@ -1092,59 +742,6 @@ public int getInt(String propertyName, int defaultValue) 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 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) ((Hashtable) properties).clone(); - if (properties instanceof HashMap) - return (Map) ((HashMap) properties).clone(); - if (properties instanceof TreeMap) - return (Map) ((TreeMap) properties).clone(); - - // well you can't say that I didn't try!!! - - return new Hashtable(properties); - } - /** * Determines whether the property with the specified * propertyName has been previously declared as System @@ -1157,8 +754,7 @@ private Map cloneProperties() */ private boolean isSystem(String propertyName) { - return properties.containsKey(propertyName) - && properties.get(propertyName) instanceof PropertyReference; + return store.isSystem(propertyName); } /** diff --git a/src/net/java/sip/communicator/impl/configuration/ConfigurationStore.java b/src/net/java/sip/communicator/impl/configuration/ConfigurationStore.java new file mode 100644 index 000000000..1aa96aeb5 --- /dev/null +++ b/src/net/java/sip/communicator/impl/configuration/ConfigurationStore.java @@ -0,0 +1,33 @@ +/* + * 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 net.java.sip.communicator.util.xml.*; + +/** + * @author Lubomir Marinov + */ +public interface ConfigurationStore +{ + public Object getProperty(String name); + + public String[] getPropertyNames(); + + public boolean isSystem(String name); + + public void reloadConfiguration(File file) throws IOException, XMLException; + + public void removeProperty(String name); + + public void setNonSystemProperty(String name, Object value); + + public void setSystemProperty(String name); + + public void storeConfiguration(OutputStream out) throws IOException; +} diff --git a/src/net/java/sip/communicator/impl/configuration/PropertyConfigurationStore.java b/src/net/java/sip/communicator/impl/configuration/PropertyConfigurationStore.java new file mode 100644 index 000000000..8d7fb33df --- /dev/null +++ b/src/net/java/sip/communicator/impl/configuration/PropertyConfigurationStore.java @@ -0,0 +1,73 @@ +/* + * 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.*; + +/** + * @author Lubomir Marinov + */ +public class PropertyConfigurationStore + implements ConfigurationStore +{ + private final Properties properties = new Properties(); + + public Object getProperty(String name) + { + return properties.get(name); + } + + public String[] getPropertyNames() + { + synchronized (properties) + { + Set propertyNames = properties.keySet(); + return propertyNames.toArray(new String[propertyNames.size()]); + } + } + + public boolean isSystem(String name) + { + return (System.getProperty(name) != null); + } + + public void reloadConfiguration(File file) throws IOException + { + properties.clear(); + + InputStream in = new BufferedInputStream(new FileInputStream(file)); + try + { + properties.load(in); + } + finally + { + in.close(); + } + } + + public void removeProperty(String name) + { + properties.remove(name); + } + + public void setNonSystemProperty(String name, Object value) + { + properties.setProperty(name, value.toString()); + } + + public void setSystemProperty(String name) + { + removeProperty(name); + } + + public void storeConfiguration(OutputStream out) throws IOException + { + properties.store(out, null); + } +} diff --git a/src/net/java/sip/communicator/impl/configuration/xml/XMLConfigurationStore.java b/src/net/java/sip/communicator/impl/configuration/xml/XMLConfigurationStore.java new file mode 100644 index 000000000..c292227e3 --- /dev/null +++ b/src/net/java/sip/communicator/impl/configuration/xml/XMLConfigurationStore.java @@ -0,0 +1,473 @@ +/* + * 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.xml; + +import java.io.*; +import java.util.*; + +import javax.xml.parsers.*; + +import org.w3c.dom.*; +import org.xml.sax.*; + +import net.java.sip.communicator.impl.configuration.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.xml.*; + +public class XMLConfigurationStore + implements ConfigurationStore +{ + private static final Logger logger = + Logger.getLogger(XMLConfigurationStore.class); + + /** 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 list of properties currently registered in the configuration service. + */ + private Map properties = new Hashtable(); + + /** + * 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 fileExtractedProperties = + new Hashtable(); + + /** + * The XML Document containing the configuration file this service loaded. + */ + private Document propertiesDocument; + + /** + * Returns a copy of the Map containing all configuration properties + * @return a Map clone of the current configuration property set. + */ + private Map 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) ((Hashtable) properties).clone(); + if (properties instanceof HashMap) + return (Map) ((HashMap) properties).clone(); + if (properties instanceof TreeMap) + return (Map) ((TreeMap) properties).clone(); + + // well you can't say that I didn't try!!! + + return new Hashtable(properties); + } + + 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; + } + + 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; + } + + public String[] getPropertyNames() + { + Set propertyNames = properties.keySet(); + return propertyNames.toArray(new String[propertyNames.size()]); + } + + public boolean isSystem(String propertyName) + { + return properties.get(propertyName) instanceof PropertyReference; + } + + /** + * 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. + */ + private Map loadConfiguration(File file) + throws IOException, + XMLException + { + try + { + DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Map properties = new Hashtable(); + + //if the file is empty (or contains only sth insignificant) + //ifnore it and create a new document. + if(file.length() < "".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(); + } + } + + /** + * 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 should 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 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); + } + } + } + + /** + * Creates new entries in the XML doc for every element in the + * newProperties table. + * + * @param doc the XML Document 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 newProperties) + { + for (Map.Entry 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 doc for the specified key value + * pair. + * @param doc the XML document to update. + * @param key the value of the name attribute for the new entry + * @param value the value of the value 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); + } + + public void reloadConfiguration(File file) throws IOException, XMLException + { + properties = new Hashtable(); + + fileExtractedProperties = loadConfiguration(file); + properties.putAll(fileExtractedProperties); + } + + public void removeProperty(String propertyName) + { + properties.remove(propertyName); + + fileExtractedProperties.remove(propertyName); + } + + public void setNonSystemProperty(String propertyName, Object property) + { + properties.put(propertyName, property); + } + + public void setSystemProperty(String propertyName) + { + setNonSystemProperty(propertyName, new PropertyReference(propertyName)); + } + + public void storeConfiguration(OutputStream out) + { + // 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 newlyAddedProperties = cloneProperties(); + + // remove those that were originally there; + for (String propName : fileExtractedProperties.keySet()) + newlyAddedProperties.remove(propName); + + this.processNewProperties(propertiesDocument, newlyAddedProperties); + + XMLUtils.indentedWriteXML(propertiesDocument, out); + } + + /** + * 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 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); + } + } + } + + /** + * 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 retrieve it from the system + * properties when necessary. + */ + private static class PropertyReference + { + private final String propertyName; + + public 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); + } + } +} diff --git a/src/net/java/sip/communicator/plugin/ircaccregwizz/FirstWizardPage.java b/src/net/java/sip/communicator/plugin/ircaccregwizz/FirstWizardPage.java index ed0128db3..c8acab7fa 100644 --- a/src/net/java/sip/communicator/plugin/ircaccregwizz/FirstWizardPage.java +++ b/src/net/java/sip/communicator/plugin/ircaccregwizz/FirstWizardPage.java @@ -9,6 +9,7 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.*; import javax.swing.*; import javax.swing.event.*; @@ -317,8 +318,7 @@ private void setNextButtonAccordingToUserID() || nickField.getText().equals("") || serverField.getText() == null || serverField.getText().equals("") - || (!passwordNotRequired.isSelected() - && passField.getText().equals(""))) + || (!passwordNotRequired.isSelected() && isEmpty(passField))) { wizard.getWizardContainer().setNextFinishButtonEnabled(false); } @@ -328,6 +328,27 @@ private void setNextButtonAccordingToUserID() } } + private boolean isEmpty(JPasswordField passField) + { + if (passField.getDocument() != null) + { + char[] pass = passField.getPassword(); + + if (pass != null) + { + + /* + * The Javadoc of JPasswordField.getPassword() recommends + * clearing the returned character array for stronger security + * by setting each character to zero + */ + Arrays.fill(pass, '\0'); + return (pass.length <= 0); + } + } + return true; + } + /** * Handles the DocumentEvent triggered when user types in the * User ID field. Enables or disables the "Next" wizard button according to