/*- * Copyright © 2009 Diamond Light Source Ltd., Science and Technology * Facilities Council Daresbury Laboratory * * This file is part of GDA. * * GDA is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License version 3 as published by the Free * Software Foundation. * * GDA 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 GDA. If not, see <http://www.gnu.org/licenses/>. */ package gda.configuration.properties; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Properties; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; /** * Converter to convert a Java Properties API compliant property file into an XML representation compatible with Jakarta * commons configuration XML properties file format. */ public class PropertiesToXmlConverter { private static final Logger logger = LoggerFactory.getLogger(PropertiesToXmlConverter.class); private Properties props = null; private Document doc = null; private Element root = null; // string to prepend to (a non-XML-compliant) property segment which has // a non-alpha character in the first character position private static final String prependString = "A"; /** * From an XML DOM element, try to locate a named child node. * * @param current * current element node * @param childName * name of child node to locate * @return located child node. null if not found. */ private Element getChild(Element current, String childName) { NodeList nodeList = current.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node n = nodeList.item(i); if (n.getNodeName().equals(childName)) { return (Element) n; } } return null; } /** * Adds a new element as a child of a current element. Assumes existing children are sorted alphabetically. Child is * added at appropriate place in list of children. Repeated use of this routine results in elements with * alphabetically sorted child elements. * * @param current * current parent element to insert new element into * @param newChild * new element to insert as child of current element */ private void addChildSortedAlphabetically(Element current, Element newChild) { NodeList nodeList = current.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node n = nodeList.item(i); // insert before end of child list if it precedes exisiting // nodes if (n.getNodeName().compareToIgnoreCase(newChild.getNodeName()) > 0) { current.insertBefore(newChild, n); return; } } // add to end of list if falls after all others alphabetically current.appendChild(newChild); } /** * Create a XML DOM representation for a property key-value pair (from a Properties object). N.B. Should create a * representation compatible with Jakarta commons configuration XML properties file format. * * @param root * the root element of the DOM instance * @param propertyName * name of a property * @param propertyValue * value of the property */ private void addPropertyToXmlDomRepresentation(Element root, String propertyName, String propertyValue) { int split; String name = propertyName; Element current = root; Element test = null; // split string into dot-separated chunks while ((split = name.indexOf(".")) != -1) { String subName = name.substring(0, split); name = name.substring(split + 1); // if no existing element with current name, create an element // with // this name and attach to parent. if ((test = getChild(current, subName)) == null) { Element subElement = doc.createElement(subName); addChildSortedAlphabetically(current, subElement); current = subElement; } else { // name already exists, so drop down a level current = test; } } // When out of loop, what's left is the final element's name. // README - if 1st char of property name not alpha, get invalid XML - so // workaround to insert name - needs resolving? // if first character of name is not alpha, then insert an alpha char char c = name.charAt(0); if (Character.isLetter(c) == false) { logger.debug("Found non-alpha first-character in last segment of property: " + propertyName + " (" + name + ")"); logger.debug("Converting to: " + prependString + name); logger.debug(""); name = prependString + name; } // Create the leaf element Element last = null; Text valueTextNode = null; try { last = doc.createElement(name); // set its text set to the property value. valueTextNode = doc.createTextNode(propertyValue); last.appendChild(valueTextNode); // Attach it to the parent addChildSortedAlphabetically(current, last); } catch (DOMException e) { logger.error(e.getMessage()); } } /** * Write an XML DOM instance to an XML file. Using Generic Java Transform API, so it is not tied to using 3rd-party * code. And keeps it using generic APIs, so Impl can still be swapped? * * @param outputFileName * The name of the XML properties file to write out. */ private void writeDomToXmlFileTransform(String outputFileName) { String encoding = "UTF-8"; int indentAmount = 3; Transformer t = null; try { // Implemention using javax.xml.transform.Transformer // README - could this use an XSL(T) stylesheet for Jakarta // format? TransformerFactory factory = TransformerFactory.newInstance(); // README - workaround for lack of working "indent-amount" in // transformer properties in Java 1.5 (XSLT/Xalan) // implementation factory.setAttribute("indent-number", new Integer(indentAmount)); // Create a transformer which can "copy" a DOMSource to a // StreamResult // ie perform an XML serialization operation. t = factory.newTransformer(); } catch (TransformerConfigurationException e) { logger.error(e.getMessage()); } catch (TransformerFactoryConfigurationError e) { logger.error(e.getMessage()); } if (t == null) { logger.error("writeDomToXmlFileXalan failed - could not create Transformer"); return; } t.setOutputProperty(OutputKeys.METHOD, "xml"); t.setOutputProperty(OutputKeys.VERSION, "1.0"); t.setOutputProperty(OutputKeys.ENCODING, encoding); // README - need to enable indents to get newlines AND indents! t.setOutputProperty(OutputKeys.INDENT, "yes"); t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // t.setOutputProperty(OutputKeys.STANDALONE, "no"); t.setOutputProperty(OutputKeys.MEDIA_TYPE, "text/xml"); // README - see workaround in TransformerFactory.setAttribute above. // For both instances of setting "indent-amount" below, its getting // ignored!! Without the workaround, output does have newlines, // but there are NO INDENTS!!! // t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", // String.valueOf(indentAmount)); // t.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", // String.valueOf(indentAmount)); Source s = new DOMSource(doc); try { // Result r = new StreamResult(System.out); // README - Must wrap FileOutputStream with a writer or // bufferedwriter, // to workaround a "buggy" behavior of the xml handling code. // A FileOutputStream on its own causes "indent-number" to be // ignored! // This method works properly - "indent-number" is not ignored! // N.B. However, note that the encoding is forced here. OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(outputFileName), encoding); Result r = new StreamResult(out); t.transform(s, r); } catch (UnsupportedEncodingException e) { logger.error(e.getMessage()); } catch (FileNotFoundException e) { logger.error(e.getMessage()); } catch (TransformerException e) { logger.error(e.getMessage()); } } /** * Write an XML DOM instance to an XML file. * * @param outputFileName * The name of the XML properties file to write out. */ private void writeDomToXmlFile(String outputFileName) { // This method uses generic Java Transform APIs - so does not depend on // specific Implementation. writeDomToXmlFileTransform(outputFileName); } /** * Creates a XML DOM instance containing all loaded properties. */ private void convertPropertiesToXml() { doc = XmlDomFactory.createDocument(null, "root", null); root = doc.getDocumentElement(); // add every property into the DOM Document instance Enumeration<?> propertyNames = props.propertyNames(); while (propertyNames.hasMoreElements()) { String name = (String) propertyNames.nextElement(); String value = props.getProperty(name); addPropertyToXmlDomRepresentation(root, name, value); } } /** * Load a Java Properties API compliant properties file into a Properties object in memory. * * @param inputFileName * The name of the properties file to load in. */ private void loadPropertiesFile(String inputFileName) { try { FileInputStream f = new FileInputStream(inputFileName); props = new Properties(); props.load(f); } catch (FileNotFoundException e) { logger.error(e.getMessage()); logger.error("could not load input file: " + inputFileName); } catch (IOException e) { logger.error(e.getMessage()); logger.error("could not create properties object from input file: " + inputFileName); } catch (Exception e) { logger.error(e.getMessage()); logger.error("could not create properties object from input file: " + inputFileName); } } /** * Converter to convert a Java Properties API compliant property file into an XML representation compatible with * Jakarta commons configuration XML properties file format. * <p> * <p> * Usage: * <p> * java gda.configuration.properties.PropertiesToXmlConverter [properties file] [XML file for output] * <p> * * @param args */ public static void main(String[] args) { if (args.length != 2) { logger.info("Usage: java gda.configuration.properties.PropertiesToXmlConverter " + "[properties file] [XML file for output]"); System.exit(0); } String inputFileName = args[0]; String outputFileName = args[1]; try { PropertiesToXmlConverter converter = new PropertiesToXmlConverter(); converter.loadPropertiesFile(inputFileName); if (converter.props != null) { converter.convertPropertiesToXml(); converter.writeDomToXmlFile(outputFileName); } } catch (Exception e) { logger.error(e.getMessage()); logger.debug(e.getStackTrace().toString()); } } }