org.trancecode.xproc.Environment.java Source code

Java tutorial

Introduction

Here is the source code for org.trancecode.xproc.Environment.java

Source

/*
 * Copyright (C) 2008 Herve Quiroz
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 *
 * $Id$
 */
package org.trancecode.xproc;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;

import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathCompiler;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmValue;
import org.apache.commons.lang.StringUtils;
import org.trancecode.api.ReturnsNullable;
import org.trancecode.collection.TcMaps;
import org.trancecode.logging.Logger;
import org.trancecode.xml.Location;
import org.trancecode.xml.saxon.Saxon;
import org.trancecode.xml.saxon.SaxonAxis;
import org.trancecode.xml.saxon.SaxonBuilder;
import org.trancecode.xml.saxon.SaxonLocation;
import org.trancecode.xml.saxon.SaxonNamespaces;
import org.trancecode.xproc.api.PipelineException;
import org.trancecode.xproc.api.XProcException;
import org.trancecode.xproc.binding.PortBinding;
import org.trancecode.xproc.port.EnvironmentPort;
import org.trancecode.xproc.port.Port;
import org.trancecode.xproc.port.PortFunctions;
import org.trancecode.xproc.port.PortReference;
import org.trancecode.xproc.port.XProcPorts;
import org.trancecode.xproc.step.Step;
import org.trancecode.xproc.variable.Variable;

/**
 * @author Herve Quiroz
 */
public final class Environment {
    private static final Logger LOG = Logger.getLogger(Environment.class);

    private static final QName ATTRIBUTE_NAME = new QName("name");
    private static final QName ATTRIBUTE_NAMESPACE = new QName("namespace");
    private static final QName ATTRIBUTE_VALUE = new QName("value");
    private static final QName ELEMENT_PARAM = XProcXmlModel.xprocStepNamespace().newSaxonQName("param");
    private static final QName ELEMENT_RESULT = XProcXmlModel.xprocStepNamespace().newSaxonQName("result");

    private static final ThreadLocal<Environment> CURRENT_ENVIRONMENT = new ThreadLocal<>();
    private static final ThreadLocal<XdmNode> CURRENT_XPATH_CONTEXT = new ThreadLocal<>();
    private static final ThreadLocal<XdmNode> CURRENT_NAMESPACE_CONTEXT = new ThreadLocal<>();

    private final EnvironmentPort defaultReadablePort;
    private final Map<QName, String> inheritedVariables;
    private final Map<QName, String> localVariables;
    private final PipelineContext configuration;
    private final Map<PortReference, EnvironmentPort> ports;
    private final Step pipeline;
    private final EnvironmentPort defaultParametersPort;
    private final EnvironmentPort xpathContextPort;

    public static void setCurrentNamespaceContext(final XdmNode node) {
        CURRENT_NAMESPACE_CONTEXT.set(node);
    }

    public static XdmNode getCurrentNamespaceContext() {
        return CURRENT_NAMESPACE_CONTEXT.get();
    }

    public static void setCurrentXPathContext(final XdmNode node) {
        CURRENT_XPATH_CONTEXT.set(node);
    }

    public static XdmNode getCurrentXPathContext() {
        return CURRENT_XPATH_CONTEXT.get();
    }

    public static void setCurrentEnvironment(final Environment environment) {
        CURRENT_ENVIRONMENT.set(environment);
    }

    public void setCurrentEnvironment() {
        setCurrentEnvironment(this);
    }

    public static Environment getCurrentEnvironment() {
        return CURRENT_ENVIRONMENT.get();
    }

    private static Map<PortReference, EnvironmentPort> getPortsMap(final Iterable<EnvironmentPort> ports) {
        return Maps.uniqueIndex(ports, PortFunctions.getPortReference());
    }

    public static Environment newEnvironment(final Step pipeline, final PipelineContext configuration) {
        final Map<QName, String> variables = ImmutableMap.of();
        final Iterable<EnvironmentPort> ports = ImmutableList.of();
        return new Environment(pipeline, configuration, ports, null, null, null, variables, variables);
    }

    private Environment(final Step pipeline, final PipelineContext configuration,
            final Iterable<EnvironmentPort> ports, final EnvironmentPort defaultReadablePort,
            final EnvironmentPort defaultParametersPort, final EnvironmentPort xpathContextPort,
            final Map<QName, String> inheritedVariables, final Map<QName, String> localVariables) {
        this(pipeline, configuration, getPortsMap(ports), defaultReadablePort, defaultParametersPort,
                xpathContextPort, inheritedVariables, localVariables);
    }

    private Environment(final Step pipeline, final PipelineContext configuration,
            final Map<PortReference, EnvironmentPort> ports, final EnvironmentPort defaultReadablePort,
            final EnvironmentPort defaultParametersPort, final EnvironmentPort xpathContextPort,
            final Map<QName, String> inheritedVariables, final Map<QName, String> localVariables) {
        this.pipeline = pipeline;
        this.configuration = configuration;
        this.ports = ImmutableMap.copyOf(ports);
        this.defaultReadablePort = defaultReadablePort;
        this.defaultParametersPort = defaultParametersPort;
        this.xpathContextPort = xpathContextPort;
        this.inheritedVariables = ImmutableMap.copyOf(inheritedVariables);
        this.localVariables = ImmutableMap.copyOf(localVariables);
    }

    private Environment setupStepEnvironment(final Step step, final boolean evaluteVariables) {
        LOG.trace("{@method} step = {}", step.getName());

        Environment environment = setupInputPorts(step);
        environment = environment.setPrimaryInputPortAsDefaultReadablePort(step);
        environment = environment.setXPathContextPort(step);
        environment = environment.setDefaultParametersPort(step);
        if (evaluteVariables) {
            environment = environment.setupVariables(step);
        }
        environment = environment.setDefaultParametersPort(step);

        return environment;
    }

    private Environment setupInputPorts(final Step step) {
        LOG.trace("{@method} step = {}", step.getName());

        final Map<PortReference, EnvironmentPort> newPorts = Maps.newHashMap();

        for (final Port port : step.getInputPorts()) {
            EnvironmentPort environmentPort = EnvironmentPort.newEnvironmentPort(port, this);
            if (port.getPortName().equals(XProcPorts.XPATH_CONTEXT) && port.getPortBindings().isEmpty()
                    && getXPathContextPort() != null) {
                LOG.trace("  {} is XPath context port", environmentPort);
                environmentPort = environmentPort.pipe(getXPathContextPort());
            }
            if (port.isParameter() && defaultParametersPort != null) {
                environmentPort = environmentPort.pipe(defaultParametersPort);
            }
            newPorts.put(port.getPortReference(), environmentPort);
        }

        for (final Port port : step.getOutputPorts()) {
            if (port.getPortBindings().isEmpty()) {
                newPorts.put(port.getPortReference(), EnvironmentPort.newEnvironmentPort(port, this));
            }
        }

        return addPorts(newPorts);
    }

    public Environment setupOutputPorts(final Step step) {
        LOG.trace("{@method} step = {}", step.getName());

        return setupOutputPorts(step, this);
    }

    public Environment setupOutputPorts(final Step step, final Environment sourceEnvironment) {
        LOG.trace("{@method} step = {}", step.getName());

        final Map<PortReference, EnvironmentPort> newPorts = Maps.newHashMap();

        for (final Port port : step.getOutputPorts()) {
            if (!ports.containsKey(port.getPortReference())) {
                newPorts.put(port.getPortReference(), EnvironmentPort.newEnvironmentPort(port, sourceEnvironment));
            }
        }

        Environment result = addPorts(newPorts);
        result = result.setPrimaryOutputPortAsDefaultReadablePort(step, sourceEnvironment);
        result = result.setDefaultReadablePortAsXPathContextPort();
        return result;
    }

    public Environment setDefaultReadablePortAsXPathContextPort() {
        final EnvironmentPort port = getDefaultReadablePort();
        LOG.trace("{@method} port = {}", port);
        return setXPathContextPort(port);
    }

    private Environment setPrimaryInputPortAsDefaultReadablePort(final Step step) {
        LOG.trace("{@method} step = {} ; type = {}", step.getName(), step.getType());

        final Port primaryInputPort = step.getPrimaryInputPort();
        LOG.trace("primaryInputPort = {}", primaryInputPort);

        if (primaryInputPort == null) {
            return this;
        }

        LOG.trace("new default readable port = {}", primaryInputPort);

        // if port is empty then pipe to existing default readable port
        final EnvironmentPort environmentPort = getEnvironmentPort(primaryInputPort);
        final EnvironmentPort nonEmptyEnvironmentPort;
        if (Iterables.isEmpty(environmentPort.portBindings()) && getDefaultReadablePort() != null) {
            nonEmptyEnvironmentPort = environmentPort.pipe(getDefaultReadablePort());
        } else {
            nonEmptyEnvironmentPort = environmentPort;
        }

        return addPorts(nonEmptyEnvironmentPort).setDefaultReadablePort(nonEmptyEnvironmentPort);
    }

    public Environment setPrimaryOutputPortAsDefaultReadablePort(final Step step,
            final Environment sourceEnvironment) {
        LOG.trace("{@method} step = {}", step.getName());

        final Port primaryOutputPort = step.getPrimaryOutputPort();

        if (primaryOutputPort == null) {
            return this;
        }

        LOG.trace("new default readable port = {}", primaryOutputPort);

        final EnvironmentPort environmentPort = getEnvironmentPort(primaryOutputPort);
        final EnvironmentPort nonEmptyEnvironmentPort;
        if (Iterables.isEmpty(environmentPort.portBindings())) {
            nonEmptyEnvironmentPort = environmentPort.pipe(sourceEnvironment.getDefaultReadablePort());
        } else {
            nonEmptyEnvironmentPort = environmentPort;
        }

        return addPorts(nonEmptyEnvironmentPort).setDefaultReadablePort(nonEmptyEnvironmentPort);
    }

    private Environment setXPathContextPort(final Step step) {
        LOG.trace("{@method} step = {}", step.getName());

        final Port xpathContextPort = step.getXPathContextPort();
        if (xpathContextPort == null) {
            return this;
        }

        return setXPathContextPort(getEnvironmentPort(xpathContextPort));
    }

    public Environment setupVariables(final Step step) {
        LOG.trace("{@method} step = {}", step.getName());
        final Iterable<Variable> allDeclaredVariables = Iterables.concat(step.getVariables().values(),
                step.getParameters().values());
        LOG.trace("  variables = {}", allDeclaredVariables);

        final Map<QName, String> allVariables = Maps.newHashMap(inheritedVariables);
        allVariables.putAll(localVariables);

        final Map<QName, String> newLocalVariables = Maps.newHashMap(localVariables);
        final List<XdmNode> newParameterNodes = Lists.newArrayListWithCapacity(step.getParameters().size());

        for (final Variable variable : allDeclaredVariables) {
            LOG.trace("variable = {}", variable);
            final String value;
            if (variable.getValue() != null) {
                value = variable.getValue();
            } else if (variable.getSelect() == null) {
                if (variable.isRequired()) {
                    throw XProcExceptions.xs0018(variable);
                }

                value = null;
            } else {
                final PortBinding xpathPortBinding = variable.getPortBinding();
                final XdmNode xpathContextNode;
                if (xpathPortBinding != null) {
                    try {
                        xpathContextNode = Iterables
                                .getOnlyElement(xpathPortBinding.newEnvironmentPortBinding(this).readNodes(), null);
                    } catch (final NoSuchElementException e) {
                        // TODO XProc error?
                        throw new IllegalStateException("error while evaluating " + variable.getName(), e);
                    }
                } else {
                    xpathContextNode = getXPathContextNode(variable);
                }

                final XdmValue result = evaluateXPath(variable.getSelect(), getPipelineContext().getProcessor(),
                        xpathContextNode, variable.getNode(), allVariables, variable.getLocation());
                final XdmItem resultNode = Iterables.getOnlyElement(result);

                value = resultNode.getStringValue();
            }

            LOG.trace("{} = {}", variable.getName(), value);

            if (value != null) {
                if (variable.isParameter()) {
                    if (getDefaultParametersPort() == null) {
                        throw XProcExceptions.xs0034(variable.getLocation(), step, variable.getName());
                    }
                    final XdmNode parameterNode = newParameterElement(variable.getName(), value);
                    newParameterNodes.add(parameterNode);
                } else {
                    allVariables.put(variable.getName(), value);
                    newLocalVariables.put(variable.getName(), value);
                }
            }
        }

        final EnvironmentPort parametersPort = getDefaultParametersPort();
        final Environment resultEnvironment;
        if (newParameterNodes.isEmpty()) {
            resultEnvironment = this;
        } else {
            assert parametersPort != null : step;
            final EnvironmentPort newParametersPort = parametersPort.writeNodes(newParameterNodes, true);
            resultEnvironment = addPorts(newParametersPort);
        }

        return resultEnvironment.setLocalVariables(newLocalVariables);
    }

    private static XdmValue evaluateXPath(final String select, final Processor processor,
            final XdmNode xpathContextNode, final XdmNode namespaceContextNode, final Map<QName, String> variables,
            final Location location) {
        LOG.trace("{@method} select = {} ; variables = {}", select, variables);

        try {
            final XPathCompiler xpathCompiler = processor.newXPathCompiler();
            xpathCompiler.setSchemaAware(true);
            for (final Map.Entry<QName, String> variableEntry : variables.entrySet()) {
                if (variableEntry.getValue() != null) {
                    xpathCompiler.declareVariable(variableEntry.getKey());
                }
            }

            for (final Entry<String, String> namespace : SaxonNamespaces.namespaceSequence(namespaceContextNode)) {
                xpathCompiler.declareNamespace(namespace.getKey(), namespace.getValue());
            }

            setCurrentNamespaceContext(namespaceContextNode);

            final XPathSelector selector = xpathCompiler.compile(select).load();
            if (xpathContextNode != null) {
                LOG.trace("xpathContextNode = {}", xpathContextNode);
                selector.setContextItem(processor.newDocumentBuilder().build(xpathContextNode.asSource()));
                setCurrentXPathContext(xpathContextNode);
            }

            for (final Map.Entry<QName, String> variableEntry : variables.entrySet()) {
                if (variableEntry.getValue() != null) {
                    LOG.trace("  {} = {}", variableEntry.getKey(), variableEntry.getValue());
                    selector.setVariable(variableEntry.getKey(), new XdmAtomicValue(variableEntry.getValue()));
                }
            }

            final XdmValue result = selector.evaluate();
            LOG.trace("result = {}", result);
            return result;
        } catch (final SaxonApiException e) {
            final XProcException xprocException = XProcExceptions.xd0023(location, select, e.getMessage());
            xprocException.initCause(e);
            throw xprocException;
        }
    }

    public Environment newFollowingStepEnvironment(final Step step) {
        return newFollowingStepEnvironment(step, true);
    }

    public Environment newFollowingStepEnvironment(final Step step, final boolean evaluateVariables) {
        LOG.trace("{@method} step = {}", step.getName());

        return newFollowingStepEnvironment().setupStepEnvironment(step, evaluateVariables).setupStepAlias(step);
    }

    public Environment newFollowingStepEnvironment() {
        return new Environment(pipeline, configuration, ports, defaultReadablePort, defaultParametersPort,
                xpathContextPort, inheritedVariables, localVariables);
    }

    private Environment setupStepAlias(final Step step) {
        Environment environment = this;
        final String internalStepName = step.getInternalName();
        if (internalStepName != null) {
            LOG.trace("step alias {} -> {}", step.getName(), internalStepName);
            for (final Port port : step.getInputPorts()) {
                final Port internalPort = port.setStepName(internalStepName).pipe(port);
                LOG.trace("{} -> {}", internalPort, port);
                final EnvironmentPort environmentPort = EnvironmentPort.newEnvironmentPort(internalPort,
                        environment);
                environment = environment.addPorts(environmentPort);
            }
        }
        return environment;
    }

    public Environment newChildStepEnvironment() {
        final Map<QName, String> variables = ImmutableMap.of();
        return new Environment(pipeline, configuration, ports, defaultReadablePort, defaultParametersPort,
                xpathContextPort, TcMaps.merge(inheritedVariables, localVariables), variables);
    }

    public Environment setLocalVariables(final Map<QName, String> localVariables) {
        assert localVariables != null;

        return new Environment(pipeline, configuration, ports, defaultReadablePort, defaultParametersPort,
                xpathContextPort, inheritedVariables, TcMaps.merge(this.localVariables, localVariables));
    }

    public void setLocalVariable(final QName name, final String value) {
        localVariables.put(name, value);
    }

    public EnvironmentPort getDefaultReadablePort() {
        return defaultReadablePort;
    }

    public EnvironmentPort getXPathContextPort() {
        return xpathContextPort;
    }

    public Environment setXPathContextPort(final EnvironmentPort xpathContextPort) {
        LOG.trace("{@method} port = {}", xpathContextPort);
        assert xpathContextPort == null || ports.containsValue(xpathContextPort);

        return new Environment(pipeline, configuration, ports, defaultReadablePort, defaultParametersPort,
                xpathContextPort, inheritedVariables, localVariables);
    }

    public PipelineContext getPipelineContext() {
        return configuration;
    }

    public String getVariable(final QName name, final String defaultValue) {
        assert name != null;
        LOG.trace("{@method} name = {} ; defaultValue = {}", name, defaultValue);

        final String value = getVariable(name);
        if (value != null) {
            return value;
        }

        return defaultValue;
    }

    public String getVariable(final QName name) {
        assert name != null;
        LOG.trace("{@method} name = {}", name);

        final String localValue = localVariables.get(name);
        if (localValue != null) {
            return localValue;
        }

        return inheritedVariables.get(name);
    }

    public Map<QName, String> getLocalVariables() {
        return localVariables;
    }

    public Map<QName, String> getInheritedVariables() {
        return inheritedVariables;
    }

    public Environment setDefaultReadablePort(final EnvironmentPort defaultReadablePort) {
        assert defaultReadablePort == null
                || ports.containsValue(defaultReadablePort) : defaultReadablePort.getPortReference() + " ; "
                        + ports.keySet();
        LOG.trace("{@method} defaultReadablePort = {}", defaultReadablePort);

        return new Environment(pipeline, configuration, ports, defaultReadablePort, defaultParametersPort,
                xpathContextPort, inheritedVariables, localVariables);
    }

    public Environment setDefaultReadablePort(final PortReference portReference) {
        return setDefaultReadablePort(getPort(portReference));
    }

    public Map<PortReference, EnvironmentPort> getPorts() {
        return ports;
    }

    public EnvironmentPort getEnvironmentPort(final Port port) {
        return getEnvironmentPort(port.getPortReference());
    }

    public EnvironmentPort getEnvironmentPort(final PortReference portReference) {
        final EnvironmentPort port = ports.get(portReference);
        Preconditions.checkArgument(port != null, "no such port: %s\navailable ports: %s", portReference,
                ports.keySet());
        return port;
    }

    public Environment addPorts(final EnvironmentPort... ports) {
        return addPorts(ImmutableList.copyOf(ports));
    }

    public Environment addPorts(final Iterable<EnvironmentPort> ports) {
        assert ports != null;
        LOG.trace("{@method} ports = {}", ports);

        final Map<PortReference, EnvironmentPort> newPorts = Maps.newHashMap(this.ports);
        newPorts.putAll(getPortsMap(ports));

        return new Environment(pipeline, configuration, newPorts, defaultReadablePort, defaultParametersPort,
                xpathContextPort, inheritedVariables, localVariables);
    }

    public Environment addPorts(final Map<PortReference, EnvironmentPort> ports) {
        assert ports != null;
        LOG.trace("{@method} ports = {}", ports);

        return new Environment(pipeline, configuration, TcMaps.merge(this.ports, ports), defaultReadablePort,
                defaultParametersPort, xpathContextPort, inheritedVariables, localVariables);
    }

    public Step getPipeline() {
        return pipeline;
    }

    public URI getBaseUri() {
        return URI.create(pipeline.getLocation().getSystemId());
    }

    public EnvironmentPort getDefaultParametersPort() {
        return defaultParametersPort;
    }

    private Environment setDefaultParametersPort(final Step step) {
        LOG.trace("{@method} step = {}", step);
        final Port port = step.getPrimaryParameterPort();
        LOG.trace("  port = {}", port);
        if (port != null) {
            final EnvironmentPort environmentPort = getEnvironmentPort(port);
            assert environmentPort != null;
            return setDefaultParametersPort(environmentPort);
        }

        return this;
    }

    public Environment setDefaultParametersPort(final EnvironmentPort defaultParametersPort) {
        assert defaultParametersPort == null || ports.containsValue(defaultParametersPort) : String
                .format("port = %s ; defaultParametersPort = %s", ports, defaultParametersPort);
        LOG.trace("{@method} defaultParametersPort = {}", defaultParametersPort);

        return new Environment(pipeline, configuration, ports, defaultReadablePort, defaultParametersPort,
                xpathContextPort, inheritedVariables, localVariables);
    }

    @ReturnsNullable
    public XdmNode getXPathContextNode() {
        LOG.trace("{@method}");

        // TODO cache

        final EnvironmentPort xpathContextPort = getXPathContextPort();
        LOG.trace("  xpathContextPort = {}", xpathContextPort);
        if (xpathContextPort != null) {
            final Iterator<XdmNode> contextNodes = xpathContextPort.readNodes().iterator();
            if (contextNodes.hasNext()) {
                final XdmNode contextNode = contextNodes.next();
                if (xpathContextPort.getDeclaredPort().getPortName().equals(XProcPorts.XPATH_CONTEXT)) {
                    // TODO XProc error
                    assert !contextNodes.hasNext() : xpathContextPort.readNodes();
                }

                return Saxon.asDocumentNode(contextNode, configuration.getProcessor());
            }
        }

        return Saxon.getEmptyDocument(configuration.getProcessor());
    }

    @ReturnsNullable
    public XdmNode getXPathContextNode(final Variable variable) {
        final EnvironmentPort xpathContextPort = getXPathContextPort();
        if (xpathContextPort != null) {
            final Iterable<XdmNode> nodes = xpathContextPort.readNodes();
            if (Iterables.size(nodes) > 1 && variable.isVariable()) {
                throw XProcExceptions.xd0008(SaxonLocation.of(variable.getNode()));
            } else if (!Iterables.isEmpty(nodes)) {
                return Iterables.getFirst(nodes, null);
            }
        }
        return Saxon.getEmptyDocument(configuration.getProcessor());
    }

    public XdmValue evaluateXPath(final String select) {
        assert select != null;
        LOG.trace("{@method} select = {}", select);

        final XdmNode xpathContextNode = getXPathContextNode();
        assert xpathContextNode != null;
        LOG.trace("xpathContextNode = {}", xpathContextNode);

        return evaluateXPath(select, xpathContextNode);
    }

    public XdmValue evaluateXPath(final String select, final XdmNode xpathContextNode) {
        return evaluateXPath(select, xpathContextNode, null);
    }

    public XdmValue evaluateXPath(final String select, final XdmNode xpathContextNode,
            final Map<QName, String> additionalParameters) {
        assert select != null;
        LOG.trace("{@method} select = {}", select);

        // TODO slow
        final Map<QName, String> temporaryVariables = TcMaps.merge(inheritedVariables, localVariables);
        final Map<QName, String> variables;

        if (additionalParameters != null) {
            variables = TcMaps.merge(temporaryVariables, additionalParameters);
        } else {
            variables = temporaryVariables;
        }

        try {
            final XPathCompiler xpathCompiler = configuration.getProcessor().newXPathCompiler();
            xpathCompiler.setSchemaAware(true);
            final String pipelineSystemId = getPipeline().getLocation().getSystemId();
            if (!Strings.isNullOrEmpty(pipelineSystemId)) {
                xpathCompiler.setBaseURI(URI.create(pipelineSystemId));
            }
            for (final Map.Entry<QName, String> variableEntry : variables.entrySet()) {
                if (variableEntry.getValue() != null) {
                    xpathCompiler.declareVariable(variableEntry.getKey());
                }
            }

            xpathCompiler.declareNamespace(XProcXmlModel.xprocNamespace().prefix(),
                    XProcXmlModel.xprocNamespace().uri());
            xpathCompiler.declareNamespace(XProcXmlModel.xprocStepNamespace().prefix(),
                    XProcXmlModel.xprocStepNamespace().uri());

            final XPathSelector selector = xpathCompiler.compile(select).load();
            setCurrentXPathContext(xpathContextNode);
            if (xpathContextNode != null) {
                selector.setContextItem(xpathContextNode);
            }

            for (final Map.Entry<QName, String> variableEntry : variables.entrySet()) {
                if (variableEntry.getValue() != null) {
                    selector.setVariable(variableEntry.getKey(),
                            Saxon.getUntypedXdmItem(variableEntry.getValue(), configuration.getProcessor()));
                }
            }

            return selector.evaluate();
        } catch (final Exception e) {
            final String exceptionMessage = e.getMessage();

            if (exceptionMessage.contains("context item is undefined")) {
                // Keep the original exception message to allow correct
                // exception handling
                throw new IllegalStateException(exceptionMessage, e);
            } else {
                throw new IllegalStateException("error while evaluating XPath query: " + select, e);
            }
        }
    }

    private EnvironmentPort getPort(final PortReference portReference) {
        assert ports.containsKey(portReference) : "port = " + portReference + " ; ports = " + ports.keySet();
        return ports.get(portReference);
    }

    public Environment writeNodes(final PortReference portReference, final XdmNode... nodes) {
        return writeNodes(portReference, ImmutableList.copyOf(nodes));
    }

    public Environment writeNodes(final PortReference portReference, final Iterable<XdmNode> nodes) {
        LOG.trace("{@method} port = {}", portReference);

        return addPorts(getPort(portReference).writeNodes(nodes));
    }

    public Iterable<XdmNode> readNodes(final PortReference portReference) {
        LOG.trace("{@method} port = {}", portReference);
        final Iterable<XdmNode> nodes = getPort(portReference).readNodes();
        LOG.trace("nodes = {}", nodes);
        return nodes;
    }

    public XdmNode readNode(final PortReference portReference) {
        return Iterables.getOnlyElement(readNodes(portReference));
    }

    public Map<QName, String> readParameters(final PortReference portReference) {
        LOG.trace("{@method} portReference = {}", portReference);

        final Builder<QName, String> parameters = ImmutableMap.builder();
        for (final XdmNode parameterNode : readNodes(portReference)) {
            final XPathCompiler xpathCompiler = getPipelineContext().getProcessor().newXPathCompiler();
            xpathCompiler.setSchemaAware(true);
            try {
                final XPathSelector paramsSelector = xpathCompiler.compile("//.[@name]").load();
                paramsSelector.setContextItem(parameterNode);
                for (XdmItem aParamsSelector : paramsSelector) {
                    final XdmNode item = (XdmNode) aParamsSelector;
                    final Iterable<XdmNode> attributes = SaxonAxis.attributes(item);
                    for (XdmNode attribute : attributes) {
                        final QName attName = attribute.getNodeName();
                        if (!ATTRIBUTE_NAME.equals(attName) && !ATTRIBUTE_NAMESPACE.equals(attName)
                                && !ATTRIBUTE_VALUE.equals(attName)) {
                            throw XProcExceptions.xd0014(item);
                        }
                    }
                    final String name = item.getAttributeValue(ATTRIBUTE_NAME);
                    final String namespace = item.getAttributeValue(ATTRIBUTE_NAMESPACE);
                    if (!StringUtils.isEmpty(namespace) && name.contains(":")) {
                        final QName aNode = new QName(StringUtils.substringAfter(name, ":"), item);
                        if (!namespace.equals(aNode.getNamespaceURI())) {
                            throw XProcExceptions.xd0025(SaxonLocation.of(item));
                        }
                    }
                    final String value = item.getAttributeValue(ATTRIBUTE_VALUE);
                    // TODO name should be real QName
                    parameters.put(new QName(namespace, name), value);
                }
            } catch (final SaxonApiException e) {
                throw new PipelineException(e);
            }
        }

        return parameters.build();
    }

    public XdmNode newParameterElement(final QName name, final String value) {
        Preconditions.checkNotNull(name);
        Preconditions.checkNotNull(value);

        final SaxonBuilder builder = new SaxonBuilder(configuration.getProcessor().getUnderlyingConfiguration());
        builder.startDocument();
        builder.startElement(ELEMENT_PARAM);
        builder.namespace(XProcXmlModel.xprocStepNamespace().prefix(), XProcXmlModel.xprocStepNamespace().uri());
        builder.attribute(ATTRIBUTE_NAME, name.toString());
        builder.attribute(ATTRIBUTE_VALUE, value);
        builder.text(value);
        builder.endElement();
        builder.endDocument();

        return builder.getNode();
    }

    public XdmNode newResultElement(final String value) {
        Preconditions.checkNotNull(value);

        final SaxonBuilder builder = new SaxonBuilder(configuration.getProcessor().getUnderlyingConfiguration());
        builder.startDocument();
        builder.startElement(ELEMENT_RESULT);
        builder.namespace(XProcXmlModel.xprocStepNamespace().prefix(), XProcXmlModel.xprocStepNamespace().uri());
        builder.text(value);
        builder.endElement();
        builder.endDocument();

        return builder.getNode();
    }

    public Iterable<EnvironmentPort> getOutputPorts() {
        return Iterables.filter(ports.values(), port -> port.getDeclaredPort().isOutput());
    }
}