/**
* Copyright 2009, 2010 The Regents of the University of California
* Licensed under the Educational Community 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.osedu.org/licenses/ECL-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 ch.entwine.weblounge.bridge.oaipmh.util;
import static org.opencastproject.util.data.Collections.flatMap;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.opencastproject.util.data.Function;
import org.opencastproject.util.data.Function0;
import org.opencastproject.util.data.Option;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
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;
/**
* DOM based XML generation environment. Implement {@link #create()} to create
* the XML. Serialize to an output stream with
* {@link #generate(java.io.OutputStream)}.
*
* todo document the node creator functions
*/
public abstract class XmlGen {
private Document document;
/**
* Create a new environment.
*/
public XmlGen() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.newDocument();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
private void write(OutputStream out) {
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(out);
transformer.transform(source, result);
} catch (TransformerException e) {
throw new RuntimeException(e);
}
}
/**
* Generate the XML and write it to <code>out</code>.
*/
public void generate(OutputStream out) {
document.appendChild(create());
write(out);
}
/**
* Generate the document.
*/
public Document generate() {
document.appendChild(create());
return document;
}
/**
* Implement this method to create the DOM. Use the various node creation
* functions for this purpose.
*/
public abstract Element create();
// --
protected Namespace ns(String prefix, String namespace) {
return new Namespace(prefix, namespace);
}
protected Node schemaLocation(String location) {
return $a("xsi:schemaLocation", location);
}
// CHECKSTYLE:OFF
protected Node $a(String name, String value) {
Attr a = document.createAttribute(name);
a.setValue(value);
return a;
}
protected Node $aBlank(String name, String value) {
if (StringUtils.isNotBlank(value)) {
Attr a = document.createAttribute(name);
a.setValue(value);
return a;
} else {
return nodeZero();
}
}
protected Node $aSome(final String name, final Option<String> value) {
return value.fold(new Option.Match<String, Node>() {
public Node some(String value) {
Attr a = document.createAttribute(name);
a.setValue(value);
return a;
}
public Node none() {
return nodeZero();
}
});
}
/**
* Create an element with the qualified name <code>qname</code> -- i.e.
* <code>prefix:tagname</code> -- in the namespace <code>namespace</code> with
* children <code>nodes</code>.
*/
protected Element $e(String qname, String namespace, Node... nodes) {
return appendTo(document.createElementNS(namespace, qname), _(nodes));
}
/**
* Create an element with the qualified name <code>qname</code> -- i.e.
* <code>prefix:tagname</code> -- in the namespace <code>namespace</code> with
* children <code>nodes</code>.
*/
protected Element $e(String qname, String namespace, NodeList nodes) {
return appendTo(document.createElementNS(namespace, qname), nodes);
}
protected Node $eTxtBlank(final String name, String text) {
return $txtBlank(text).map(new Function<Node, Node>() {
public Node apply(Node text) {
Element e = document.createElement(name);
e.appendChild(text);
return e;
}
}).getOrElse(nodeZero);
}
protected Node $eTxt(final String name, String text) {
Element e = document.createElement(name);
e.appendChild($txt(text));
return e;
}
protected Element $e(String name, List<Namespace> namespaces, Node... nodes) {
Element e = document.createElement(name);
return appendTo(appendNs(e, namespaces), _(nodes));
}
protected Element $e(String name, List<Namespace> namespaces, NodeList nodes) {
Element e = document.createElement(name);
return appendTo(appendNs(e, namespaces), nodes);
}
protected Element $e(String qname, String namespace,
List<Namespace> namespaces, Node... nodes) {
Element e = document.createElementNS(namespace, qname);
return appendTo(appendNs(e, namespaces), _(nodes));
}
/**
* Create a new DOM element.
*
* @param qname
* fully qualified tag name, e.g. "name" or "dc:title"
* @param namespace
* namespace to which this tag belongs to
* @param namespaces
* additional namespace declarations
* @param nodes
* child nodes
*/
protected Element $e(String qname, String namespace,
List<Namespace> namespaces, List<Node> nodes) {
Element e = document.createElementNS(namespace, qname);
return appendTo(appendNs(e, namespaces), nodes);
}
protected Element $e(String name, Node... nodes) {
return $e(name, _(nodes));
}
protected Element $e(String name, List<Node> nodes) {
return appendTo(document.createElement(name), nodes);
}
/**
* Conditional element. Only created if at least one subnode is present.
* Subnodes may be attributes, elements, text nodes, etc.
*/
protected Node $e(String name, Option<Node>... nodes) {
List<Node> existing = filter(_(nodes));
if (!existing.isEmpty()) {
return $e(name, existing);
} else {
return nodeZero();
}
}
protected Node $txt(String text) {
return document.createTextNode(text);
}
protected Node $cdata(String text) {
return document.createCDATASection(text);
}
/**
* Text blank.
*/
protected Option<Node> $txtBlank(String text) {
return StringUtils.isNotBlank(text) ? Option.some($txt(text)) : Option.<Node> none();
}
// --
protected <A> List<A> _(A... as) {
List<A> l = new ArrayList<A>(as.length);
for (A a : as)
l.add(a);
return l;
}
/**
* Create an empty list with elements of type A.
*/
protected <A> List<A> __(Class<A> cls) {
return new ArrayList<A>();
}
// CHECKSTYLE:ON
private List<Node> filter(List<Option<Node>> nodes) {
return (List<Node>) flatMap(nodes, new Function<Option<Node>, Collection<Node>>() {
public Collection<Node> apply(Option<Node> nodeOption) {
return nodeOption.fold(new Option.Match<Node, Collection<Node>>() {
public Collection<Node> some(Node node) {
return _(node);
}
public Collection<Node> none() {
return Collections.EMPTY_LIST;
}
});
}
});
}
private Element appendNs(Element e, List<Namespace> namespaces) {
for (Namespace n : namespaces) {
e.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + n.getPrefix(), n.getNamespace());
}
return e;
}
/**
* Append <code>nodes</code> to element <code>e</code>. Respects different
* node types like attributes and elements.
*/
private Element appendTo(Element e, List<Node> nodes) {
for (Node node : nodes)
appendTo(e, node);
return e;
}
/**
* Like {@link #appendTo(org.w3c.dom.Element, java.util.List)} but with a
* different signature.
*/
private Element appendTo(Element e, NodeList nodes) {
for (int i = 0; i < nodes.getLength(); i++)
appendTo(e, nodes.item(i));
return e;
}
/**
* Append node <code>n</code> to element <code>e</code> respecting different
* node types like attributes and elements.
*/
private void appendTo(Element e, Node n) {
Node toAppend = ObjectUtils.equals(n.getOwnerDocument(), document) ? n : document.importNode(n, true);
if (toAppend instanceof Attr) {
e.setAttributeNode((Attr) toAppend);
} else {
e.appendChild(toAppend);
}
}
/**
* The neutral element.
*/
protected Node nodeZero() {
return document.createTextNode("");
}
/**
* Lazy version of {@link #nodeZero()}.
*/
protected Function0<Node> nodeZero = new Function0<Node>() {
public Node apply() {
return nodeZero();
}
};
/**
* Create a text node from a string.
*/
protected Function<String, Node> mkText = new Function<String, Node>() {
public Node apply(String token) {
return $txt(token);
}
};
protected class Namespace {
private final String prefix;
private final String namespace;
Namespace(String prefix, String namespace) {
this.prefix = prefix;
this.namespace = namespace;
}
public String getPrefix() {
return prefix;
}
public String getNamespace() {
return namespace;
}
}
}