org.orbeon.oxf.processor.PageFlowControllerBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.orbeon.oxf.processor.PageFlowControllerBuilder.java

Source

/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * This program 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 program 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.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.processor;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.QName;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.processor.pipeline.PipelineConfig;
import org.orbeon.oxf.processor.pipeline.PipelineProcessor;
import org.orbeon.oxf.processor.pipeline.ast.*;
import org.orbeon.oxf.resources.URLFactory;
import org.orbeon.oxf.xml.NamespaceMapping;
import org.orbeon.oxf.xml.XMLConstants;
import org.orbeon.oxf.xml.dom4j.*;

import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// PageFlowControllerProcessor code that hasn't been migrated to Scala yet
public class PageFlowControllerBuilder {

    public final static NamespaceMapping NAMESPACES_WITH_XSI_AND_XSLT;

    // External resources
    private final static String REVERSE_SETVALUES_XSL = "oxf:/ops/pfc/setvalue-reverse.xsl";
    private final static String REWRITE_XSL = "oxf:/ops/pfc/rewrite.xsl";
    private final static String XFORMS_XML_SUBMISSION_XPL = "oxf:/ops/pfc/xforms-xml-submission.xpl";

    // Instance passing configuration
    public final static String INSTANCE_PASSING_REDIRECT = "redirect";
    public final static String INSTANCE_PASSING_FORWARD = "forward";
    public final static String INSTANCE_PASSING_REDIRECT_PORTAL = "redirect-exit-portal";

    static {
        final Map<String, String> mapping = new HashMap<String, String>();
        mapping.put(XMLConstants.XSI_PREFIX, XMLConstants.XSI_URI);
        mapping.put(XMLConstants.XSLT_PREFIX, XMLConstants.XSLT_NAMESPACE_URI);

        NAMESPACES_WITH_XSI_AND_XSLT = new NamespaceMapping(mapping);
    }

    public static void handleEpilogue(final String controllerContext, List<ASTStatement> statements,
            final String epilogueURL, final Element epilogueElement, final ASTOutput epilogueData,
            final ASTOutput epilogueModelData, final ASTOutput epilogueInstance) {
        if (epilogueURL == null) {
            // Run HTML serializer if no epilogue is specified
            statements.add(new ASTChoose(new ASTHrefId(epilogueData)) {
                {
                    addWhen(new ASTWhen("not(/*/@xsi:nil = 'true')") {
                        {
                            setNamespaces(NAMESPACES_WITH_XSI_AND_XSLT);
                            // The epilogue did not do the serialization
                            addStatement(new ASTProcessorCall(XMLConstants.HTML_SERIALIZER_PROCESSOR_QNAME) {
                                {
                                    Document config = new NonLazyUserDataDocument(
                                            new NonLazyUserDataElement("config"));
                                    Element rootElement = config.getRootElement();
                                    rootElement.addElement("version").addText("5.0");
                                    rootElement.addElement("encoding").addText("utf-8");
                                    addInput(new ASTInput("config", config));
                                    addInput(new ASTInput("data", new ASTHrefId(epilogueData)));
                                    //setLocationData(TODO);
                                }
                            });
                        }
                    });
                    addWhen(new ASTWhen());
                }
            });
        } else {
            // Send result through epilogue
            statements.add(new ASTProcessorCall(XMLConstants.PIPELINE_PROCESSOR_QNAME) {
                {
                    final String url;
                    try {
                        url = URLFactory.createURL(controllerContext, epilogueURL).toExternalForm();
                    } catch (MalformedURLException e) {
                        throw new OXFException(e);
                    }
                    addInput(new ASTInput("config", new ASTHrefURL(url)));
                    addInput(new ASTInput("data", new ASTHrefId(epilogueData)));
                    addInput(new ASTInput("model-data", new ASTHrefId(epilogueModelData)));
                    addInput(new ASTInput("instance", new ASTHrefId(epilogueInstance)));
                    final String[] locationParams = new String[] { "pipeline", epilogueURL };
                    setLocationData(new ExtendedLocationData((LocationData) epilogueElement.getData(),
                            "executing epilogue", epilogueElement, locationParams, true));
                }
            });
        }
    }

    public static Document getSetValuesDocument(final Element pageElement) {
        final List setValueElements = pageElement.elements("setvalue");
        final Document setvaluesDocument;
        if (!setValueElements.isEmpty()) {
            // Create document with setvalues
            setvaluesDocument = new NonLazyUserDataDocument(new NonLazyUserDataElement("params"));
            // New <setvalue> elements
            if (!setValueElements.isEmpty()) {
                for (Object setValueElement1 : setValueElements) {
                    final Element setValueElement = (Element) setValueElement1;
                    setvaluesDocument.getRootElement().add((Element) setValueElement.clone());
                }
            }
        } else {
            setvaluesDocument = null;
        }
        return setvaluesDocument;
    }

    /**
     * Handle <page>
     */
    public static void handlePage(final StepProcessorContext stepProcessorContext, final String urlBase,
            List<ASTStatement> statementsList, final Element pageElement, final String matcherOutputOrParamId,
            final ASTOutput viewData, final ASTOutput epilogueModelData, final ASTOutput viewInstance,
            final Map<String, String> pageIdToPathInfo, final Map<String, Document> pageIdToSetvaluesDocument,
            final String instancePassing) {

        // Get page attributes
        final String modelAttribute = pageElement.attributeValue("model");
        final String viewAttribute = pageElement.attributeValue("view");
        final String defaultSubmissionAttribute = pageElement.attributeValue("default-submission");

        // Get setvalues document
        final Document setvaluesDocument = getSetValuesDocument(pageElement);

        // Get actions
        final List actionElements = pageElement.elements("action");

        // Handle initial instance
        final ASTOutput defaultSubmission = new ASTOutput("data", "default-submission");
        if (defaultSubmissionAttribute != null) {
            statementsList.add(new ASTProcessorCall(XMLConstants.URL_GENERATOR_PROCESSOR_QNAME) {
                {
                    final String url;
                    try {
                        url = URLFactory.createURL(urlBase, defaultSubmissionAttribute).toExternalForm();
                    } catch (MalformedURLException e) {
                        throw new OXFException(e);
                    }
                    final Document configDocument = new NonLazyUserDataDocument(
                            new NonLazyUserDataElement("config"));
                    configDocument.getRootElement().addText(url);

                    addInput(new ASTInput("config", configDocument));
                    addOutput(defaultSubmission);
                }
            });
        }

        // XForms Input
        final ASTOutput isRedirect = new ASTOutput(null, "is-redirect");

        // Always hook up XML submission
        final ASTOutput xformedInstance = new ASTOutput("instance", "xformed-instance");
        {
            final LocationData locDat = Dom4jUtils.getLocationData();
            xformedInstance.setLocationData(locDat);
        }

        // Use XML Submission pipeline
        statementsList.add(new ASTProcessorCall(XMLConstants.PIPELINE_PROCESSOR_QNAME) {
            {
                addInput(new ASTInput("config", new ASTHrefURL(XFORMS_XML_SUBMISSION_XPL)));
                if (setvaluesDocument != null) {
                    addInput(new ASTInput("setvalues", setvaluesDocument));
                    addInput(new ASTInput("matcher-result", new ASTHrefId(matcherOutputOrParamId)));
                } else {
                    addInput(new ASTInput("setvalues", Dom4jUtils.NULL_DOCUMENT));
                    addInput(new ASTInput("matcher-result", Dom4jUtils.NULL_DOCUMENT));
                }
                if (defaultSubmissionAttribute != null) {
                    addInput(new ASTInput("default-submission", new ASTHrefId(defaultSubmission)));
                } else {
                    addInput(new ASTInput("default-submission", Dom4jUtils.NULL_DOCUMENT));
                }
                addOutput(xformedInstance);
            }
        });

        // Make sure the xformed-instance id is used for p:choose
        statementsList.add(new ASTProcessorCall(XMLConstants.NULL_PROCESSOR_QNAME) {
            {
                addInput(new ASTInput("data", new ASTHrefId(xformedInstance)));
            }
        });

        // Execute actions
        final ASTOutput xupdatedInstance = new ASTOutput(null, "xupdated-instance");
        final ASTOutput actionData = new ASTOutput(null, "action-data");
        final int[] actionNumber = new int[] { 0 };
        final boolean[] foundActionWithoutWhen = new boolean[] { false };
        final ASTChoose actionsChoose = new ASTChoose(new ASTHrefId(xformedInstance)) {
            {

                // Always add a branch to test on whether the XML submission asked to bypass actions, model, view, and epilogue
                // Use of this <bypass> document is arguably a HACK 
                addWhen(new ASTWhen() {
                    {

                        setTest("/bypass[@xsi:nil = 'true']");
                        setNamespaces(NAMESPACES_WITH_XSI_AND_XSLT);

                        addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                addOutput(new ASTOutput("data", xupdatedInstance));
                            }
                        });
                        addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                            {
                                final Document config = new NonLazyUserDataDocument(
                                        new NonLazyUserDataElement("is-redirect"));
                                config.getRootElement().addText("true");
                                addInput(new ASTInput("data", config));
                                addOutput(new ASTOutput("data", isRedirect));
                            }
                        });
                        addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                addOutput(new ASTOutput("data", actionData));
                            }
                        });
                    }
                });

                for (Object actionElement1 : actionElements) {

                    // Get info about action
                    actionNumber[0]++;
                    final Element actionElement = (Element) actionElement1;
                    final String whenAttribute;
                    {
                        // NOTE: Prior to 2012-06-27, the XSD schema would put a default value of true()
                        final String tempWhen = actionElement.attributeValue("when");
                        if (tempWhen == null)
                            whenAttribute = "true()";
                        else
                            whenAttribute = tempWhen;
                    }
                    ;
                    final String actionAttribute = actionElement.attributeValue("action");

                    // Execute action
                    addWhen(new ASTWhen() {
                        {

                            // Add condition, remember that we found an <action> without a when
                            if (whenAttribute != null) {
                                if (foundActionWithoutWhen[0])
                                    throw new ValidationException("Unreachable <action>",
                                            (LocationData) actionElement.getData());
                                setTest(whenAttribute);
                                setNamespaces(new NamespaceMapping(
                                        Dom4jUtils.getNamespaceContextNoDefault(actionElement)));
                                setLocationData((LocationData) actionElement.getData());
                            } else {
                                foundActionWithoutWhen[0] = true;
                            }

                            final boolean resultTestsOnActionData =
                                    // Must have an action, in the first place
                                    actionAttribute != null &&
                            // More than one <result>: so at least the first one must have a "when"
                            actionElement.elements("result").size() > 1;

                            final ASTOutput internalActionData = actionAttribute == null ? null
                                    : new ASTOutput(null, "internal-action-data-" + actionNumber[0]);
                            if (actionAttribute != null) {
                                // TODO: handle passing and modifications of action data in model, and view, and pass to instance
                                addStatement(new StepProcessorCall(stepProcessorContext, urlBase, actionAttribute,
                                        "action") {
                                    {
                                        addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                        addInput(new ASTInput("instance", new ASTHrefId(xformedInstance)));
                                        addInput(new ASTInput("xforms-model", Dom4jUtils.NULL_DOCUMENT));
                                        addInput(new ASTInput("matcher", new ASTHrefId(matcherOutputOrParamId)));
                                        final ASTOutput dataOutput = new ASTOutput("data", internalActionData);
                                        final String[] locationParams = new String[] { "pipeline", actionAttribute,
                                                "page id", pageElement.attributeValue("id"), "when",
                                                whenAttribute };
                                        dataOutput.setLocationData(new ExtendedLocationData(
                                                (LocationData) actionElement.getData(),
                                                "reading action data output", pageElement, locationParams, true));
                                        addOutput(dataOutput);
                                        setLocationData(
                                                new ExtendedLocationData((LocationData) actionElement.getData(),
                                                        "executing action", pageElement, locationParams, true));
                                    }
                                });

                                // Force execution of action if no <result> is reading it
                                if (!resultTestsOnActionData) {
                                    addStatement(
                                            new ASTProcessorCall(XMLConstants.NULL_SERIALIZER_PROCESSOR_QNAME) {
                                                {
                                                    addInput(new ASTInput("data",
                                                            new ASTHrefId(internalActionData)));
                                                }
                                            });
                                }

                                // Export internal-action-data as action-data
                                addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                    {
                                        addInput(new ASTInput("data", new ASTHrefId(internalActionData)));
                                        addOutput(new ASTOutput("data", actionData));
                                    }
                                });
                            } else {
                                addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                    {
                                        addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                        addOutput(new ASTOutput("data", actionData));
                                    }
                                });
                            }

                            if (actionElement.elements("result").size() > 0 && internalActionData != null) {
                                // At least one result testing on action

                                // Test based on action
                                addStatement(new ASTChoose(new ASTHrefId(internalActionData)) {
                                    {
                                        for (Object o : actionElement.elements("result")) {
                                            final Element resultElement = (Element) o;
                                            final String resultWhenAttribute;
                                            {
                                                // NOTE: Prior to 2012-06-27, the XSD schema would put a default value of true()
                                                final String tempWhen = resultElement.attributeValue("when");
                                                if (tempWhen == null)
                                                    resultWhenAttribute = "true()";
                                                else
                                                    resultWhenAttribute = tempWhen;
                                            }
                                            ;

                                            // Execute result
                                            addWhen(new ASTWhen() {
                                                {
                                                    if (resultWhenAttribute != null) {
                                                        setTest(resultWhenAttribute);
                                                        setNamespaces(new NamespaceMapping(Dom4jUtils
                                                                .getNamespaceContextNoDefault(resultElement)));
                                                        final String[] locationParams = new String[] { "page id",
                                                                pageElement.attributeValue("id"), "when",
                                                                resultWhenAttribute };
                                                        setLocationData(new ExtendedLocationData(
                                                                (LocationData) resultElement.getData(),
                                                                "executing result", resultElement, locationParams,
                                                                true));
                                                    }
                                                    executeResult(this, pageIdToPathInfo, pageIdToSetvaluesDocument,
                                                            xformedInstance, resultElement, internalActionData,
                                                            isRedirect, xupdatedInstance, instancePassing);
                                                }
                                            });
                                        }

                                        // Continue when all results fail
                                        addWhen(new ASTWhen() {
                                            {
                                                addStatement(new ASTProcessorCall(
                                                        XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                                    {
                                                        addInput(new ASTInput("data", new NonLazyUserDataDocument(
                                                                new NonLazyUserDataElement("is-redirect") {
                                                                    {
                                                                        setText("false");
                                                                    }
                                                                })));
                                                        addOutput(new ASTOutput("data", isRedirect));
                                                    }
                                                });
                                                addStatement(new ASTProcessorCall(
                                                        XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                                    {
                                                        addInput(new ASTInput("data",
                                                                new ASTHrefId(xformedInstance)));
                                                        addOutput(new ASTOutput("data", xupdatedInstance));
                                                    }
                                                });
                                            }
                                        });
                                    }
                                });

                            } else {
                                // No result or result not depending on action
                                final Element resultElement = actionElement.element("result");
                                executeResult(this, pageIdToPathInfo, pageIdToSetvaluesDocument, xformedInstance,
                                        resultElement, internalActionData, isRedirect, xupdatedInstance,
                                        instancePassing);
                            }
                        }
                    });
                }

                if (!foundActionWithoutWhen[0]) {
                    // Defaul branch for when all actions fail
                    addWhen(new ASTWhen() {
                        {
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", new ASTHrefId(xformedInstance)));
                                    addOutput(new ASTOutput("data", xupdatedInstance));
                                }
                            });
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    final Document config = new NonLazyUserDataDocument(
                                            new NonLazyUserDataElement("is-redirect"));
                                    config.getRootElement().addText("false");
                                    addInput(new ASTInput("data", config));
                                    addOutput(new ASTOutput("data", isRedirect));
                                }
                            });
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                    addOutput(new ASTOutput("data", actionData));
                                }
                            });
                        }
                    });
                }
            }
        };

        // Add choose statement
        statementsList.add(actionsChoose);

        // Only continue if there was no redirect
        statementsList.add(new ASTChoose(new ASTHrefId(isRedirect)) {
            {
                addWhen(new ASTWhen("/is-redirect = 'false'") {
                    {

                        // Handle page model
                        final ASTOutput modelData = new ASTOutput(null, "model-data");
                        final ASTOutput modelInstance = new ASTOutput(null, "model-instance");
                        if (modelAttribute != null) {
                            // There is a model
                            addStatement(
                                    new StepProcessorCall(stepProcessorContext, urlBase, modelAttribute, "model") {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(actionData)));
                                            addInput(new ASTInput("instance", new ASTHrefId(xupdatedInstance)));
                                            addInput(new ASTInput("xforms-model", Dom4jUtils.NULL_DOCUMENT));
                                            addInput(
                                                    new ASTInput("matcher", new ASTHrefId(matcherOutputOrParamId)));
                                            final String[] locationParams = new String[] { "page id",
                                                    pageElement.attributeValue("id"), "model", modelAttribute };
                                            {
                                                final ASTOutput dataOutput = new ASTOutput("data", modelData);
                                                dataOutput.setLocationData(new ExtendedLocationData(
                                                        (LocationData) pageElement.getData(),
                                                        "reading page model data output", pageElement,
                                                        locationParams, true));
                                                addOutput(dataOutput);
                                            }
                                            {
                                                final ASTOutput instanceOutput = new ASTOutput("instance",
                                                        modelInstance);
                                                addOutput(instanceOutput);
                                                instanceOutput.setLocationData(new ExtendedLocationData(
                                                        (LocationData) pageElement.getData(),
                                                        "reading page model instance output", pageElement,
                                                        locationParams, true));
                                            }
                                            setLocationData(new ExtendedLocationData(
                                                    (LocationData) pageElement.getData(), "executing page model",
                                                    pageElement, locationParams, true));
                                        }
                                    });
                        } else if (viewAttribute != null) {
                            // There is no model but there is a view
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", new ASTHrefId(actionData)));
                                    addOutput(new ASTOutput("data", modelData));
                                }
                            });
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", new ASTHrefId(xupdatedInstance)));
                                    addOutput(new ASTOutput("data", modelInstance));
                                }
                            });
                        }

                        // Handle page view
                        if (viewAttribute != null) {
                            // There is a view
                            addStatement(
                                    new StepProcessorCall(stepProcessorContext, urlBase, viewAttribute, "view") {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(modelData)));
                                            addInput(new ASTInput("instance", new ASTHrefId(modelInstance)));
                                            addInput(new ASTInput("xforms-model", Dom4jUtils.NULL_DOCUMENT));
                                            addInput(
                                                    new ASTInput("matcher", new ASTHrefId(matcherOutputOrParamId)));
                                            final String[] locationParams = new String[] { "page id",
                                                    pageElement.attributeValue("id"), "view", viewAttribute };
                                            {
                                                final ASTOutput dataOutput = new ASTOutput("data", viewData);
                                                dataOutput.setLocationData(new ExtendedLocationData(
                                                        (LocationData) pageElement.getData(),
                                                        "reading page view data output", pageElement,
                                                        locationParams, true));
                                                addOutput(dataOutput);
                                            }
                                            {
                                                final ASTOutput instanceOutput = new ASTOutput("instance",
                                                        viewInstance);
                                                instanceOutput.setLocationData(new ExtendedLocationData(
                                                        (LocationData) pageElement.getData(),
                                                        "reading page view instance output", pageElement,
                                                        locationParams, true));
                                                addOutput(instanceOutput);
                                            }
                                            setLocationData(new ExtendedLocationData(
                                                    (LocationData) pageElement.getData(), "executing page view",
                                                    pageElement, locationParams, true));
                                        }
                                    });
                        } else {
                            // There is no view, send nothing to epilogue
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                    addOutput(new ASTOutput("data", viewData));
                                }
                            });
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                    addOutput(new ASTOutput("data", viewInstance));
                                }
                            });
                        }

                        if (modelAttribute != null && viewAttribute == null) {
                            // With XForms NG we want lazy evaluation of the instance, so we should not force a
                            // read on the instance. We just connect the output.
                            addStatement(new ASTProcessorCall(XMLConstants.NULL_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", new ASTHrefId(modelInstance)));
                                }
                            });
                        }

                        if (modelAttribute == null && viewAttribute == null) {
                            // Send out epilogue model data as a null document
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                    addOutput(new ASTOutput("data", epilogueModelData));
                                }
                            });
                        } else {
                            // Send out epilogue model data as produced by the model or used by the view
                            addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                {
                                    addInput(new ASTInput("data", new ASTHrefId(modelData)));
                                    addOutput(new ASTOutput("data", epilogueModelData));
                                }
                            });
                        }

                    }
                });
                addWhen(new ASTWhen() {
                    {
                        // There is a redirection due to the action

                        // With XForms NG we want lazy evaluation of the instance, so we should not force a
                        // read on the instance. We just connect the output.
                        addStatement(new ASTProcessorCall(XMLConstants.NULL_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("data", new ASTHrefId(xupdatedInstance)));
                            }
                        });
                        // Just connect the output
                        addStatement(new ASTProcessorCall(XMLConstants.NULL_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("data", new ASTHrefId(actionData)));
                            }
                        });
                        addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                addOutput(new ASTOutput("data", viewData));
                            }
                        });
                        addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                addOutput(new ASTOutput("data", epilogueModelData));
                            }
                        });
                        addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("data", Dom4jUtils.NULL_DOCUMENT));
                                addOutput(new ASTOutput("data", viewInstance));
                            }
                        });
                    }
                });
            }
        });
    }

    private static void executeResult(ASTWhen when, final Map<String, String> pageIdToPathInfo,
            final Map<String, Document> pageIdToSetvaluesDocument, final ASTOutput instanceToUpdate,
            final Element resultElement, final ASTOutput actionData, final ASTOutput redirect,
            final ASTOutput xupdatedInstance, String instancePassing) {

        // Instance to update: either current, or instance from other page
        final String resultPageId = resultElement == null ? null : resultElement.attributeValue("page");
        Attribute instancePassingAttribute = resultElement == null ? null
                : resultElement.attribute("instance-passing");
        final String _instancePassing = instancePassingAttribute == null ? instancePassing
                : instancePassingAttribute.getValue();

        // Create resulting instance
        final ASTOutput internalXUpdatedInstance;
        final boolean isTransformedInstance;
        if (resultElement != null && resultElement.attribute("transform") != null
                && !resultElement.elements().isEmpty()) {
            // Generic transform mechanism
            internalXUpdatedInstance = new ASTOutput("data", "internal-xupdated-instance");
            isTransformedInstance = true;

            final Document transformConfig = Dom4jUtils
                    .createDocumentCopyParentNamespaces((Element) resultElement.elements().get(0));
            final QName transformQName = Dom4jUtils.extractAttributeValueQName(resultElement, "transform");

            // Run transform
            final String resultTraceAttribute = resultElement.attributeValue("trace");
            when.addStatement(new ASTProcessorCall(transformQName) {
                {
                    addInput(new ASTInput("config", transformConfig));// transform
                    addInput(new ASTInput("instance", new ASTHrefId(instanceToUpdate)));// source-instance
                    addInput(new ASTInput("data", new ASTHrefId(instanceToUpdate)));// destination-instance
                    //addInput(new ASTInput("request-instance", new ASTHrefId(requestInstance)));// params-instance TODO
                    if (actionData != null)
                        addInput(new ASTInput("action", new ASTHrefId(actionData)));// action
                    else
                        addInput(new ASTInput("action", Dom4jUtils.NULL_DOCUMENT));// action
                    addOutput(new ASTOutput("data", internalXUpdatedInstance) {
                        {
                            setDebug(resultTraceAttribute);
                        }
                    });// updated-instance
                }
            });
        } else {
            internalXUpdatedInstance = instanceToUpdate;
            isTransformedInstance = false;
        }

        // Do redirect if we are going to a new page (NOTE: even if the new page has the same id as the current page)
        if (resultPageId != null) {
            final String forwardPathInfo = pageIdToPathInfo.get(resultPageId);
            if (forwardPathInfo == null)
                throw new OXFException("Cannot find page with id '" + resultPageId + "'");

            final Document setvaluesDocument = pageIdToSetvaluesDocument.get(resultPageId);
            final boolean doServerSideRedirect = _instancePassing != null
                    && _instancePassing.equals(INSTANCE_PASSING_FORWARD);
            final boolean doRedirectExitPortal = _instancePassing != null
                    && _instancePassing.equals(INSTANCE_PASSING_REDIRECT_PORTAL);

            // TODO: we should probably optimize all the redirect handling below with a dedicated processor
            {
                // Do redirect passing parameters from internalXUpdatedInstance without modifying URL
                final ASTOutput parametersOutput;
                if (isTransformedInstance) {
                    parametersOutput = new ASTOutput(null, "parameters");
                    // Pass parameters only if needed
                    final QName instanceToParametersProcessor = XMLConstants.INSTANCE_TO_PARAMETERS_PROCESSOR_QNAME;
                    when.addStatement(new ASTProcessorCall(instanceToParametersProcessor) {
                        {
                            addInput(new ASTInput("instance", new ASTHrefId(internalXUpdatedInstance)));
                            addInput(new ASTInput("filter",
                                    (setvaluesDocument != null) ? setvaluesDocument : Dom4jUtils.NULL_DOCUMENT));
                            addOutput(new ASTOutput("data", parametersOutput));
                        }
                    });
                } else {
                    parametersOutput = null;
                }
                // Handle path info
                final ASTOutput forwardPathInfoOutput = new ASTOutput(null, "forward-path-info");
                when.addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                    {
                        addInput(new ASTInput("data",
                                new NonLazyUserDataDocument(new NonLazyUserDataElement("path-info") {
                                    {
                                        setText(forwardPathInfo);
                                    }
                                })));
                        addOutput(new ASTOutput("data", forwardPathInfoOutput));
                    }
                });
                // Handle server-side redirect and exit portal redirect
                final ASTOutput isServerSideRedirectOutput = new ASTOutput(null, "is-server-side-redirect");
                when.addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                    {
                        addInput(new ASTInput("data",
                                new NonLazyUserDataDocument(new NonLazyUserDataElement("server-side") {
                                    {
                                        setText(Boolean.toString(doServerSideRedirect));
                                    }
                                })));
                        addOutput(new ASTOutput("data", isServerSideRedirectOutput));
                    }
                });
                final ASTOutput isRedirectExitPortal = new ASTOutput(null, "is-redirect-exit-portal");
                when.addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                    {
                        addInput(new ASTInput("data",
                                new NonLazyUserDataDocument(new NonLazyUserDataElement("exit-portal") {
                                    {
                                        setText(Boolean.toString(doRedirectExitPortal));
                                    }
                                })));
                        addOutput(new ASTOutput("data", isRedirectExitPortal));
                    }
                });
                // Aggregate redirect-url config
                final ASTHref redirectURLData;
                if (setvaluesDocument != null && isTransformedInstance) {
                    // Setvalues document - things are little more complicated, so we delegate
                    final ASTOutput redirectDataOutput = new ASTOutput(null, "redirect-data");

                    final ASTHrefAggregate redirectDataAggregate = new ASTHrefAggregate("redirect-url",
                            new ASTHrefId(forwardPathInfoOutput), new ASTHrefId(isServerSideRedirectOutput),
                            new ASTHrefId(isRedirectExitPortal));
                    redirectDataAggregate.getHrefs().add(new ASTHrefId(parametersOutput));

                    when.addStatement(new ASTProcessorCall(XMLConstants.UNSAFE_XSLT_PROCESSOR_QNAME) {
                        {
                            addInput(new ASTInput("config", new ASTHrefURL(REVERSE_SETVALUES_XSL)));
                            addInput(new ASTInput("data", redirectDataAggregate));
                            addInput(new ASTInput("instance", new ASTHrefId(internalXUpdatedInstance)));
                            addInput(new ASTInput("setvalues", setvaluesDocument));
                            addOutput(new ASTOutput("data", redirectDataOutput));
                        }
                    });
                    redirectURLData = new ASTHrefId(redirectDataOutput);
                } else {
                    // No setvalues document, we can simply aggregate with XPL
                    final ASTHrefAggregate redirectDataAggregate = new ASTHrefAggregate("redirect-url",
                            new ASTHrefId(forwardPathInfoOutput), new ASTHrefId(isServerSideRedirectOutput),
                            new ASTHrefId(isRedirectExitPortal));
                    if (isTransformedInstance) // Pass parameters only if needed
                        redirectDataAggregate.getHrefs().add(new ASTHrefId(parametersOutput));
                    redirectURLData = redirectDataAggregate;
                }
                // Execute the redirect
                when.addStatement(new ASTProcessorCall(XMLConstants.REDIRECT_PROCESSOR_QNAME) {
                    {
                        addInput(new ASTInput("data", redirectURLData));// {{setDebug("redirect 2");}}
                        final String[] locationParams = new String[] { "result page id", resultPageId };
                        setLocationData(new ExtendedLocationData((LocationData) resultElement.getData(),
                                "page redirection", resultElement, locationParams, true));
                    }
                });
            }
        }

        // Signal if we did a redirect
        when.addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
            {
                addInput(
                        new ASTInput("data", new NonLazyUserDataDocument(new NonLazyUserDataElement("is-redirect") {
                            {
                                setText(Boolean.toString(resultPageId != null));
                            }
                        })));
                addOutput(new ASTOutput("data", redirect));
            }
        });

        // Export XUpdated instance from this branch
        when.addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
            {
                addInput(new ASTInput("data", new ASTHrefId(internalXUpdatedInstance)));
                addOutput(new ASTOutput("data", xupdatedInstance));
            }
        });
    }

    /**
     * Creates a single StepProcessor. This should be called only once for a given Page Flow
     * configuration. Then the same StepProcessor should be used for each step.
     */
    public static class StepProcessorContext {

        private PipelineConfig pipelineConfig;

        public StepProcessorContext(final Object controllerValidity) {
            this.pipelineConfig = PipelineProcessor.createConfigFromAST(new ASTPipeline() {
                {
                    setValidity(controllerValidity);

                    final ASTParam stepURLInput = addParam(new ASTParam(ASTParam.INPUT, "step-url"));
                    final ASTParam stepTypeInput = addParam(new ASTParam(ASTParam.INPUT, "step-type"));
                    final ASTParam dataInput = addParam(new ASTParam(ASTParam.INPUT, "data"));
                    final ASTParam instanceInput = addParam(new ASTParam(ASTParam.INPUT, "instance"));
                    final ASTParam xformsModelInput = addParam(new ASTParam(ASTParam.INPUT, "xforms-model"));
                    final ASTParam matcherInput = addParam(new ASTParam(ASTParam.INPUT, "matcher"));
                    final ASTParam dataOutput = addParam(new ASTParam(ASTParam.OUTPUT, "data"));
                    final ASTParam instanceOutput = addParam(new ASTParam(ASTParam.OUTPUT, "instance"));

                    // Rewrite the URL if needed
                    final ASTOutput rewroteStepURL = new ASTOutput(null, "rewrote-step-url");
                    addStatement(new ASTChoose(new ASTHrefId(stepURLInput)) {
                        {
                            addWhen(new ASTWhen("contains(/config/url, '${')") {
                                {
                                    addStatement(new ASTProcessorCall(XMLConstants.XSLT_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefAggregate("root",
                                                    new ASTHrefId(stepURLInput), new ASTHrefId(matcherInput))));
                                            addInput(new ASTInput("config", new ASTHrefURL(REWRITE_XSL)));
                                            addOutput(new ASTOutput("data", rewroteStepURL));
                                        }
                                    });
                                }
                            });
                            addWhen(new ASTWhen() {
                                {
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(stepURLInput)));
                                            addOutput(new ASTOutput("data", rewroteStepURL));
                                        }
                                    });
                                }
                            });
                        }
                    });

                    final ASTOutput contentXIncluded = new ASTOutput("data", "content-xincluded");
                    {
                        // Read file to "execute"
                        final ASTOutput content = new ASTOutput("data", "content");
                        addStatement(new ASTProcessorCall(XMLConstants.URL_GENERATOR_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("config", new ASTHrefId(rewroteStepURL)));
                                addOutput(content);
                            }
                        });

                        // Insert XInclude processor to process content with XInclude
                        addStatement(new ASTProcessorCall(XMLConstants.XINCLUDE_PROCESSOR_QNAME) {
                            {
                                addInput(new ASTInput("config", new ASTHrefId(content)));
                                addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                addInput(new ASTInput("instance", new ASTHrefId(instanceInput)));
                                final ASTOutput contentOutput = new ASTOutput("data", contentXIncluded);
                                addOutput(contentOutput);
                            }
                        });
                    }

                    final ASTOutput resultData = new ASTOutput(null, "result-data");
                    final ASTOutput resultInstance = new ASTOutput(null, "result-instance");

                    // Perform verifications on input/output of XPL
                    addStatement(new ASTChoose(new ASTHrefId(stepTypeInput)) {
                        {
                            addWhen(new ASTWhen("/step-type = 'view'") {
                                {
                                    // We are dealing with a view
                                    addStatement(new ASTChoose(new ASTHrefId(contentXIncluded)) {
                                        {
                                            addWhen(new ASTWhen(
                                                    "namespace-uri(/*) = 'http://www.orbeon.com/oxf/pipeline' "
                                                            + "and count(/*/*[local-name() = 'param' and @type = 'output' and @name = 'data']) = 0") {
                                                {
                                                    // The XPL has not data output
                                                    addStatement(new ASTProcessorCall(
                                                            XMLConstants.ERROR_PROCESSOR_QNAME) {
                                                        {
                                                            final Document errorDocument = new NonLazyUserDataDocument(
                                                                    new NonLazyUserDataElement("error"));
                                                            errorDocument.getRootElement()
                                                                    .addText("XPL view must have a 'data' output");
                                                            addInput(new ASTInput("config", errorDocument));
                                                        }
                                                    });
                                                }
                                            });
                                        }
                                    });
                                }
                            });
                        }
                    });

                    addStatement(new ASTChoose(new ASTHrefId(contentXIncluded)) {
                        {

                            // XPL file with instance & data output
                            addWhen(new ASTWhen("namespace-uri(/*) = 'http://www.orbeon.com/oxf/pipeline' "
                                    + "and /*/*[local-name() = 'param' and @type = 'output' and @name = 'data'] "
                                    + "and /*/*[local-name() = 'param' and @type = 'output' and @name = 'instance']") {
                                {
                                    addStatement(new ASTProcessorCall(XMLConstants.PIPELINE_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("config", new ASTHrefId(contentXIncluded)));
                                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                            addInput(new ASTInput("instance", new ASTHrefId(instanceInput)));
                                            addInput(new ASTInput("xforms-model", new ASTHrefId(xformsModelInput)));
                                            final ASTOutput datOut = new ASTOutput("data", resultData);
                                            addOutput(datOut);
                                            final ASTOutput instOut = new ASTOutput("instance", resultInstance);
                                            addOutput(instOut);
                                        }
                                    });
                                }
                            });

                            // XPL file with only data output
                            addWhen(new ASTWhen("namespace-uri(/*) = 'http://www.orbeon.com/oxf/pipeline' "
                                    + "and /*/*[local-name() = 'param' and @type = 'output' and @name = 'data']") {
                                {
                                    addStatement(new ASTProcessorCall(XMLConstants.PIPELINE_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("config", new ASTHrefId(contentXIncluded)));
                                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                            addInput(new ASTInput("instance", new ASTHrefId(instanceInput)));
                                            addInput(new ASTInput("xforms-model", new ASTHrefId(xformsModelInput)));
                                            final ASTOutput datOut = new ASTOutput("data", resultData);
                                            addOutput(datOut);
                                        }
                                    });
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(instanceInput)));
                                            final ASTOutput datOut = new ASTOutput("data", resultInstance);
                                            addOutput(datOut);
                                        }
                                    });
                                }
                            });

                            // XPL file with only instance output
                            addWhen(new ASTWhen("namespace-uri(/*) = 'http://www.orbeon.com/oxf/pipeline' "
                                    + "and /*/*[local-name() = 'param' and @type = 'output' and @name = 'instance']") {
                                {
                                    addStatement(new ASTProcessorCall(XMLConstants.PIPELINE_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("config", new ASTHrefId(contentXIncluded)));
                                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                            addInput(new ASTInput("instance", new ASTHrefId(instanceInput)));
                                            addInput(new ASTInput("xforms-model", new ASTHrefId(xformsModelInput)));
                                            final ASTOutput instOut = new ASTOutput("instance", resultInstance);
                                            addOutput(instOut);
                                        }
                                    });
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                            final ASTOutput resDatOut = new ASTOutput("data", resultData);
                                            addOutput(resDatOut);
                                        }
                                    });
                                }
                            });

                            // XPL file with no output
                            addWhen(new ASTWhen("namespace-uri(/*) = 'http://www.orbeon.com/oxf/pipeline'") {
                                {
                                    addStatement(new ASTProcessorCall(XMLConstants.PIPELINE_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("config", new ASTHrefId(contentXIncluded)));
                                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                            addInput(new ASTInput("instance", new ASTHrefId(instanceInput)));
                                            addInput(new ASTInput("xforms-model", new ASTHrefId(xformsModelInput)));
                                        }
                                    });
                                    // Simply bypass data and instance channels
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                            final ASTOutput resDatOut = new ASTOutput("data", resultData);
                                            addOutput(resDatOut);
                                        }
                                    });
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(instanceInput)));
                                            final ASTOutput resInstOut = new ASTOutput("data", resultInstance);
                                            addOutput(resInstOut);
                                        }
                                    });
                                }
                            });

                            // PFC file (should only work as model)
                            addWhen(new ASTWhen("namespace-uri(/*) = 'http://www.orbeon.com/oxf/controller'") {
                                {
                                    addStatement(new ASTProcessorCall(XMLConstants.PAGE_FLOW_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("controller", new ASTHrefId(contentXIncluded)));
                                        }
                                    });
                                    // Simply bypass data and instance channels
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                            final ASTOutput resDatOut = new ASTOutput("data", resultData);
                                            addOutput(resDatOut);
                                        }
                                    });
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(instanceInput)));
                                            final ASTOutput resInstOut = new ASTOutput("data", resultInstance);
                                            addOutput(resInstOut);
                                        }
                                    });
                                }
                            });

                            // XSLT file (including XSLT 2.0 "Simplified Stylesheet Modules")
                            addWhen(new ASTWhen(
                                    "namespace-uri(/*) = 'http://www.w3.org/1999/XSL/Transform' or /*/@xsl:version = '2.0'") {
                                {
                                    setNamespaces(NAMESPACES_WITH_XSI_AND_XSLT);

                                    // Copy the instance as is
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(instanceInput)));
                                            final ASTOutput resInstOut = new ASTOutput("data", resultInstance);
                                            addOutput(resInstOut);
                                        }
                                    });

                                    // Process XInclude
                                    //                        final ASTOutput xincludedContent = new ASTOutput("data", "xincluded-content");
                                    //                        addStatement(new ASTProcessorCall(XMLConstants.XINCLUDE_PROCESSOR_QNAME) {{
                                    //                            addInput(new ASTInput("config", new ASTHrefId(content)));
                                    ////                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                    ////                            addInput(new ASTInput("instance", new ASTHrefId(instanceInput)));
                                    //                            addOutput(xincludedContent);
                                    //                        }});

                                    addStatement(new ASTChoose(new ASTHrefId(contentXIncluded)) {

                                        private void addXSLTWhen(final String condition,
                                                final QName processorQName) {
                                            addWhen(new ASTWhen(condition) {
                                                {
                                                    setNamespaces(NAMESPACES_WITH_XSI_AND_XSLT);

                                                    // Changed from <= 2.8 behavior
                                                    addStatement(new ASTProcessorCall(processorQName) {
                                                        {
                                                            addInput(new ASTInput("config",
                                                                    new ASTHrefId(contentXIncluded)));
                                                            addInput(
                                                                    new ASTInput("data", new ASTHrefId(dataInput)));
                                                            addInput(new ASTInput("instance",
                                                                    new ASTHrefId(instanceInput)));
                                                            final ASTOutput resDatOut = new ASTOutput("data",
                                                                    resultData);
                                                            addOutput(resDatOut);
                                                        }
                                                    });
                                                }
                                            });
                                        }

                                        {
                                            // XSLT 1.0: There is no xsl:version = '2.0' attribute (therefore the namespace of the
                                            //           root element is xsl as per the condition above) and the version attribute
                                            //           is exactly '1.0'
                                            addXSLTWhen("not(/*/@xsl:version = '2.0') and /*/@version = '1.0'",
                                                    XMLConstants.PFC_XSLT10_PROCESSOR_QNAME);

                                            // XSLT 2.0: There is an xsl:version = '2.0' attribute or the namespace or the root
                                            //           element is xsl and the version is different from '1.0'
                                            addXSLTWhen(null, XMLConstants.PFC_XSLT20_PROCESSOR_QNAME);
                                        }
                                    });
                                }
                            });

                            // XML file
                            addWhen(new ASTWhen() {
                                {

                                    // Copy the instance as is
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(instanceInput)));
                                            final ASTOutput resInstOut = new ASTOutput("data", resultInstance);
                                            addOutput(resInstOut);
                                        }
                                    });

                                    // Copy the data as is
                                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                                        {
                                            addInput(new ASTInput("data", new ASTHrefId(contentXIncluded)));
                                            final ASTOutput resDatOut = new ASTOutput("data", resultData);
                                            addOutput(resDatOut);
                                        }
                                    });

                                    // Insert XInclude processor to process static XML file with XInclude
                                    //                        addStatement(new ASTProcessorCall(XMLConstants.XINCLUDE_PROCESSOR_QNAME) {{
                                    //                            addInput(new ASTInput("config", new ASTHrefId(content)));
                                    //                            addInput(new ASTInput("data", new ASTHrefId(dataInput)));
                                    //                            addInput(new ASTInput("instance", new ASTHrefId(instanceInput)));
                                    //                            final ASTOutput resDatOut = new ASTOutput("data", resultData);
                                    //                            addOutput(resDatOut);
                                    //                        }});
                                }
                            });
                        }
                    });

                    // Connect results
                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                        {
                            addInput(new ASTInput("data", new ASTHrefId(resultData)));
                            addOutput(new ASTOutput("data", dataOutput));
                        }
                    });
                    addStatement(new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME) {
                        {
                            addInput(new ASTInput("data", new ASTHrefId(resultInstance)));
                            final ASTOutput resDatOut = new ASTOutput("data", instanceOutput);
                            addOutput(resDatOut);
                        }
                    });
                }
            });
        }

        public PipelineConfig getPipelineConfig() {
            return pipelineConfig;
        }
    }

    private static class StepProcessorCall extends ASTProcessorCall {
        public StepProcessorCall(StepProcessorContext stepProcessorContext, String controllerContext, String uri,
                String stepType) {
            super(new PipelineProcessor(stepProcessorContext.getPipelineConfig()));
            try {
                // Create document and input for URI
                final String url = URLFactory.createURL(controllerContext, uri).toExternalForm();
                final Document configDocument;
                {
                    configDocument = new NonLazyUserDataDocument(new NonLazyUserDataElement("config"));
                    final Element urlElement = configDocument.getRootElement().addElement("url");
                    urlElement.addText(url);
                    final Element handleXIncludeElement = configDocument.getRootElement()
                            .addElement("handle-xinclude");
                    handleXIncludeElement.addText("false");
                    // Allow external entities in document
                    final Element externalEntitiesElement = configDocument.getRootElement()
                            .addElement("external-entities");
                    externalEntitiesElement.addText("true");
                }

                addInput(new ASTInput("step-url", configDocument));
                // Create document and input for step type
                final Document stepTypeDocument = new NonLazyUserDataDocument(
                        new NonLazyUserDataElement("step-type"));
                stepTypeDocument.getRootElement().addText(stepType);
                addInput(new ASTInput("step-type", stepTypeDocument));
            } catch (MalformedURLException e) {
                throw new OXFException(e);
            }
        }
    }
}