sascha@372: package de.intevation.flys.artifacts.services.meta; sascha@372: sascha@972: import java.util.regex.Pattern; sascha@372: import java.util.regex.Matcher; sascha@372: sascha@372: import java.util.ArrayList; sascha@372: import java.util.List; sascha@372: import java.util.HashMap; sascha@372: import java.util.Map; sascha@372: sascha@372: import org.w3c.dom.Document; sascha@372: import org.w3c.dom.NodeList; sascha@372: import org.w3c.dom.Node; sascha@372: import org.w3c.dom.Element; sascha@372: sascha@957: import javax.xml.xpath.XPath; sascha@957: import javax.xml.xpath.XPathFactory; sascha@957: import javax.xml.xpath.XPathExpressionException; sascha@957: import javax.xml.xpath.XPathConstants; sascha@957: sascha@372: import java.sql.SQLException; sascha@372: import java.sql.Connection; sascha@372: sascha@372: import de.intevation.artifacts.common.utils.XMLUtils; sascha@372: sascha@372: import org.apache.log4j.Logger; sascha@372: sascha@372: public class Builder sascha@372: { sascha@372: private static Logger log = Logger.getLogger(Builder.class); sascha@372: sascha@972: public static final Pattern STRIP_LINE_INDENT = sascha@972: Pattern.compile("\\s*\\r?\\n\\s*"); sascha@972: sascha@372: public static final String DC_NAMESPACE_URI = sascha@372: "http://www.intevation.org/2011/Datacage"; sascha@372: sascha@957: private static final Document EVAL_DOCUMENT = sascha@957: XMLUtils.newDocument(); sascha@957: sascha@957: private static final XPathFactory XPATH_FACTORY = sascha@957: XPathFactory.newInstance(); sascha@957: sascha@415: protected Document template; sascha@372: sascha@973: protected Map compiledStatements; sascha@973: sascha@963: sascha@372: public class BuildHelper sascha@372: { sascha@973: protected Node output; sascha@973: protected Document owner; sascha@973: protected StackFrames frames; sascha@973: protected Connection connection; sascha@973: protected Map statements; sascha@372: sascha@415: public BuildHelper( sascha@963: Node output, sascha@415: Connection connection, sascha@415: Map parameters sascha@415: ) { sascha@372: this.output = output; sascha@372: this.connection = connection; sascha@415: frames = new StackFrames(parameters); sascha@973: statements = new HashMap(); sascha@963: owner = getOwnerDocument(output); sascha@372: } sascha@372: sascha@372: public void build(List elements) throws SQLException { sascha@372: try { sascha@372: for (Node current: elements) { sascha@372: build(output, current); sascha@372: } sascha@372: } sascha@372: finally { sascha@973: closeStatements(); sascha@372: } sascha@372: } sascha@372: sascha@973: protected void closeStatements() { sascha@973: for (CompiledStatement.Instance csi: statements.values()) { sascha@973: csi.close(); sascha@973: } sascha@973: statements.clear(); sascha@973: } sascha@973: sascha@372: protected void context(Node parent, Element current) sascha@372: throws SQLException sascha@372: { sascha@372: NodeList elements = current.getElementsByTagNameNS( sascha@372: DC_NAMESPACE_URI, "elements"); sascha@372: sascha@372: if (elements.getLength() < 1) { sascha@372: log.warn("no elements found -> ignore"); sascha@372: return; sascha@372: } sascha@372: sascha@372: NodeList subs = elements.item(0).getChildNodes(); sascha@372: int S = subs.getLength(); sascha@372: sascha@372: if (S < 1) { sascha@372: log.warn("elements is empty -> ignore"); sascha@372: return; sascha@372: } sascha@372: sascha@372: NodeList stmntNode = current.getElementsByTagNameNS( sascha@372: DC_NAMESPACE_URI, "statement"); sascha@372: sascha@372: if (stmntNode.getLength() < 1) { sascha@372: log.warn("dc:context: too less statements"); sascha@372: return; sascha@372: } sascha@372: sascha@372: String stmntText = stmntNode.item(0).getTextContent(); sascha@372: sascha@973: CompiledStatement.Instance csi = statements.get(stmntText); sascha@973: sascha@973: if (csi == null) { sascha@973: CompiledStatement cs = compiledStatements.get(stmntText); sascha@973: csi = cs.new Instance(); sascha@973: statements.put(stmntText, csi); sascha@372: } sascha@372: sascha@973: ResultData rd = csi.execute(connection, frames); sascha@372: sascha@372: String [] columns = rd.getColumnLabels(); sascha@372: sascha@372: sascha@372: for (Object [] row: rd.getRows()) { sascha@372: frames.enter(); sascha@372: try { sascha@372: frames.put(columns, row); sascha@372: for (int i = 0; i < S; ++i) { sascha@372: build(parent, subs.item(i)); sascha@372: } sascha@372: } sascha@372: finally { sascha@372: frames.leave(); sascha@372: } sascha@372: } sascha@372: } sascha@372: sascha@372: protected void element(Node parent, Element current) sascha@372: throws SQLException sascha@372: { sascha@372: String attr = expand(current.getAttribute("name")); sascha@372: sascha@372: if (log.isDebugEnabled()) { sascha@372: log.debug("dc:element -> '" + attr + "'"); sascha@372: } sascha@372: sascha@372: if (attr.length() == 0) { sascha@372: log.warn("no name attribute found"); sascha@372: return; sascha@372: } sascha@372: sascha@963: Element element = owner.createElement(attr); sascha@372: sascha@372: NodeList children = current.getChildNodes(); sascha@372: for (int i = 0, N = children.getLength(); i < N; ++i) { sascha@372: build(element, children.item(i)); sascha@372: } sascha@372: sascha@372: parent.appendChild(element); sascha@372: } sascha@372: sascha@372: protected void text(Node parent, Element current) sascha@372: throws SQLException sascha@372: { sascha@372: log.debug("dc:text"); sascha@372: String value = expand(current.getTextContent()); sascha@963: parent.appendChild(owner.createTextNode(value)); sascha@372: } sascha@372: sascha@372: sascha@372: protected void attribute(Node parent, Element current) { sascha@372: sascha@372: if (parent.getNodeType() != Node.ELEMENT_NODE) { sascha@372: log.warn("need element here"); sascha@372: return; sascha@372: } sascha@372: sascha@372: String name = expand(current.getAttribute("name")); sascha@372: String value = expand(current.getAttribute("value")); sascha@372: sascha@372: Element element = (Element)parent; sascha@372: sascha@372: element.setAttribute(name, value); sascha@372: } sascha@372: sascha@962: protected void callMacro(Node parent, Element current) sascha@962: throws SQLException sascha@962: { sascha@962: String name = current.getAttribute("name"); sascha@962: sascha@962: if (name.length() == 0) { sascha@962: log.warn("missing 'name' attribute in 'call-macro'"); sascha@962: return; sascha@962: } sascha@962: sascha@962: NodeList macros = template.getElementsByTagNameNS( sascha@962: DC_NAMESPACE_URI, "macro"); sascha@962: sascha@962: for (int i = 0, N = macros.getLength(); i < N; ++i) { sascha@962: Element macro = (Element)macros.item(i); sascha@962: if (name.equals(macro.getAttribute("name"))) { sascha@962: NodeList subs = macro.getChildNodes(); sascha@962: for (int j = 0, M = subs.getLength(); j < M; ++j) { sascha@962: build(parent, subs.item(j)); sascha@962: } sascha@962: return; sascha@962: } sascha@962: } sascha@962: sascha@962: log.warn("no macro '" + name + "' found."); sascha@962: } sascha@962: sascha@961: protected void ifClause(Node parent, Element current) sascha@961: throws SQLException sascha@961: { sascha@961: String test = current.getAttribute("test"); sascha@961: sascha@961: if (test.length() == 0) { sascha@961: log.warn("missing 'test' attribute in 'if'"); sascha@961: return; sascha@961: } sascha@961: sascha@961: Boolean result = evaluateXPath(test); sascha@961: sascha@961: if (result != null && result.booleanValue()) { sascha@961: NodeList subs = current.getChildNodes(); sascha@961: for (int i = 0, N = subs.getLength(); i < N; ++i) { sascha@961: build(parent, subs.item(i)); sascha@961: } sascha@961: } sascha@961: } sascha@961: sascha@957: protected void choose(Node parent, Element current) sascha@957: throws SQLException sascha@957: { sascha@957: Node branch = null; sascha@957: sascha@957: NodeList children = current.getChildNodes(); sascha@957: for (int i = 0, N = children.getLength(); i < N; ++i) { sascha@957: Node child = children.item(i); sascha@957: String ns = child.getNamespaceURI(); sascha@957: if (ns == null sascha@957: || !ns.equals(DC_NAMESPACE_URI) sascha@957: || child.getNodeType() != Node.ELEMENT_NODE sascha@957: ) { sascha@957: continue; sascha@957: } sascha@957: String name = child.getLocalName(); sascha@957: if ("when".equals(name)) { sascha@957: Element when = (Element)child; sascha@957: String test = when.getAttribute("test"); sascha@957: if (test.length() == 0) { sascha@957: log.warn("no 'test' attribute found for when"); sascha@957: continue; sascha@957: } sascha@957: sascha@959: Boolean result = evaluateXPath(test); sascha@959: if (result != null && result.booleanValue()) { sascha@959: branch = child; sascha@959: break; sascha@959: } sascha@957: sascha@957: continue; sascha@957: } sascha@957: else if ("otherwise".equals(name)) { sascha@957: branch = child; sascha@957: // No break here. sascha@957: } sascha@957: } sascha@957: sascha@957: if (branch != null) { sascha@957: NodeList subs = branch.getChildNodes(); sascha@957: for (int i = 0, N = subs.getLength(); i < N; ++i) { sascha@957: build(parent, subs.item(i)); sascha@957: } sascha@957: } sascha@957: } sascha@957: sascha@959: protected Boolean evaluateXPath(String expr) { sascha@976: sascha@976: if (log.isDebugEnabled()) { sascha@976: log.debug("evaluate: '" + expr + "'"); sascha@976: } sascha@976: sascha@959: try { sascha@959: XPath xpath = XPATH_FACTORY.newXPath(); sascha@959: xpath.setXPathVariableResolver(frames); sascha@959: xpath.setXPathFunctionResolver(FunctionResolver.FUNCTIONS); sascha@959: Object result = xpath.evaluate( sascha@959: expr, EVAL_DOCUMENT, XPathConstants.BOOLEAN); sascha@959: sascha@959: return result instanceof Boolean sascha@959: ? (Boolean)result sascha@959: : null; sascha@959: } sascha@959: catch (XPathExpressionException xfce) { sascha@959: log.error(xfce); sascha@959: } sascha@959: return null; sascha@959: } sascha@959: sascha@415: protected void convert(Node parent, Element current) { sascha@415: sascha@415: String variable = expand(current.getAttribute("var")); sascha@415: String type = expand(current.getAttribute("type")); sascha@415: sascha@415: if (frames.containsKey(variable)) { sascha@415: Object object = TypeConverter.convert( sascha@415: frames.get(variable), sascha@415: type); sascha@415: frames.put(variable, object); sascha@415: } sascha@415: } sascha@415: sascha@372: protected String expand(String s) { sascha@372: Matcher m = CompiledStatement.VAR.matcher(s); sascha@372: sascha@372: StringBuffer sb = new StringBuffer(); sascha@372: while (m.find()) { sascha@372: String key = m.group(1); sascha@372: Object value = frames.get(key); sascha@372: m.appendReplacement(sb, value != null ? value.toString() : ""); sascha@372: } sascha@372: m.appendTail(sb); sascha@372: return sb.toString(); sascha@372: } sascha@372: sascha@742: protected void build(Node parent, Node current) sascha@372: throws SQLException sascha@372: { sascha@372: String ns = current.getNamespaceURI(); sascha@372: if (ns != null && ns.equals(DC_NAMESPACE_URI)) { sascha@372: if (current.getNodeType() != Node.ELEMENT_NODE) { sascha@372: log.warn("need elements here"); sascha@372: } sascha@372: else { sascha@372: String localName = current.getLocalName(); sascha@972: if ("attribute".equals(localName)) { sascha@972: attribute(parent, (Element)current); sascha@972: } sascha@972: else if ("context".equals(localName)) { sascha@372: context(parent, (Element)current); sascha@372: } sascha@972: else if ("if".equals(localName)) { sascha@972: ifClause(parent, (Element)current); sascha@972: } sascha@957: else if ("choose".equals(localName)) { sascha@957: choose(parent, (Element)current); sascha@957: } sascha@972: else if ("call-macro".equals(localName)) { sascha@972: callMacro(parent, (Element)current); sascha@961: } sascha@972: else if ("macro".equals(localName)) { sascha@972: // simply ignore the definition. sascha@372: } sascha@372: else if ("element".equals(localName)) { sascha@372: element(parent, (Element)current); sascha@372: } sascha@372: else if ("text".equals(localName)) { sascha@372: text(parent, (Element)current); sascha@372: } sascha@415: else if ("convert".equals(localName)) { sascha@415: convert(parent, (Element)current); sascha@415: } sascha@372: else { sascha@372: log.warn("unknown '" + localName + "' -> ignore"); sascha@372: } sascha@372: } sascha@372: return; sascha@372: } sascha@372: sascha@410: if (current.getNodeType() == Node.TEXT_NODE) { sascha@410: String txt = current.getNodeValue(); sascha@410: if (txt != null && txt.trim().length() == 0) { sascha@410: return; sascha@410: } sascha@410: } sascha@410: sascha@963: Node copy = owner.importNode(current, false); sascha@372: sascha@372: NodeList children = current.getChildNodes(); sascha@372: for (int i = 0, N = children.getLength(); i < N; ++i) { sascha@372: build(copy, children.item(i)); sascha@372: } sascha@372: parent.appendChild(copy); sascha@372: } sascha@372: } // class BuildHelper sascha@372: sascha@372: sascha@372: public Builder() { sascha@973: compiledStatements = new HashMap(); sascha@372: } sascha@372: sascha@372: public Builder(Document template) { sascha@973: this(); sascha@372: this.template = template; sascha@973: compileStatements(); sascha@973: } sascha@973: sascha@973: protected void compileStatements() { sascha@973: sascha@973: NodeList nodes = template.getElementsByTagNameNS( sascha@973: DC_NAMESPACE_URI, "statement"); sascha@973: sascha@973: for (int i = 0, N = nodes.getLength(); i < N; ++i) { sascha@973: Element stmntElement = (Element)nodes.item(i); sascha@973: String stmnt = trimStatement(stmntElement.getTextContent()); sascha@973: if (stmnt == null || stmnt.length() == 0) { sascha@973: throw new IllegalArgumentException("found empty statement"); sascha@973: } sascha@973: CompiledStatement cs = new CompiledStatement(stmnt); sascha@973: // for faster lookup store a shortend string into the template sascha@973: stmnt = "s" + i; sascha@973: stmntElement.setTextContent(stmnt); sascha@973: compiledStatements.put(stmnt, cs); sascha@973: } sascha@372: } sascha@372: sascha@963: protected static List rootsToList(NodeList roots) { sascha@372: sascha@372: List elements = new ArrayList(); sascha@372: sascha@372: for (int i = 0, N = roots.getLength(); i < N; ++i) { sascha@372: NodeList rootChildren = roots.item(i).getChildNodes(); sascha@372: for (int j = 0, M = rootChildren.getLength(); j < M; ++j) { sascha@372: Node child = rootChildren.item(j); sascha@372: if (child.getNodeType() == Node.ELEMENT_NODE) { sascha@372: elements.add(child); sascha@372: } sascha@372: } sascha@372: } sascha@372: sascha@963: return elements; sascha@963: } sascha@963: sascha@972: protected static final String trimStatement(String stmnt) { sascha@973: if (stmnt == null) return null; sascha@972: //XXX: Maybe a bit to radical for multiline strings? sascha@972: return STRIP_LINE_INDENT.matcher(stmnt.trim()).replaceAll(" "); sascha@972: } sascha@972: sascha@972: protected static Document getOwnerDocument(Node node) { sascha@972: Document document = node.getOwnerDocument(); sascha@972: return document != null ? document : (Document)node; sascha@972: } sascha@972: sascha@963: public void build( sascha@963: Connection connection, sascha@963: Node output, sascha@963: Map parameters sascha@963: ) sascha@963: throws SQLException sascha@963: { sascha@963: BuildHelper helper = new BuildHelper(output, connection, parameters); sascha@963: sascha@963: NodeList roots = template.getElementsByTagNameNS( sascha@963: DC_NAMESPACE_URI, "template"); sascha@963: sascha@963: helper.build(rootsToList(roots)); sascha@372: } sascha@372: } sascha@372: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :