sascha@998: package de.intevation.flys.artifacts.datacage.templating; sascha@998: sascha@998: import java.util.regex.Pattern; sascha@998: import java.util.regex.Matcher; sascha@998: sascha@998: import java.util.ArrayList; sascha@998: import java.util.List; sascha@998: import java.util.HashMap; sascha@998: import java.util.Map; sascha@1011: import java.util.Deque; sascha@1011: import java.util.ArrayDeque; sascha@998: sascha@998: import org.w3c.dom.Document; sascha@998: import org.w3c.dom.NodeList; sascha@998: import org.w3c.dom.Node; sascha@998: import org.w3c.dom.Element; sascha@998: sascha@998: import javax.xml.xpath.XPath; sascha@998: import javax.xml.xpath.XPathFactory; sascha@998: import javax.xml.xpath.XPathExpressionException; sascha@998: import javax.xml.xpath.XPathConstants; sascha@998: sascha@998: import java.sql.SQLException; sascha@998: import java.sql.Connection; sascha@998: sascha@998: import de.intevation.artifacts.common.utils.XMLUtils; sascha@998: sascha@1818: import de.intevation.flys.utils.Pair; sascha@1818: sascha@998: import org.apache.log4j.Logger; sascha@998: sascha@998: public class Builder sascha@998: { sascha@998: private static Logger log = Logger.getLogger(Builder.class); sascha@998: sascha@1015: public static final String CONNECTION_USER = "user"; sascha@1015: public static final String CONNECTION_SYSTEM = "system"; sascha@1015: public static final String DEFAULT_CONNECTION_NAME = CONNECTION_SYSTEM; sascha@1015: sascha@998: public static final Pattern STRIP_LINE_INDENT = sascha@998: Pattern.compile("\\s*\\r?\\n\\s*"); sascha@998: sascha@998: public static final String DC_NAMESPACE_URI = sascha@998: "http://www.intevation.org/2011/Datacage"; sascha@998: sascha@998: private static final Document EVAL_DOCUMENT = sascha@998: XMLUtils.newDocument(); sascha@998: sascha@998: private static final XPathFactory XPATH_FACTORY = sascha@998: XPathFactory.newInstance(); sascha@998: sascha@998: protected Document template; sascha@998: sascha@998: protected Map compiledStatements; sascha@998: sascha@1011: public static class NamedConnection { sascha@1011: sascha@1011: protected String name; sascha@1011: protected Connection connection; sascha@1011: protected boolean cached; sascha@1011: sascha@1011: public NamedConnection() { sascha@1011: } sascha@1011: sascha@1011: public NamedConnection( sascha@3076: String name, sascha@1011: Connection connection sascha@1011: ) { sascha@1011: this(name, connection, true); sascha@1011: } sascha@1011: sascha@1011: public NamedConnection( sascha@3076: String name, sascha@1011: Connection connection, sascha@1011: boolean cached sascha@1011: ) { sascha@1011: this.name = name; sascha@1011: this.connection = connection; sascha@1011: this.cached = cached; sascha@1011: } sascha@1011: } // class NamedConnection sascha@1011: sascha@998: sascha@998: public class BuildHelper sascha@998: { sascha@1818: protected Node output; sascha@1818: protected Document owner; sascha@1818: protected StackFrames frames; sascha@1818: protected List connections; sascha@1818: protected Map statements; sascha@1818: protected Deque> connectionsStack; sascha@998: sascha@998: public BuildHelper( sascha@1011: Node output, sascha@1011: List connections, sascha@1011: Map parameters sascha@998: ) { sascha@1011: if (connections.isEmpty()) { sascha@1011: throw new IllegalArgumentException("no connections given."); sascha@1011: } sascha@1011: sascha@1011: this.connections = connections; sascha@1818: connectionsStack = sascha@1818: new ArrayDeque>(); sascha@1011: this.output = output; sascha@1011: frames = new StackFrames(parameters); sascha@1011: owner = getOwnerDocument(output); sascha@1011: statements = sascha@1011: new HashMap(); sascha@998: } sascha@998: sascha@998: public void build() throws SQLException { sascha@998: try { sascha@998: synchronized (template) { sascha@998: for (Node current: rootsToList()) { sascha@998: build(output, current); sascha@998: } sascha@998: } sascha@998: } sascha@998: finally { sascha@998: closeStatements(); sascha@998: } sascha@998: } sascha@998: sascha@998: protected void closeStatements() { sascha@998: for (CompiledStatement.Instance csi: statements.values()) { sascha@998: csi.close(); sascha@998: } sascha@998: statements.clear(); sascha@998: } sascha@998: felix@1890: /** felix@1890: * Handle a \ node. felix@1890: */ sascha@998: protected void context(Node parent, Element current) sascha@998: throws SQLException sascha@998: { sascha@1023: log.debug("dc:context"); sascha@998: sascha@1023: NodeList subs = current.getChildNodes(); sascha@1023: int S = subs.getLength(); sascha@1023: felix@1859: // Check only direct children. sascha@1023: Node stmntNode = null; sascha@1023: for (int i = 0; i < S; ++i) { sascha@1023: Node node = subs.item(i); sascha@1023: String ns; sascha@1023: if (node.getNodeType() == Node.ELEMENT_NODE felix@1890: && node.getLocalName().equals("statement") sascha@1023: && (ns = node.getNamespaceURI()) != null sascha@1023: && ns.equals(DC_NAMESPACE_URI)) { sascha@1023: stmntNode = node; sascha@1023: break; sascha@1023: } sascha@998: } sascha@998: sascha@1023: if (stmntNode == null) { sascha@1023: log.warn("dc:context: cannot find statement"); sascha@998: return; sascha@998: } sascha@998: felix@1890: String stmntText = stmntNode.getTextContent(); felix@1890: sascha@1011: String con = current.getAttribute("connection"); sascha@1011: sascha@1011: String key = con + "-" + stmntText; sascha@1011: sascha@1011: CompiledStatement.Instance csi = statements.get(key); sascha@998: sascha@998: if (csi == null) { sascha@998: CompiledStatement cs = compiledStatements.get(stmntText); sascha@998: csi = cs.new Instance(); sascha@1011: statements.put(key, csi); sascha@998: } sascha@998: sascha@1011: NamedConnection connection = connectionsStack.isEmpty() sascha@1011: ? connections.get(0) sascha@1818: : connectionsStack.peek().getA(); sascha@998: sascha@1011: if (con.length() > 0) { sascha@1011: for (NamedConnection nc: connections) { sascha@1011: if (con.equals(nc.name)) { sascha@1011: connection = nc; sascha@1011: break; sascha@998: } sascha@998: } sascha@1011: } sascha@1011: sascha@1023: ResultData rd = csi.execute( sascha@1023: connection.connection, sascha@1023: frames, sascha@1023: connection.cached); sascha@1011: sascha@1023: // only descent if there are results sascha@1023: if (!rd.isEmpty()) { teichmann@4051: connectionsStack.push( teichmann@4051: new Pair(connection, rd)); sascha@1023: try { sascha@1023: for (int i = 0; i < S; ++i) { sascha@1023: build(parent, subs.item(i)); sascha@1011: } sascha@998: } sascha@1023: finally { sascha@1023: connectionsStack.pop(); sascha@1023: } sascha@998: } sascha@1023: } sascha@1023: felix@1890: /** felix@1890: * Kind of foreach over results of a statement within a context. felix@1890: */ sascha@3076: protected void elements(Node parent, Element current) sascha@1023: throws SQLException sascha@1023: { sascha@1023: log.debug("dc:elements"); sascha@1023: sascha@1818: if (connectionsStack.isEmpty()) { sascha@1023: log.warn("dc:elements without having results"); sascha@1023: return; sascha@1023: } sascha@1023: sascha@1023: NodeList subs = current.getChildNodes(); sascha@1023: int S = subs.getLength(); sascha@1023: sascha@1023: if (S == 0) { sascha@1023: log.debug("dc:elements has no children"); sascha@1023: return; sascha@1023: } sascha@1023: sascha@1818: ResultData rd = connectionsStack.peek().getB(); sascha@1023: sascha@1023: String [] columns = rd.getColumnLabels(); sascha@1023: sascha@1023: //if (log.isDebugEnabled()) { sascha@1023: // log.debug("pushing vars: " sascha@1023: // + java.util.Arrays.toString(columns)); sascha@1023: //} sascha@1023: sascha@1023: for (Object [] row: rd.getRows()) { sascha@1023: frames.enter(); sascha@1023: try { sascha@1023: frames.put(columns, row); sascha@1023: //if (log.isDebugEnabled()) { sascha@1023: // log.debug("current vars: " + frames.dump()); sascha@1023: //} sascha@1023: for (int i = 0; i < S; ++i) { sascha@1023: build(parent, subs.item(i)); sascha@1023: } sascha@1023: } sascha@1023: finally { sascha@1023: frames.leave(); sascha@1023: } sascha@1011: } sascha@998: } sascha@998: felix@1890: /** felix@1890: * Create element. felix@1890: */ sascha@998: protected void element(Node parent, Element current) sascha@998: throws SQLException sascha@998: { sascha@998: String attr = expand(current.getAttribute("name")); sascha@998: sascha@998: if (log.isDebugEnabled()) { sascha@998: log.debug("dc:element -> '" + attr + "'"); sascha@998: } sascha@998: sascha@998: if (attr.length() == 0) { sascha@998: log.warn("no name attribute found"); sascha@998: return; sascha@998: } sascha@998: sascha@998: Element element = owner.createElement(attr); sascha@998: sascha@998: NodeList children = current.getChildNodes(); sascha@998: for (int i = 0, N = children.getLength(); i < N; ++i) { sascha@998: build(element, children.item(i)); sascha@998: } sascha@998: sascha@998: parent.appendChild(element); sascha@998: } sascha@998: sascha@998: protected void text(Node parent, Element current) sascha@998: throws SQLException sascha@998: { sascha@998: log.debug("dc:text"); sascha@998: String value = expand(current.getTextContent()); sascha@998: parent.appendChild(owner.createTextNode(value)); sascha@998: } sascha@998: felix@1890: /** felix@1890: * Add attribute to an element felix@3571: * @see Element felix@1890: */ sascha@998: protected void attribute(Node parent, Element current) { sascha@998: sascha@998: if (parent.getNodeType() != Node.ELEMENT_NODE) { sascha@998: log.warn("need element here"); sascha@998: return; sascha@998: } sascha@998: sascha@998: String name = expand(current.getAttribute("name")); sascha@998: String value = expand(current.getAttribute("value")); sascha@998: sascha@998: Element element = (Element)parent; sascha@998: sascha@998: element.setAttribute(name, value); sascha@998: } sascha@998: sascha@998: protected void callMacro(Node parent, Element current) sascha@998: throws SQLException sascha@998: { sascha@998: String name = current.getAttribute("name"); sascha@998: sascha@998: if (name.length() == 0) { sascha@998: log.warn("missing 'name' attribute in 'call-macro'"); sascha@998: return; sascha@998: } sascha@998: sascha@998: NodeList macros = template.getElementsByTagNameNS( sascha@998: DC_NAMESPACE_URI, "macro"); sascha@998: sascha@998: for (int i = 0, N = macros.getLength(); i < N; ++i) { felix@1890: Element macro = (Element) macros.item(i); sascha@998: if (name.equals(macro.getAttribute("name"))) { sascha@998: NodeList subs = macro.getChildNodes(); sascha@998: for (int j = 0, M = subs.getLength(); j < M; ++j) { sascha@998: build(parent, subs.item(j)); sascha@998: } sascha@998: return; sascha@998: } sascha@998: } sascha@998: sascha@998: log.warn("no macro '" + name + "' found."); sascha@998: } sascha@998: sascha@998: protected void ifClause(Node parent, Element current) sascha@998: throws SQLException sascha@998: { sascha@998: String test = current.getAttribute("test"); sascha@998: sascha@998: if (test.length() == 0) { sascha@998: log.warn("missing 'test' attribute in 'if'"); sascha@998: return; sascha@998: } sascha@998: sascha@998: Boolean result = evaluateXPath(test); sascha@998: sascha@998: if (result != null && result.booleanValue()) { sascha@998: NodeList subs = current.getChildNodes(); sascha@998: for (int i = 0, N = subs.getLength(); i < N; ++i) { sascha@998: build(parent, subs.item(i)); sascha@998: } sascha@998: } sascha@998: } sascha@998: sascha@998: protected void choose(Node parent, Element current) sascha@998: throws SQLException sascha@998: { sascha@998: Node branch = null; sascha@998: sascha@998: NodeList children = current.getChildNodes(); sascha@998: for (int i = 0, N = children.getLength(); i < N; ++i) { sascha@998: Node child = children.item(i); sascha@998: String ns = child.getNamespaceURI(); sascha@998: if (ns == null sascha@998: || !ns.equals(DC_NAMESPACE_URI) sascha@998: || child.getNodeType() != Node.ELEMENT_NODE sascha@998: ) { sascha@998: continue; sascha@998: } sascha@998: String name = child.getLocalName(); sascha@998: if ("when".equals(name)) { sascha@998: Element when = (Element)child; sascha@998: String test = when.getAttribute("test"); sascha@998: if (test.length() == 0) { sascha@998: log.warn("no 'test' attribute found for when"); sascha@998: continue; sascha@998: } sascha@998: sascha@998: Boolean result = evaluateXPath(test); sascha@998: if (result != null && result.booleanValue()) { sascha@998: branch = child; sascha@998: break; sascha@998: } sascha@998: sascha@998: continue; sascha@998: } sascha@998: else if ("otherwise".equals(name)) { sascha@998: branch = child; sascha@998: // No break here. sascha@998: } sascha@998: } sascha@998: sascha@998: if (branch != null) { sascha@998: NodeList subs = branch.getChildNodes(); sascha@998: for (int i = 0, N = subs.getLength(); i < N; ++i) { sascha@998: build(parent, subs.item(i)); sascha@998: } sascha@998: } sascha@998: } sascha@998: sascha@998: protected Boolean evaluateXPath(String expr) { sascha@998: sascha@998: if (log.isDebugEnabled()) { sascha@998: log.debug("evaluate: '" + expr + "'"); sascha@998: } sascha@998: sascha@998: try { sascha@998: XPath xpath = XPATH_FACTORY.newXPath(); sascha@998: xpath.setXPathVariableResolver(frames); sascha@998: xpath.setXPathFunctionResolver(FunctionResolver.FUNCTIONS); sascha@998: Object result = xpath.evaluate( sascha@998: expr, EVAL_DOCUMENT, XPathConstants.BOOLEAN); sascha@998: sascha@998: return result instanceof Boolean sascha@998: ? (Boolean)result sascha@998: : null; sascha@998: } sascha@998: catch (XPathExpressionException xfce) { felix@1859: log.error("expression: " + expr, xfce); sascha@998: } sascha@998: return null; sascha@998: } sascha@998: sascha@998: protected void convert(Node parent, Element current) { sascha@998: sascha@998: String variable = expand(current.getAttribute("var")); sascha@998: String type = expand(current.getAttribute("type")); sascha@998: sascha@1716: Object [] result = new Object[1]; sascha@1716: sascha@1716: if (frames.getStore(variable, result)) { sascha@1716: Object object = TypeConverter.convert(result[0], type); sascha@1716: frames.put(variable.toUpperCase(), object); sascha@998: } sascha@998: } sascha@998: sascha@998: protected String expand(String s) { sascha@998: Matcher m = CompiledStatement.VAR.matcher(s); sascha@998: sascha@1716: Object [] result = new Object[1]; sascha@1716: sascha@998: StringBuffer sb = new StringBuffer(); sascha@998: while (m.find()) { sascha@998: String key = m.group(1); sascha@1716: result[0] = null; sascha@1716: if (frames.getStore(key, result)) { sascha@1716: m.appendReplacement( sascha@1716: sb, result[0] != null ? result[0].toString() : ""); sascha@1716: } sascha@1716: else { sascha@1716: m.appendReplacement(sb, "\\${" + key + "}"); sascha@1716: } sascha@998: } sascha@998: m.appendTail(sb); sascha@998: return sb.toString(); sascha@998: } sascha@998: sascha@998: protected void build(Node parent, Node current) sascha@998: throws SQLException sascha@998: { sascha@998: String ns = current.getNamespaceURI(); sascha@998: if (ns != null && ns.equals(DC_NAMESPACE_URI)) { sascha@998: if (current.getNodeType() != Node.ELEMENT_NODE) { sascha@998: log.warn("need elements here"); sascha@998: } sascha@998: else { sascha@998: String localName = current.getLocalName(); sascha@998: if ("attribute".equals(localName)) { sascha@998: attribute(parent, (Element)current); sascha@998: } sascha@998: else if ("context".equals(localName)) { sascha@998: context(parent, (Element)current); sascha@998: } sascha@998: else if ("if".equals(localName)) { sascha@998: ifClause(parent, (Element)current); sascha@998: } sascha@998: else if ("choose".equals(localName)) { sascha@998: choose(parent, (Element)current); sascha@998: } sascha@998: else if ("call-macro".equals(localName)) { sascha@998: callMacro(parent, (Element)current); sascha@998: } sascha@998: else if ("macro".equals(localName)) { felix@1029: // Simply ignore the definition. sascha@998: } sascha@998: else if ("element".equals(localName)) { sascha@998: element(parent, (Element)current); sascha@998: } sascha@1023: else if ("elements".equals(localName)) { sascha@1023: elements(parent, (Element)current); sascha@1023: } sascha@998: else if ("text".equals(localName)) { sascha@998: text(parent, (Element)current); sascha@998: } sascha@1023: else if ("comment".equals(localName) sascha@1023: || "statement".equals(localName)) { sascha@1023: // ignore comments and statements in output sascha@1017: } sascha@998: else if ("convert".equals(localName)) { sascha@998: convert(parent, (Element)current); sascha@998: } sascha@998: else { sascha@998: log.warn("unknown '" + localName + "' -> ignore"); sascha@998: } sascha@998: } sascha@998: return; sascha@998: } sascha@998: sascha@998: if (current.getNodeType() == Node.TEXT_NODE) { sascha@998: String txt = current.getNodeValue(); sascha@998: if (txt != null && txt.trim().length() == 0) { sascha@998: return; sascha@998: } sascha@998: } sascha@998: sascha@998: Node copy = owner.importNode(current, false); sascha@998: sascha@998: NodeList children = current.getChildNodes(); sascha@998: for (int i = 0, N = children.getLength(); i < N; ++i) { sascha@998: build(copy, children.item(i)); sascha@998: } sascha@998: parent.appendChild(copy); sascha@998: } sascha@998: } // class BuildHelper sascha@998: sascha@998: sascha@998: public Builder() { sascha@998: compiledStatements = new HashMap(); sascha@998: } sascha@998: sascha@998: public Builder(Document template) { sascha@998: this(); sascha@998: this.template = template; sascha@998: compileStatements(); sascha@998: } sascha@998: sascha@998: protected void compileStatements() { sascha@998: sascha@998: NodeList nodes = template.getElementsByTagNameNS( sascha@998: DC_NAMESPACE_URI, "statement"); sascha@998: sascha@998: for (int i = 0, N = nodes.getLength(); i < N; ++i) { sascha@998: Element stmntElement = (Element)nodes.item(i); sascha@998: String stmnt = trimStatement(stmntElement.getTextContent()); sascha@998: if (stmnt == null || stmnt.length() == 0) { sascha@998: throw new IllegalArgumentException("found empty statement"); sascha@998: } sascha@998: CompiledStatement cs = new CompiledStatement(stmnt); felix@1029: // For faster lookup store a shortend string into the template. sascha@998: stmnt = "s" + i; sascha@998: stmntElement.setTextContent(stmnt); sascha@998: compiledStatements.put(stmnt, cs); sascha@998: } sascha@998: } sascha@998: sascha@998: protected List rootsToList() { sascha@998: sascha@998: NodeList roots = template.getElementsByTagNameNS( sascha@998: DC_NAMESPACE_URI, "template"); sascha@998: sascha@998: List elements = new ArrayList(); sascha@998: sascha@998: for (int i = 0, N = roots.getLength(); i < N; ++i) { sascha@998: NodeList rootChildren = roots.item(i).getChildNodes(); sascha@998: for (int j = 0, M = rootChildren.getLength(); j < M; ++j) { sascha@998: Node child = rootChildren.item(j); sascha@998: if (child.getNodeType() == Node.ELEMENT_NODE) { sascha@998: elements.add(child); sascha@998: } sascha@998: } sascha@998: } sascha@998: sascha@998: return elements; sascha@998: } sascha@998: sascha@998: protected static final String trimStatement(String stmnt) { sascha@998: if (stmnt == null) return null; sascha@998: //XXX: Maybe a bit to radical for multiline strings? sascha@998: return STRIP_LINE_INDENT.matcher(stmnt.trim()).replaceAll(" "); sascha@998: } sascha@998: sascha@998: protected static Document getOwnerDocument(Node node) { sascha@998: Document document = node.getOwnerDocument(); sascha@998: return document != null ? document : (Document)node; sascha@998: } sascha@998: sascha@1011: private static final List wrap(Connection connection) { sascha@1011: List list = new ArrayList(1); sascha@1015: list.add(new NamedConnection(DEFAULT_CONNECTION_NAME, connection)); sascha@1011: return list; sascha@1011: } sascha@1011: sascha@998: public void build( sascha@998: Connection connection, sascha@998: Node output, sascha@998: Map parameters sascha@998: ) sascha@998: throws SQLException sascha@998: { sascha@1011: build(wrap(connection), output, parameters); sascha@1011: } sascha@1011: sascha@1011: public void build( sascha@1011: List connections, sascha@1011: Node output, sascha@1011: Map parameters sascha@1011: ) sascha@1011: throws SQLException sascha@1011: { sascha@1011: BuildHelper helper = new BuildHelper(output, connections, parameters); sascha@998: sascha@998: helper.build(); sascha@998: } sascha@998: } sascha@998: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :