/**
* '$RCSfile: ConfigXML.java,v $'
* Copyright: 2000 Regents of the University of California and the
* National Center for Ecological Analysis and Synthesis
* Authors: @authors@
* Release: @release@
*
* '$Author: leinfelder $'
* '$Date: 2014-06-02 19:16:01 +0000 (Mon, 02 Jun 2014) $'
* '$Revision: 172 $'
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.ucsb.nceas.utilities.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import edu.ucsb.nceas.utilities.config.exception.ElementNotFoundException;
import edu.ucsb.nceas.utilities.config.exception.IndexTooLargeException;
/**
* This class is designed to store configuration information in
* an XML file. The concept is similar to that of a Properties
* file except that using the XML format allows for a hierarchy
* of properties and repeated properties.
*
* All 'keys' are element names, while values are always stored
* as XML text nodes. The XML file is parsed and stored in
* memory as a DOM object.
*
* Note that nodes are specified by node tags rather than paths
*/
public class ConfigXML
{
/**
* root node of the in-memory DOM structure
*/
private Node root;
/**
* Document node of the in-memory DOM structure
*/
private Document doc;
/**
* XML file name in string form
*/
private String fileName;
/**
* Print writer (output)
*/
private PrintWriter out;
private static final String configDirectory = ".morpho";
/**
* Set up a DOM parser for reading an XML document
*
* @return a DOM parser object for parsing
*/
private static DocumentBuilder createDomParser() throws Exception
{
DocumentBuilder parser = null;
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
parser = factory.newDocumentBuilder();
if (parser == null)
{
throw new Exception("Could not create Document parser in " +
"MonarchUtil.DocumentBuilder");
}
}
catch (ParserConfigurationException pce)
{
throw new Exception("Could not create Document parser in " +
"MonarchUtil.DocumentBuilder: " + pce.getMessage());
}
return parser;
}
/**
* String passed to the creator is the XML config file name
*
* @param filename name of XML file
*/
public ConfigXML(String filename) throws FileNotFoundException, Exception
{
this.fileName = filename;
DocumentBuilder parser = createDomParser();
File XMLConfigFile = new File(filename);
InputSource in;
FileInputStream fs;
fs = new FileInputStream(filename);
in = new InputSource(fs);
try
{
doc = parser.parse(in);
fs.close();
} catch(Exception e1) {
throw e1;
}
root = doc.getDocumentElement();
}
/**
* String passed to the creator is the XML config file name
*
* @param input stream containing the XML configuration data
*/
public ConfigXML(InputStream configStream) throws FileNotFoundException,
Exception
{
DocumentBuilder parser = createDomParser();
InputSource in;
in = new InputSource(configStream);
try
{
doc = parser.parse(in);
configStream.close();
} catch(Exception e1) {
throw e1;
}
root = doc.getDocumentElement();
}
/**
* Gets the value(s) corresponding to a key string (i.e. the
* value(s) for a named parameter.
*
* @param key 'key' is element name.
* @return Returns a Vector of strings because may have repeated elements
* @throws ElementNotFoundException if key is not found
*/
public Vector get(String key) throws ElementNotFoundException
{
NodeList nl = doc.getElementsByTagName(key);
Vector result = new Vector();
if (nl.getLength() < 1)
{
throw new ElementNotFoundException("Element " + key + " not found");
}
for (int i = 0; i < nl.getLength(); i++)
{
Node cn = nl.item(i).getFirstChild(); // assume 1st child is text node
if ((cn != null) && (cn.getNodeType() == Node.TEXT_NODE))
{
String temp = cn.getNodeValue();
result.addElement(temp.trim());
}
}
return result;
}
/**
* Gets the value(s) corresponding to a key string (i.e. the
* value(s) for a named parameter.
*
* @param key 'key' is element name.
* @param i zero based index of elements with the name stored in key
* @return String value of the ith element with name in 'key'
* @throws ElementNotFoundException if key is not found at position i
*/
public String get(String key, int i) throws ElementNotFoundException
{
NodeList nl = doc.getElementsByTagName(key);
String result = null;
if (nl.getLength() < 1)
{
throw new ElementNotFoundException("Element " + key +
" not found at position " + i);
}
if (nl.getLength() < i)
{
throw new ElementNotFoundException("Element " +
key + " not found at position " + i);
}
Node cn = nl.item(i).getFirstChild(); // assume 1st child is text node
if ((cn != null) && (cn.getNodeType() == Node.TEXT_NODE))
{
result = (cn.getNodeValue().trim());
}
return result;
}
/**
* used to set a value corresponding to 'key'; value is changed
* in DOM structure in memory
*
* @param key 'key' is element name.
* @param i index in set of elements with 'key' name
* @param value new value to be inserted in ith key
* @throws ElementNotFoundException if key is not found at position i
*/
public void set(String key, int i, String value)
throws ElementNotFoundException
{
boolean result = false;
NodeList nl = doc.getElementsByTagName(key);
if (nl.getLength() <= i) {
throw new ElementNotFoundException("Cannot set key " + key +
" at position " + i +
" either because it does not exist " +
"or because the there are not " + i +
" elements by that name.");
} else {
Node cn = nl.item(i).getFirstChild(); // assumed to be a text node
if (cn == null) {
// No text node, so append one with the value
Node newText = doc.createTextNode(value);
nl.item(i).appendChild(newText);
} else if (cn.getNodeType() == Node.TEXT_NODE) {
// found the text node, so change its value
cn.setNodeValue(value);
}
}
}
/**
* Inserts another node before the first element with
* the name contained in 'key', otherwise appends it
* to the end of the config file (last element in root node)
*
* @param key element name which will be duplicated
* @param value value for new element
*/
public void insert(String key, String value)
{
// Create the new element, with its text value child
Node newElem = doc.createElement(key);
Node newText = doc.createTextNode(value);
newElem.appendChild(newText);
// Determine if there are existing elements of the same name
NodeList nl = doc.getElementsByTagName(key);
// If so, insert new element before existing
if (nl.getLength() > 0) {
Node nnn = nl.item(0);
Node parent = nnn.getParentNode();
//insert newElem before nnn
parent.insertBefore(newElem, nnn);
// Otherwise, append new element to end of root
} else {
root.appendChild(newElem);
}
}
/**
* Add a sub field to an existing config field
*
* @param parentName name of parent element
* @param i index of parent element
* @param childName element name of new child
* @param value value of new child
* @throws IndexTooLargeException if i is larger than the number of nodes
* of parentName
*/
public void addSubField(String parentName, int i, String childName, String value)
throws IndexTooLargeException
{
NodeList nl = doc.getElementsByTagName(parentName);
if (nl.getLength() > 0)
{
if (nl.getLength() <= i)
{
throw new IndexTooLargeException("Error setting XMLConfig value: " +
"index too large");
}
else
{
Node parent = nl.item(i);
Node newElem = doc.createElement(childName);
Node newText = doc.createTextNode(value);
//add text to element
newElem.appendChild(newText);
//add newElem to parent
parent.appendChild(newElem);
}
}
}
/**
* deletes indicated field
*
* @param nodeName field to delete
* @param i node index
* @throws IndexTooLargeException if i is larger than the number of nodeName
* nodes.
*/
public void delete(String nodeName, int i) throws IndexTooLargeException
{
NodeList nl = doc.getElementsByTagName(nodeName);
if (nl.getLength() > 0)
{
if (nl.getLength() <= i)
{
throw new IndexTooLargeException("Error removing XMLConfig value: " +
"index too large");
}
else
{
Node nnn = nl.item(i);
Node parent = nnn.getParentNode();
parent.removeChild(nnn);
}
}
}
/**
* removes all subfields within specified super field
*
* @param parentName the super field
* @param i index of super field
*/
public void deleteSubFields(String parentName, int i)
throws IndexTooLargeException
{
NodeList nl = doc.getElementsByTagName(parentName);
if (nl.getLength() > 0)
{
if (nl.getLength() <= i)
{
throw new IndexTooLargeException("Error setting XMLConfig value: " +
"index too large");
}
else
{
Node parent = nl.item(i);
NodeList nlchildren = parent.getChildNodes();
int numchildren = nlchildren.getLength();
for (int k = 0; k < numchildren; k++)
{
Node temp = nlchildren.item(0);
parent.removeChild(temp);
}
}
}
}
/**
* Assume that there is some parent node which has a subset of
* child nodes that are repeated e.g.
*
* xxx
* qqq
* yyy
* www
* ...
*
*
* this method will return a Hashtable of names-values of parent
* @param field the name of the field in which the name/value pair resides
* @param name the name sub field in the name/value pair
* @param value the value sub field in the name/value pair
* @throws ElementNotFoundException if the parentName field is not found
*/
public Hashtable getNameValuePairs(String field, String name,
String value)
throws ElementNotFoundException
{
try
{
Hashtable h = getNameValuePairs(field, name, value, 0);
return h;
}
catch(IndexTooLargeException e)
{
throw new ElementNotFoundException("The element " + field +
" does not exist.");
}
}
/**
* Assume that there is some parent node which has a subset of
* child nodes that are repeated e.g.
*
* xxx
* qqq
* yyy
* www
* ...
*
*
* this method will return a Hashtable of names-values of parent
* @param field the name of the parent field of the name/value pair
* @param name the name node in the name/value pair
* @param value the value node in the name/value pair
* @param i the index of the parent that you want in the node list.
* @throws IndexTooLargeException if there are less than i parentName nodes
*/
public Hashtable getNameValuePairs(String field, String name,
String value, int i)
throws IndexTooLargeException
{
String keyval = "";
String valval = "";
Hashtable ht = new Hashtable();
NodeList nl = doc.getElementsByTagName(field);
if (nl.getLength() > 0)
{
if(nl.getLength() <= i)
{
throw new IndexTooLargeException("There are not " + i + " nodes in " +
"the resultset.");
}
// always use the first parent
NodeList children = nl.item(i).getChildNodes();
if (children.getLength() > 0)
{
for (int j = 0; j < children.getLength(); j++)
{
Node cn = children.item(j);
if ((cn.getNodeType() == Node.ELEMENT_NODE)
&& (cn.getNodeName().equalsIgnoreCase(name)))
{
Node ccn = cn.getFirstChild(); // assumed to be a text node
if ((ccn != null) && (ccn.getNodeType() == Node.TEXT_NODE))
{
keyval = ccn.getNodeValue();
}
}
if ((cn.getNodeType() == Node.ELEMENT_NODE)
&& (cn.getNodeName().equalsIgnoreCase(value)))
{
Node ccn = cn.getFirstChild(); // assumed to be a text node
if ((ccn != null) && (ccn.getNodeType() == Node.TEXT_NODE))
{
valval = ccn.getNodeValue();
ht.put(keyval, valval);
}
}
}
}
}
return ht;
}
/**
* utility routine to return the value(s) of a node defined by
* a specified XPath
* @param pathstring the path to the requested nodes
*/
public Vector getValuesForPath(String pathstring)
throws ElementNotFoundException, Exception
{
Vector val = new Vector();
if (!pathstring.startsWith("/"))
{
pathstring = "//*/"+pathstring;
}
try
{
NodeList nl = null;
nl = XPathAPI.selectNodeList(doc, pathstring);
if ((nl!=null)&&(nl.getLength()>0))
{
// loop over node list is needed if node is repeated
for (int k=0;k");
print(nd);
out.close();
}
}
/**
* This method can 'print' any DOM subtree. Specifically it is
* set (by means of 'out') to write the in-memory DOM to the
* same XML file that was originally read. Action thus saves
* a new version of the XML doc
*
* @param node node usually set to the 'doc' node for complete XML file
* re-write
*/
private void print(Node node)
{
// is there anything to do?
if (node == null)
{
return;
}
int type = node.getNodeType();
switch (type)
{
// print document
case Node.DOCUMENT_NODE:
{
out.println("");
print(((Document) node).getDocumentElement());
out.flush();
break;
}
// print element with attributes
case Node.ELEMENT_NODE:
{
out.print('<');
out.print(node.getNodeName());
Attr attrs[] = sortAttributes(node.getAttributes());
for (int i = 0; i < attrs.length; i++)
{
Attr attr = attrs[i];
out.print(' ');
out.print(attr.getNodeName());
out.print("=\"");
out.print(normalize(attr.getNodeValue()));
out.print('"');
}
out.print('>');
NodeList children = node.getChildNodes();
if (children != null)
{
int len = children.getLength();
for (int i = 0; i < len; i++)
{
print(children.item(i));
}
}
break;
}
// handle entity reference nodes
case Node.ENTITY_REFERENCE_NODE:
{
out.print('&');
out.print(node.getNodeName());
out.print(';');
break;
}
// print cdata sections
case Node.CDATA_SECTION_NODE:
{
out.print("");
break;
}
// print text
case Node.TEXT_NODE:
{
out.print(normalize(node.getNodeValue()));
break;
}
// print processing instruction
case Node.PROCESSING_INSTRUCTION_NODE:
{
out.print("");
out.print(node.getNodeName());
String data = node.getNodeValue();
if (data != null && data.length() > 0)
{
out.print(' ');
out.print(data);
}
out.print("?>");
break;
}
}
if (type == Node.ELEMENT_NODE)
{
out.print("");
out.print(node.getNodeName());
out.print('>');
}
out.flush();
}
/** Returns a sorted list of attributes. */
private Attr[] sortAttributes(NamedNodeMap attrs)
{
int len = (attrs != null) ? attrs.getLength() : 0;
Attr array[] = new Attr[len];
for (int i = 0; i < len; i++)
{
array[i] = (Attr) attrs.item(i);
}
for (int i = 0; i < len - 1; i++)
{
String name = array[i].getNodeName();
int index = i;
for (int j = i + 1; j < len; j++)
{
String curName = array[j].getNodeName();
if (curName.compareTo(name) < 0)
{
name = curName;
index = j;
}
}
if (index != i)
{
Attr temp = array[i];
array[i] = array[index];
array[index] = temp;
}
}
return (array);
} // sortAttributes(NamedNodeMap):Attr[]
/** Normalizes the given string. */
private String normalize(String s)
{
StringBuffer str = new StringBuffer();
int len = (s != null) ? s.length() : 0;
for (int i = 0; i < len; i++)
{
char ch = s.charAt(i);
switch (ch)
{
case '<':
{
str.append("<");
break;
}
case '>':
{
str.append(">");
break;
}
case '&':
{
str.append("&");
break;
}
case '"':
{
str.append(""");
break;
}
case '\r':
case '\n':
{
// else, default append char
}
default:
{
str.append(ch);
}
}
}
return (str.toString());
}
/**
* Determine the home directory in which configuration files should be located
*
* @return String name of the path to the configuration directory
*/
public static String getConfigDirectory() {
return System.getProperty("user.home") + File.separator + configDirectory;
}
}