view flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java @ 4496:d8992459b408

Add method to return the facets of an artifact This methos should be used to get the facets of an artifact instead of accessing the facets member variable directly.
author Björn Ricks <bjoern.ricks@intevation.de>
date Wed, 14 Nov 2012 11:11:04 +0100
parents daf0919df76d
children 1f38fa929986
line wrap: on
line source
package de.intevation.flys.artifacts.datacage.templating;

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 java.util.Deque;
import java.util.ArrayDeque;

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 de.intevation.flys.utils.Pair;

import org.apache.log4j.Logger;

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

    public static final String CONNECTION_USER   = "user";
    public static final String CONNECTION_SYSTEM = "system";
    public static final String DEFAULT_CONNECTION_NAME = CONNECTION_SYSTEM;

    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 static class NamedConnection {

        protected String     name;
        protected Connection connection;
        protected boolean    cached;

        public NamedConnection() {
        }

        public NamedConnection(
            String     name,
            Connection connection
        ) {
            this(name, connection, true);
        }

        public NamedConnection(
            String     name,
            Connection connection,
            boolean    cached
        ) {
            this.name       = name;
            this.connection = connection;
            this.cached     = cached;
        }
    } // class NamedConnection


    public class BuildHelper
    {
        protected Node                                     output;
        protected Document                                 owner;
        protected StackFrames                              frames;
        protected List<NamedConnection>                    connections;
        protected Map<String, CompiledStatement.Instance>  statements;
        protected Deque<Pair<NamedConnection, ResultData>> connectionsStack;

        public BuildHelper(
            Node                  output,
            List<NamedConnection> connections,
            Map<String, Object>   parameters
        ) {
            if (connections.isEmpty()) {
                throw new IllegalArgumentException("no connections given.");
            }

            this.connections = connections;
            connectionsStack =
                new ArrayDeque<Pair<NamedConnection, ResultData>>();
            this.output      = output;
            frames           = new StackFrames(parameters);
            owner            = getOwnerDocument(output);
            statements =
                new HashMap<String, CompiledStatement.Instance>();
        }

        public void build() throws SQLException {
            try {
                synchronized (template) {
                    for (Node current: rootsToList()) {
                        build(output, current);
                    }
                }
            }
            finally {
                closeStatements();
            }
        }

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

        /**
         * Handle a \<context\> node.
         */
        protected void context(Node parent, Element current)
        throws SQLException
        {
            log.debug("dc:context");

            NodeList subs = current.getChildNodes();
            int S = subs.getLength();

            // Check only direct children.
            Node stmntNode = null;
            for (int i = 0; i < S; ++i) {
                Node node = subs.item(i);
                String ns;
                if (node.getNodeType() == Node.ELEMENT_NODE
                && node.getLocalName().equals("statement")
                && (ns = node.getNamespaceURI()) != null
                && ns.equals(DC_NAMESPACE_URI)) {
                    stmntNode = node;
                    break;
                }
            }

            if (stmntNode == null) {
                log.warn("dc:context: cannot find statement");
                return;
            }

            String stmntText = stmntNode.getTextContent();

            String con = current.getAttribute("connection");

            String key = con + "-" + stmntText;

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

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

            NamedConnection connection = connectionsStack.isEmpty()
                ? connections.get(0)
                : connectionsStack.peek().getA();

            if (con.length() > 0) {
                for (NamedConnection nc: connections) {
                    if (con.equals(nc.name)) {
                        connection = nc;
                        break;
                    }
                }
            }

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

            // only descent if there are results
            if (!rd.isEmpty()) {
                connectionsStack.push(
                    new Pair<NamedConnection, ResultData>(connection, rd));
                try {
                    for (int i = 0; i < S; ++i) {
                        build(parent, subs.item(i));
                    }
                }
                finally {
                    connectionsStack.pop();
                }
            }
        }

        /**
         * Kind of foreach over results of a statement within a context.
         */
        protected void elements(Node parent, Element current)
        throws SQLException
        {
            log.debug("dc:elements");

            if (connectionsStack.isEmpty()) {
                log.warn("dc:elements without having results");
                return;
            }

            NodeList subs = current.getChildNodes();
            int S = subs.getLength();

            if (S == 0) {
                log.debug("dc:elements has no children");
                return;
            }

            ResultData rd = connectionsStack.peek().getB();

            String [] columns = rd.getColumnLabels();

            //if (log.isDebugEnabled()) {
            //    log.debug("pushing vars: "
            //        + java.util.Arrays.toString(columns));
            //}

            for (Object [] row: rd.getRows()) {
                frames.enter();
                try {
                    frames.put(columns, row);
                    //if (log.isDebugEnabled()) {
                    //    log.debug("current vars: " + frames.dump());
                    //}
                    for (int i = 0; i < S; ++i) {
                        build(parent, subs.item(i));
                    }
                }
                finally {
                    frames.leave();
                }
            }
        }

        /**
         * Create element.
         */
        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));
        }

        /**
         * Add attribute to an element
         * @see Element
         */
        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("expression: " + expr, xfce);
            }
            return null;
        }

        protected void convert(Node parent, Element current) {

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

            Object [] result = new Object[1];

            if (frames.getStore(variable, result)) {
                Object object = TypeConverter.convert(result[0], type);
                frames.put(variable.toUpperCase(), object);
            }
        }

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

            Object [] result = new Object[1];

            StringBuffer sb = new StringBuffer();
            while (m.find()) {
                String key = m.group(1);
                result[0] = null;
                if (frames.getStore(key, result)) {
                    m.appendReplacement(
                        sb, result[0] != null ? result[0].toString() : "");
                }
                else {
                    m.appendReplacement(sb, "\\${" + key + "}");
                }
            }
            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 ("elements".equals(localName)) {
                        elements(parent, (Element)current);
                    }
                    else if ("text".equals(localName)) {
                        text(parent, (Element)current);
                    }
                    else if ("comment".equals(localName)
                         ||  "statement".equals(localName)) {
                        // ignore comments and statements in output
                    }
                    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;
                }
            }

            if (current.getNodeType() == Node.COMMENT_NODE) {
                // Ignore XML comments
                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 List<Node> rootsToList() {

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

        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;
    }

    private static final List<NamedConnection> wrap(Connection connection) {
        List<NamedConnection> list = new ArrayList<NamedConnection>(1);
        list.add(new NamedConnection(DEFAULT_CONNECTION_NAME, connection));
        return list;
    }

    public void build(
        Connection          connection,
        Node                output,
        Map<String, Object> parameters
    )
    throws SQLException
    {
        build(wrap(connection), output, parameters);
    }

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

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

http://dive4elements.wald.intevation.org