package com.ausregistry.jtoolkit2.xml;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.ausregistry.jtoolkit2.ErrorPkg;
/**
* An XMLWriter provides a simple interface to build a DOM tree and serialize it to XML format. XMLWriters are namespace
* aware and may be configured to validate the generated XML.
*
* Uses the maintenance, support and user level loggers.
*/
public abstract class XMLWriter {
private static final String PACKAGE_NAME = XMLWriter.class.getPackage().getName();
/*
* This lock is a workaround to an implementation flaw in com.sun.xml.internal.stream.XMLOutputFactoryImpl which
* results in the output stream assigned to a XMLStreamWriter being over-written by another thread. This is resolved
* in Java 7.
*/
private static final Object WORKAROUND_LOCK = new Object();
protected String xml;
protected String version;
protected String encoding;
protected boolean standalone;
protected Logger supportLogger = Logger.getLogger(PACKAGE_NAME + ".support");
protected Logger userLogger = Logger.getLogger(PACKAGE_NAME + ".user");
protected Logger maintLogger = Logger.getLogger(PACKAGE_NAME + ".maint");
/**
* Get an instance of the default implementation of XMLWriter.
*/
public static XMLWriter newInstance() {
return new EPPWriter();
}
/**
* Get the root element of the DOM tree associated with this writer.
*
* @return The root Element of the DOM tree.
*/
public abstract Element getRoot();
protected abstract SAXParser newSAXParser() throws SAXException, ParserConfigurationException;
protected abstract boolean isParserValidating();
protected abstract XMLBuilder getXMLBuilder();
/**
* Set the root element of the DOM tree associated with this writer.
*
* @param newRoot The DOM Element to assign as the root.
*/
protected abstract void setRoot(Element newRoot);
protected abstract Element createElement(String uri, String name);
/**
* Append a new child element to the specified element. The new element will inherit the namespace of the parent.
*
* @param parent The element which will be the parent element of the new element.
* @param name The name of the new element (tag).
*
* @return The new child element.
*/
public Element appendChild(Element parent, String name) {
return appendChild(parent, name, parent.getNamespaceURI());
}
/**
* Append a new child element to the specified element. The new element will be in the namespace identified by the
* given URI.
*
* @param parent The element which will be the parent element of the new element.
* @param name The name of the new element (tag).
*
* @param uri Namespace URI for the new child element.
*/
public Element appendChild(Element parent, String name, String uri) {
Element newElement = createElement(uri, name);
parent.appendChild(newElement);
return newElement;
}
/**
* Append a DOM Element with the given name and attribute to the specified parent Element. The new
* {@link org.w3c.dom.Element} will be in the namespace of its parent.
*
* @param parent The parent of the new Element.
* @param name The name of the new Element.
* @param attrName The name of the attribute of the new Element.
* @param attrValue The value of the attribute named {@code attrName}.
*
* @return The new Element.
*/
public Element appendChild(Element parent, String name, String attrName, String attrValue) {
Element newElement = appendChild(parent, name);
newElement.setAttribute(attrName, attrValue);
return newElement;
}
/**
* Append a DOM Element with the given name, text content value and attribute to the specified parent Element. The
* new {@link org.w3c.dom.Element} will be in the namespace of its parent.
*
* @param parent The parent of the new Element.
* @param name The name of the new Element.
* @param value The text content to assign to the text node child of the new Element.
* @param attrName The name of the attribute of the new Element.
* @param attrValue The value of the attribute named {@code attrName}.
*
* @return The new Element.
*/
public Element appendChild(Element parent, String name, String value, String attrName, String attrValue) {
Element newElement = appendChild(parent, name, attrName, attrValue);
newElement.setTextContent(value);
return newElement;
}
/**
* Append a DOM Element with the given name and attributes to the specified parent Element. The two arrays
* {@code attrNames} and {@code attrValues} must have the same length and be ordered such that the nth element of
* {@code attrValues} corresponds to the attribute with the name given by the nth element of {@code attrNames}. The
* new {@link org.w3c.dom.Element} will be in the namespace of its parent.
*
* @param parent The parent of the new Element.
* @param name The name of the new Element.
* @param attrNames The names of the attributes of the new Element.
* @param attrValues The values of each of the attributes {@code attrName}.
*
* @return The new Element.
*/
public Element appendChild(Element parent, String name, String[] attrNames, String[] attrValues) {
return appendChild(parent, name, parent.getNamespaceURI(), null, attrNames, attrValues);
}
/**
* Append a DOM Element with the given name and attributes to the specified parent Element. The new element is in
* the namespace identified the the given URI. The two arrays {@code attrNames} and {@code attrValues} must have the
* same length and be ordered such that the nth element of {@code attrValues} corresponds to the attribute with the
* name given by the nth element of {@code attrNames}.
*
* @param parent The parent of the new Element.
* @param name The name of the new Element.
* @param uri The URI of the new Element's namespace.
* @param attrNames The names of the attributes of the new Element.
* @param attrValues The values of each of the attributes {@code attrName}.
*
* @return The new Element.
*/
public Element appendChild(Element parent, String name, String uri, String value, String[] attrNames,
String[] attrValues) {
Element newElement = appendChild(parent, name, uri);
for (int i = 0; i < attrNames.length && i < attrValues.length; i++) {
newElement.setAttribute(attrNames[i], attrValues[i]);
}
/*
* if (uri != null && uri != parent.getNamespaceURI()) { newElement.setAttribute("xmlns", uri); }
*/
if (value != null) {
newElement.setTextContent(value);
}
return newElement;
}
/**
* Append multiple DOM Elements to the given Element, each having a specified text content. The text content of each
* new element is defined by the {@code values} array. The new {@link org.w3c.dom.Element} will be in the namespace
* of its parent.
*
* @param parent The parent of the new Elements.
* @param name The name of the new Elements.
*/
public void appendChildren(Element parent, String name, String[] values) {
for (String value : values) {
Element child = appendChild(parent, name);
child.setTextContent(value);
}
}
/**
* Serialize to XML format the DOM tree currently associated with this XMLWriter. If validation is enabled, then an
* alternative SAX error handler may be provided as described in
*
* @return the string
* @throws SAXException the SAX exception
* {@link com.ausregistry.jtoolkit2.xml.HandlerFactory}. That handler will receive notification of
* parsing/validation failures.
*/
public String toXML() throws SAXException {
if (xml != null) {
return xml;
}
if (!isParserValidating()) {
generateXmlLocked();
supportLogger.info(xml);
return xml;
}
xml = getXMLBuilder().toXML(getRoot());
try {
validate();
} catch (SAXException saxe) {
generateXmlLocked();
try {
validate();
} catch (SAXException se) {
userLogger.warning(xml);
throw se;
} catch (Exception e) {
}
} catch (ParserConfigurationException pce) {
userLogger.warning(pce.getMessage());
userLogger.warning(ErrorPkg.getMessage("xml.parser.operation.unsupported"));
generateXmlLocked();
} catch (IOException ioe) {
userLogger.warning(ioe.getMessage());
maintLogger.warning(ioe.getMessage());
generateXmlLocked();
}
supportLogger.info(xml);
return xml;
}
/**
* See the description of the WORKAROUND_LOCK class member for the rationale behind this method.
*/
private void generateXmlLocked() {
synchronized (XMLWriter.WORKAROUND_LOCK) {
xml = getXMLBuilder().toXML(getRoot());
}
}
private void validate() throws IOException, ParserConfigurationException, SAXException {
InputStream in = new ByteArrayInputStream(xml.getBytes());
DefaultHandler handler = HandlerFactory.newInstance();
SAXParser saxParser = newSAXParser();
saxParser.parse(in, handler);
}
}