view flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java @ 976:d830c398c8f4

Improved XPath handling for absent flags. flys-artifacts/trunk@2402 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 25 Jul 2011 14:12:14 +0000
parents c30ada285d45
children 598a5e911118
line wrap: on
line source
package de.intevation.flys.artifacts.services.meta;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathConstants;

import java.sql.SQLException;
import java.sql.Connection;

import de.intevation.artifacts.common.utils.XMLUtils;

import org.apache.log4j.Logger;

public class Builder
{
    private static Logger log = Logger.getLogger(Builder.class);

    public static final Pattern STRIP_LINE_INDENT =
        Pattern.compile("\\s*\\r?\\n\\s*");

    public static final String DC_NAMESPACE_URI =
        "http://www.intevation.org/2011/Datacage";

    private static final Document EVAL_DOCUMENT =
        XMLUtils.newDocument();

    private static final XPathFactory XPATH_FACTORY =
        XPathFactory.newInstance();

    protected Document template;

    protected Map<String, CompiledStatement> compiledStatements;


    public class BuildHelper
    {
        protected Node                                    output;
        protected Document                                owner;
        protected StackFrames                             frames;
        protected Connection                              connection;
        protected Map<String, CompiledStatement.Instance> statements;

        public BuildHelper(
            Node                output,
            Connection          connection,
            Map<String, Object> parameters
        ) {
            this.output     = output;
            this.connection = connection;
            frames          = new StackFrames(parameters);
            statements      = new HashMap<String, CompiledStatement.Instance>();
            owner           = getOwnerDocument(output);
        }

        public void build(List<Node> elements) throws SQLException {
            try {
                for (Node current: elements) {
                    build(output, current);
                }
            }
            finally {
                closeStatements();
            }
        }

        protected void closeStatements() {
            for (CompiledStatement.Instance csi: statements.values()) {
                csi.close();
            }
            statements.clear();
        }

        protected void context(Node parent, Element current)
        throws SQLException
        {
            NodeList elements = current.getElementsByTagNameNS(
                DC_NAMESPACE_URI, "elements");

            if (elements.getLength() < 1) {
                log.warn("no elements found -> ignore");
                return;
            }

            NodeList subs = elements.item(0).getChildNodes();
            int S = subs.getLength();

            if (S < 1) {
                log.warn("elements is empty -> ignore");
                return;
            }

            NodeList stmntNode = current.getElementsByTagNameNS(
                DC_NAMESPACE_URI, "statement");

            if (stmntNode.getLength() < 1) {
                log.warn("dc:context: too less statements");
                return;
            }

            String stmntText = stmntNode.item(0).getTextContent();

            CompiledStatement.Instance csi = statements.get(stmntText);

            if (csi == null) {
                CompiledStatement cs = compiledStatements.get(stmntText);
                csi = cs.new Instance();
                statements.put(stmntText, csi);
            }

            ResultData rd = csi.execute(connection, frames);

            String [] columns = rd.getColumnLabels();


            for (Object [] row: rd.getRows()) {
                frames.enter();
                try {
                    frames.put(columns, row);
                    for (int i = 0; i < S; ++i) {
                        build(parent, subs.item(i));
                    }
                }
                finally {
                    frames.leave();
                }
            }
        }

        protected void element(Node parent, Element current)
        throws SQLException
        {
            String attr = expand(current.getAttribute("name"));

            if (log.isDebugEnabled()) {
                log.debug("dc:element -> '" + attr + "'");
            }

            if (attr.length() == 0) {
                log.warn("no name attribute found");
                return;
            }

            Element element = owner.createElement(attr);

            NodeList children = current.getChildNodes();
            for (int i = 0, N = children.getLength(); i < N; ++i) {
                build(element, children.item(i));
            }

            parent.appendChild(element);
        }

        protected void text(Node parent, Element current)
        throws SQLException
        {
            log.debug("dc:text");
            String value = expand(current.getTextContent());
            parent.appendChild(owner.createTextNode(value));
        }


        protected void attribute(Node parent, Element current) {

            if (parent.getNodeType() != Node.ELEMENT_NODE) {
                log.warn("need element here");
                return;
            }

            String name  = expand(current.getAttribute("name"));
            String value = expand(current.getAttribute("value"));

            Element element = (Element)parent;

            element.setAttribute(name, value);
        }

        protected void callMacro(Node parent, Element current)
        throws SQLException
        {
            String name = current.getAttribute("name");

            if (name.length() == 0) {
                log.warn("missing 'name' attribute in 'call-macro'");
                return;
            }

            NodeList macros = template.getElementsByTagNameNS(
                DC_NAMESPACE_URI, "macro");

            for (int i = 0, N = macros.getLength(); i < N; ++i) {
                Element macro = (Element)macros.item(i);
                if (name.equals(macro.getAttribute("name"))) {
                    NodeList subs = macro.getChildNodes();
                    for (int j = 0, M = subs.getLength(); j < M; ++j) {
                        build(parent, subs.item(j));
                    }
                    return;
                }
            }

            log.warn("no macro '" + name + "' found.");
        }

        protected void ifClause(Node parent, Element current)
        throws SQLException
        {
            String test = current.getAttribute("test");

            if (test.length() == 0) {
                log.warn("missing 'test' attribute in 'if'");
                return;
            }

            Boolean result = evaluateXPath(test);

            if (result != null && result.booleanValue()) {
                NodeList subs = current.getChildNodes();
                for (int i = 0, N = subs.getLength(); i < N; ++i) {
                    build(parent, subs.item(i));
                }
            }
        }

        protected void choose(Node parent, Element current)
        throws SQLException
        {
            Node branch = null;

            NodeList children = current.getChildNodes();
            for (int i = 0, N = children.getLength(); i < N; ++i) {
                Node child = children.item(i);
                String ns = child.getNamespaceURI();
                if (ns == null
                || !ns.equals(DC_NAMESPACE_URI)
                || child.getNodeType() != Node.ELEMENT_NODE
                ) {
                    continue;
                }
                String name = child.getLocalName();
                if ("when".equals(name)) {
                    Element when = (Element)child;
                    String test = when.getAttribute("test");
                    if (test.length() == 0) {
                        log.warn("no 'test' attribute found for when");
                        continue;
                    }

                    Boolean result = evaluateXPath(test);
                    if (result != null && result.booleanValue()) {
                        branch = child;
                        break;
                    }

                    continue;
                }
                else if ("otherwise".equals(name)) {
                    branch = child;
                    // No break here.
                }
            }

            if (branch != null) {
                NodeList subs = branch.getChildNodes();
                for (int i = 0, N = subs.getLength(); i < N; ++i) {
                    build(parent, subs.item(i));
                }
            }
        }

        protected Boolean evaluateXPath(String expr) {

            if (log.isDebugEnabled()) {
                log.debug("evaluate: '" + expr + "'");
            }

            try {
                XPath xpath = XPATH_FACTORY.newXPath();
                xpath.setXPathVariableResolver(frames);
                xpath.setXPathFunctionResolver(FunctionResolver.FUNCTIONS);
                Object result = xpath.evaluate(
                    expr, EVAL_DOCUMENT, XPathConstants.BOOLEAN);

                return result instanceof Boolean
                    ? (Boolean)result
                    : null;
            }
            catch (XPathExpressionException xfce) {
                log.error(xfce);
            }
            return null;
        }

        protected void convert(Node parent, Element current) {

            String variable = expand(current.getAttribute("var"));
            String type     = expand(current.getAttribute("type"));

            if (frames.containsKey(variable)) {
                Object object = TypeConverter.convert(
                    frames.get(variable),
                    type);
                frames.put(variable, object);
            }
        }

        protected String expand(String s) {
            Matcher m = CompiledStatement.VAR.matcher(s);

            StringBuffer sb = new StringBuffer();
            while (m.find()) {
                String key = m.group(1);
                Object value = frames.get(key);
                m.appendReplacement(sb, value != null ? value.toString() : "");
            }
            m.appendTail(sb);
            return sb.toString();
        }

        protected void build(Node parent, Node current)
        throws SQLException
        {
            String ns = current.getNamespaceURI();
            if (ns != null && ns.equals(DC_NAMESPACE_URI)) {
                if (current.getNodeType() != Node.ELEMENT_NODE) {
                    log.warn("need elements here");
                }
                else {
                    String localName = current.getLocalName();
                    if ("attribute".equals(localName)) {
                        attribute(parent, (Element)current);
                    }
                    else if ("context".equals(localName)) {
                        context(parent, (Element)current);
                    }
                    else if ("if".equals(localName)) {
                        ifClause(parent, (Element)current);
                    }
                    else if ("choose".equals(localName)) {
                        choose(parent, (Element)current);
                    }
                    else if ("call-macro".equals(localName)) {
                        callMacro(parent, (Element)current);
                    }
                    else if ("macro".equals(localName)) {
                        // simply ignore the definition.
                    }
                    else if ("element".equals(localName)) {
                        element(parent, (Element)current);
                    }
                    else if ("text".equals(localName)) {
                        text(parent, (Element)current);
                    }
                    else if ("convert".equals(localName)) {
                        convert(parent, (Element)current);
                    }
                    else {
                        log.warn("unknown '" + localName + "' -> ignore");
                    }
                }
                return;
            }

            if (current.getNodeType() == Node.TEXT_NODE) {
                String txt = current.getNodeValue();
                if (txt != null && txt.trim().length() == 0) {
                    return;
                }
            }

            Node copy = owner.importNode(current, false);

            NodeList children = current.getChildNodes();
            for (int i = 0, N = children.getLength(); i < N; ++i) {
                build(copy, children.item(i));
            }
            parent.appendChild(copy);
        }
    } // class BuildHelper


    public Builder() {
        compiledStatements = new HashMap<String, CompiledStatement>();
    }

    public Builder(Document template) {
        this();
        this.template = template;
        compileStatements();
    }

    protected void compileStatements() {

        NodeList nodes = template.getElementsByTagNameNS(
            DC_NAMESPACE_URI, "statement");

        for (int i = 0, N = nodes.getLength(); i < N; ++i) {
            Element stmntElement = (Element)nodes.item(i);
            String stmnt = trimStatement(stmntElement.getTextContent());
            if (stmnt == null || stmnt.length() == 0) {
                throw new IllegalArgumentException("found empty statement");
            }
            CompiledStatement cs = new CompiledStatement(stmnt);
            // for faster lookup store a shortend string into the template
            stmnt = "s" + i;
            stmntElement.setTextContent(stmnt);
            compiledStatements.put(stmnt, cs);
        }
    }

    protected static List<Node> rootsToList(NodeList roots) {

        List<Node> elements = new ArrayList<Node>();

        for (int i = 0, N = roots.getLength(); i < N; ++i) {
            NodeList rootChildren = roots.item(i).getChildNodes();
            for (int j = 0, M = rootChildren.getLength(); j < M; ++j) {
                Node child = rootChildren.item(j);
                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    elements.add(child);
                }
            }
        }

        return elements;
    }

    protected static final String trimStatement(String stmnt) {
        if (stmnt == null) return null;
        //XXX: Maybe a bit to radical for multiline strings?
        return STRIP_LINE_INDENT.matcher(stmnt.trim()).replaceAll(" ");
    }

    protected static Document getOwnerDocument(Node node) {
        Document document = node.getOwnerDocument();
        return document != null ? document : (Document)node;
    }

    public void build(
        Connection          connection,
        Node                output,
        Map<String, Object> parameters
    )
    throws SQLException
    {
        BuildHelper helper = new BuildHelper(output, connection, parameters);

        NodeList roots = template.getElementsByTagNameNS(
            DC_NAMESPACE_URI, "template");

        helper.build(rootsToList(roots));
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org