CDataDocumentDecorator.java
package org.thewonderlemming.c4plantuml.graphml.export;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Optional;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thewonderlemming.c4plantuml.graphml.model.DataModel;
import org.thewonderlemming.c4plantuml.graphml.validation.GraphMLValidator;
import org.thewonderlemming.c4plantuml.graphml.validation.ValidationException;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.w3c.dom.UserDataHandler;
/**
* A {@link Document} decorator to handle CDATA tags around values in JAXB marshalling operations.
*
* @author thewonderlemming
*
*/
public class CDataDocumentDecorator implements Document {
private static final String INDENT_AMOUNT_OUTPUT_PROPERTY = "{http://xml.apache.org/xslt}indent-amount";
private static final Logger LOGGER = LoggerFactory.getLogger(CDataDocumentDecorator.class);
private final Document document;
/**
* A factory method that builds a new {@link Document} and decorates it with the returned
* {@link CDataDocumentDecorator} instance.
*
* @return a new {@link CDataDocumentDecorator} instance.
* @throws ParserConfigurationException if anything goes wrong while creating the {@link Document} instance.
*/
public static CDataDocumentDecorator newInstance() throws ParserConfigurationException {
final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
docBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
final Document document = docBuilderFactory.newDocumentBuilder().newDocument();
return CDataDocumentDecorator.newInstance(document);
}
/**
* A factory method that builds a new {@link CDataDocumentDecorator} instance given a {@link Document} to decorate.
*
* @param decorated the {@link Document} to decorate.
* @return a new {@link CDataDocumentDecorator} instance.
*/
public static CDataDocumentDecorator newInstance(final Document decorated) {
return new CDataDocumentDecorator(decorated);
}
private CDataDocumentDecorator(final Document document) {
this.document = document;
}
/**
* {@inheritDoc}
*/
@Override
public Node adoptNode(final Node source) {
return this.document.adoptNode(source);
}
/**
* {@inheritDoc}
*/
@Override
public Node appendChild(final Node newChild) {
return this.document.appendChild(newChild);
}
/**
* {@inheritDoc}
*/
@Override
public Node cloneNode(final boolean deep) {
return this.document.cloneNode(deep);
}
/**
* {@inheritDoc}
*/
@Override
public short compareDocumentPosition(final Node other) {
return this.document.compareDocumentPosition(other);
}
/**
* {@inheritDoc}
*/
@Override
public Attr createAttribute(final String name) {
return this.document.createAttribute(name);
}
/**
* {@inheritDoc}
*/
@Override
public Attr createAttributeNS(final String namespaceURI, final String qualifiedName) {
return this.document.createAttributeNS(namespaceURI, qualifiedName);
}
/**
* {@inheritDoc}
*/
@Override
public CDATASection createCDATASection(final String data) {
return this.document.createCDATASection(data);
}
/**
* {@inheritDoc}
*/
@Override
public Comment createComment(final String data) {
return this.document.createComment(data);
}
/**
* {@inheritDoc}
*/
@Override
public DocumentFragment createDocumentFragment() {
return this.document.createDocumentFragment();
}
/**
* {@inheritDoc}
*/
@Override
public Element createElement(final String tagName) {
return this.document.createElement(tagName);
}
/**
* {@inheritDoc}
*/
@Override
public Element createElementNS(final String namespaceURI, final String qualifiedName) {
return this.document.createElementNS(namespaceURI, qualifiedName);
}
/**
* {@inheritDoc}
*/
@Override
public EntityReference createEntityReference(final String name) {
return this.document.createEntityReference(name);
}
/**
* {@inheritDoc}
*/
@Override
public ProcessingInstruction createProcessingInstruction(final String target, final String data) {
return this.document.createProcessingInstruction(target, data);
}
/**
* {@inheritDoc}
*/
@Override
public Text createTextNode(final String data) {
return this.document.createTextNode(data);
}
/**
* Exports the content of the document as a string that contains CDATA tags around values.
*
* @param charset the {@link Charset} to use for the XML output.
* @param indent a flag that tells whether or not the XML output should fit on a single line.
* @param validate a flag that telles whether or not the XML output should be validated against an XSD.
* @param strictValidation a flag that tells whether or not validation exception should void the marshalling result.
* @return an {@link Optional} of the result of the XML marshalling operation. Can be empty.
*/
public Optional<String> exportAsString(final Charset charset, final boolean indent, final boolean validate,
final boolean strictValidation) {
try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
final Transformer nullTransformer = transformerFactory.newTransformer();
nullTransformer.setOutputProperty(OutputKeys.INDENT, indent ? "yes" : "no");
nullTransformer.setOutputProperty(INDENT_AMOUNT_OUTPUT_PROPERTY, indent ? "4" : "0");
nullTransformer.setOutputProperty(OutputKeys.ENCODING, charset.name());
nullTransformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
nullTransformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, DataModel.TAG_NAME);
nullTransformer.transform(new DOMSource(this.document), new StreamResult(baos));
final String graphML = baos.toString(charset.name());
LOGGER.debug("Current (yet to be validated) GraphML:\n{}", graphML);
if (validate) {
try (final ByteArrayInputStream bais = new ByteArrayInputStream(graphML.getBytes())) {
GraphMLValidator.validate(bais, strictValidation);
}
}
return Optional.of(graphML);
} catch (final TransformerException | ValidationException | IOException e) {
LOGGER.error("Cannot process the current DOM document because of the following: {}", e.getMessage(), e);
}
return Optional.empty();
}
/**
* {@inheritDoc}
*/
@Override
public NamedNodeMap getAttributes() {
return this.document.getAttributes();
}
/**
* {@inheritDoc}
*/
@Override
public String getBaseURI() {
return this.document.getBaseURI();
}
/**
* {@inheritDoc}
*/
@Override
public NodeList getChildNodes() {
return this.document.getChildNodes();
}
/**
* {@inheritDoc}
*/
@Override
public DocumentType getDoctype() {
return this.document.getDoctype();
}
/**
* {@inheritDoc}
*/
@Override
public Element getDocumentElement() {
return this.document.getDocumentElement();
}
/**
* {@inheritDoc}
*/
@Override
public String getDocumentURI() {
return this.document.getDocumentURI();
}
/**
* {@inheritDoc}
*/
@Override
public DOMConfiguration getDomConfig() {
return this.document.getDomConfig();
}
/**
* {@inheritDoc}
*/
@Override
public Element getElementById(final String elementId) {
return this.document.getElementById(elementId);
}
/**
* {@inheritDoc}
*/
@Override
public NodeList getElementsByTagName(final String tagname) {
return this.document.getElementsByTagName(tagname);
}
/**
* {@inheritDoc}
*/
@Override
public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
return this.document.getElementsByTagNameNS(namespaceURI, localName);
}
/**
* {@inheritDoc}
*/
@Override
public Object getFeature(final String feature, final String version) {
return this.document.getFeature(feature, version);
}
/**
* {@inheritDoc}
*/
@Override
public Node getFirstChild() {
return this.document.getFirstChild();
}
/**
* {@inheritDoc}
*/
@Override
public DOMImplementation getImplementation() {
return this.document.getImplementation();
}
/**
* {@inheritDoc}
*/
@Override
public String getInputEncoding() {
return this.document.getInputEncoding();
}
/**
* {@inheritDoc}
*/
@Override
public Node getLastChild() {
return this.document.getLastChild();
}
/**
* {@inheritDoc}
*/
@Override
public String getLocalName() {
return this.document.getLocalName();
}
/**
* {@inheritDoc}
*/
@Override
public String getNamespaceURI() {
return this.document.getNamespaceURI();
}
/**
* {@inheritDoc}
*/
@Override
public Node getNextSibling() {
return this.document.getNextSibling();
}
/**
* {@inheritDoc}
*/
@Override
public String getNodeName() {
return this.document.getNodeName();
}
/**
* {@inheritDoc}
*/
@Override
public short getNodeType() {
return this.document.getNodeType();
}
/**
* {@inheritDoc}
*/
@Override
public String getNodeValue() {
return this.document.getNodeValue();
}
/**
* {@inheritDoc}
*/
@Override
public Document getOwnerDocument() {
return this.document.getOwnerDocument();
}
/**
* {@inheritDoc}
*/
@Override
public Node getParentNode() {
return this.document.getParentNode();
}
/**
* {@inheritDoc}
*/
@Override
public String getPrefix() {
return this.document.getPrefix();
}
/**
* {@inheritDoc}
*/
@Override
public Node getPreviousSibling() {
return this.document.getPreviousSibling();
}
/**
* {@inheritDoc}
*/
@Override
public boolean getStrictErrorChecking() {
return this.document.getStrictErrorChecking();
}
/**
* {@inheritDoc}
*/
@Override
public String getTextContent() {
return this.document.getTextContent();
}
/**
* {@inheritDoc}
*/
@Override
public Object getUserData(final String key) {
return this.document.getUserData(key);
}
/**
* {@inheritDoc}
*/
@Override
public String getXmlEncoding() {
return this.document.getXmlEncoding();
}
/**
* {@inheritDoc}
*/
@Override
public boolean getXmlStandalone() {
return this.document.getXmlStandalone();
}
/**
* {@inheritDoc}
*/
@Override
public String getXmlVersion() {
return this.document.getXmlVersion();
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasAttributes() {
return this.document.hasAttributes();
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasChildNodes() {
return this.document.hasChildNodes();
}
/**
* {@inheritDoc}
*/
@Override
public Node importNode(final Node importedNode, final boolean deep) {
return this.document.importNode(importedNode, deep);
}
/**
* {@inheritDoc}
*/
@Override
public Node insertBefore(final Node newChild, final Node refChild) {
return this.document.insertBefore(newChild, refChild);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDefaultNamespace(final String namespaceURI) {
return this.document.isDefaultNamespace(namespaceURI);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEqualNode(final Node arg) {
return this.document.isEqualNode(arg);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSameNode(final Node other) {
return this.document.isSameNode(other);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSupported(final String feature, final String version) {
return this.document.isSupported(feature, version);
}
/**
* {@inheritDoc}
*/
@Override
public String lookupNamespaceURI(final String prefix) {
return this.document.lookupNamespaceURI(prefix);
}
/**
* {@inheritDoc}
*/
@Override
public String lookupPrefix(final String namespaceURI) {
return this.document.lookupPrefix(namespaceURI);
}
/**
* {@inheritDoc}
*/
@Override
public void normalize() {
this.document.normalize();
}
/**
* {@inheritDoc}
*/
@Override
public void normalizeDocument() {
this.document.normalizeDocument();
}
/**
* {@inheritDoc}
*/
@Override
public Node removeChild(final Node oldChild) {
return this.document.removeChild(oldChild);
}
/**
* {@inheritDoc}
*/
@Override
public Node renameNode(final Node n, final String namespaceURI, final String qualifiedName) {
return this.document.renameNode(n, namespaceURI, qualifiedName);
}
/**
* {@inheritDoc}
*/
@Override
public Node replaceChild(final Node newChild, final Node oldChild) {
return this.document.replaceChild(newChild, oldChild);
}
/**
* {@inheritDoc}
*/
@Override
public void setDocumentURI(final String documentURI) {
this.document.setDocumentURI(documentURI);
}
/**
* {@inheritDoc}
*/
@Override
public void setNodeValue(final String nodeValue) {
this.document.setNodeValue(nodeValue);
}
/**
* {@inheritDoc}
*/
@Override
public void setPrefix(final String prefix) {
this.document.setPrefix(prefix);
}
/**
* {@inheritDoc}
*/
@Override
public void setStrictErrorChecking(final boolean strictErrorChecking) {
this.document.setStrictErrorChecking(strictErrorChecking);
}
/**
* {@inheritDoc}
*/
@Override
public void setTextContent(final String textContent) {
this.document.setTextContent(textContent);
}
/**
* {@inheritDoc}
*/
@Override
public Object setUserData(final String key, final Object data, final UserDataHandler handler) {
return this.document.setUserData(key, data, handler);
}
/**
* {@inheritDoc}
*/
@Override
public void setXmlStandalone(final boolean xmlStandalone) {
this.document.setXmlStandalone(xmlStandalone);
}
/**
* {@inheritDoc}
*/
@Override
public void setXmlVersion(final String xmlVersion) {
this.document.setXmlVersion(xmlVersion);
}
}