
org.jitsi.impl.configuration.xml.XMLConfigurationStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of libjitsi Show documentation
Show all versions of libjitsi Show documentation
libjitsi is an advanced Java media library for secure real-time audio/video
communication
The newest version!
/*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.jitsi.impl.configuration.xml;
import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
import org.jitsi.impl.configuration.*;
import org.jitsi.util.xml.*;
import org.jitsi.utils.logging.*;
import org.w3c.dom.*;
import org.xml.sax.*;
/**
* Implements a ConfigurationStore
which serializes property
* name-value associations in XML format.
*
* @author Emil Ivov
* @author Damian Minkov
* @author Lubomir Marinov
*/
public class XMLConfigurationStore
implements ConfigurationStore
{
/**
* The Logger used by the XMLConfigurationStore class and
* its instances for logging output.
*/
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 Hashtable 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.
*/
@SuppressWarnings("unchecked")
private Map cloneProperties()
{
return (Map) properties.clone();
}
/**
* Creates a new runtime XML document which is to contain the properties
* managed by this ConfigurationStore.
*
* @return a new runtime XML Document which is to contain the
* properties managed by this ConfigurationStore
*/
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;
}
/**
* Implements {@link ConfigurationStore#getProperty(String)}. Gets the value
* in this ConfigurationStore
of a property with a specific
* name.
*
* @param propertyName the name of the property to get the value of
* @return the value in this ConfigurationStore of the property
* with the specified name; null if the property with the specified
* name does not have an association with a value in this
* ConfigurationStore
* @see ConfigurationStore#getProperty(String)
*/
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;
}
/**
* Implements {ConfigurationStore#getPropertyNames()}. Gets the names of the
* properties which have values associated in this
* ConfigurationStore.
*
* @return an array of Strings which specify the names of the
* properties that have values associated in this
* ConfigurationStore; an empty array if this instance contains no
* property values
* @see ConfigurationStore#getPropertyNames()
*/
public String[] getPropertyNames()
{
Set propertyNames = properties.keySet();
return propertyNames.toArray(new String[propertyNames.size()]);
}
/**
* Implements {ConfigurationStore#isSystemProperty(String)}. Determines
* whether a specific name stands for a system property.
*
* @param propertyName the name of a property which is to be determined
* whether it is a system property
* @return true if the specified name stands for a system property;
* false, otherwise
* @see ConfigurationStore#isSystemProperty(String)
*/
public boolean isSystemProperty(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
= XMLUtils.newDocumentBuilderFactory();
DocumentBuilder builder = factory.newDocumentBuilder();
Map props = new Hashtable();
//if the file is empty (or contains only sth insignificant)
//ignore 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)
{
String propertyName
= DOMElementWriter.decodeName(
currentNode.getNodeName());
loadNode(currentNode, propertyName, props);
}
}
return props;
}
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 propertyName a String containing the prefix describing the route
* to the specified node including its one name
* @param props the dictionary object where all properties extracted
* from this node and its children should be recorded.
*/
private void loadNode(Node node,
String propertyName,
Map props)
{
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)
{
String newProp
= propertyName
+ "."
+ DOMElementWriter.decodeName(
currentNode.getNodeName());
String value = XMLConfUtils.getAttribute(
currentNode, ATTRIBUTE_VALUE);
// the value attr is present we must handle the desired property
if(value != null)
{
String propertyType
= XMLConfUtils.getAttribute(
currentNode,
SYSTEM_ATTRIBUTE_NAME);
//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))
{
props.put(newProp, new PropertyReference(newProp));
System.setProperty(newProp, value);
}
else
props.put(newProp, value);
}
//load child nodes
loadNode(currentNode, newProp, props);
}
}
}
/**
* 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())
{
Object value = entry.getValue();
boolean system;
if (system = (value instanceof PropertyReference))
value = ((PropertyReference) value).getValue();
processNewProperty(doc, entry.getKey(), value.toString(), system);
}
}
/**
* 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 attribute for the new entry
* @param isSystem specifies whether this is a system property (system
* attribute will be set to true).
*/
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++] = DOMElementWriter.encodeName(tokenizer.nextToken());
String nodeName = toks[toks.length - 1];
Element parent
= XMLConfUtils.createLastPathComponent(doc, toks, toks.length - 1);
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);
}
/**
* Implements {@link ConfigurationStore#reloadConfiguration(File)}. Removes
* all property name-value associations currently present in this
* ConfigurationStore and deserializes new property name-value
* associations from a specific File which presumably is in the
* format represented by this instance.
*
* @param file the File to be read and to deserialize new property
* name-value associations from into this instance
* @throws IOException if there is an input error while reading from the
* specified file
* @throws XMLException if parsing the contents of the specified
* file fails
* @see ConfigurationStore#reloadConfiguration(File)
*/
public void reloadConfiguration(File file)
throws IOException,
XMLException
{
properties = new Hashtable();
fileExtractedProperties = loadConfiguration(file);
properties.putAll(fileExtractedProperties);
}
/**
* Implements {@link ConfigurationStore#removeProperty(String)}. Removes the
* value association in this ConfigurationStore of the property
* with a specific name. If the property with the specified name is not
* associated with a value in this ConfigurationStore, does
* nothing.
*
* @param propertyName the name of the property which is to have its value
* association in this ConfigurationStore removed
* @see ConfigurationStore#removeProperty(String)
*/
public void removeProperty(String propertyName)
{
properties.remove(propertyName);
fileExtractedProperties.remove(propertyName);
}
/**
* Implements
* {@link ConfigurationStore#setNonSystemProperty(String, Object)}. Sets the
* value of a non-system property with a specific name to a specific value
* in this ConfigurationStore.
*
* @param propertyName the name of the non-system property to be set to the
* specified value in this ConfigurationStore
* @param property the value to be assigned to the non-system property with the
* specified name in this ConfigurationStore
* @see ConfigurationStore#setNonSystemProperty(String, Object)
*/
public void setNonSystemProperty(String propertyName, Object property)
{
properties.put(propertyName, property);
}
/**
* Implements {@link ConfigurationStore#setSystemProperty(String)}. Sets a
* property with a specific name to be considered a system property by the
* ConfigurationStore.
*
* @param propertyName the name of the property to be set as a system
* property in this ConfigurationStore
* @see ConfigurationStore#setSystemProperty(String)
*/
public void setSystemProperty(String propertyName)
{
setNonSystemProperty(propertyName, new PropertyReference(propertyName));
}
/**
* Implements {@link ConfigurationStore#storeConfiguration(OutputStream)}.
* Stores/serializes the property name-value associations currently present
* in this ConfigurationStore into a specific OutputStream
* in the format represented by this instance.
*
* @param out the OutputStream to receive the serialized form of
* the property name-value associations currently present in this
* ConfigurationStore
* @see ConfigurationStore#storeConfiguration(OutputStream)
*/
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();
NodeList children = root.getChildNodes();
for (int i = 0; i < children.getLength(); i++)
{
Node currentNode = children.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE)
{
String propertyName
= DOMElementWriter.decodeName(currentNode.getNodeName());
updateNode(currentNode, propertyName, 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 should update together with its
* children
* @param propertyName a String containing the prefix describing the
* dot-separated route to the specified node including its one name
* @param props the dictionary object where the up to date values of the
* node should be queried.
*/
private void updateNode(Node node, String propertyName,
Map props)
{
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)
{
String newProp
= propertyName
+ "."
+ DOMElementWriter.decodeName(
currentNode.getNodeName());
Attr attr
= ((Element) currentNode).getAttributeNode(ATTRIBUTE_VALUE);
if (attr != null)
{
// update the corresponding node
Object value = props.get(newProp);
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, newProp, props);
}
}
}
/**
* 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, instead
* 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
{
/**
* The name of the system property represented by this instance.
*/
private final String propertyName;
/**
* Initializes a new PropertyReference instance which is to
* represent a system property with a specific name.
*
* @param propertyName the name of the system property to be represented
* by the new instance
*/
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);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy