All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xmlcalabash.core.XProcConfiguration Maven / Gradle / Ivy

The newest version!
package com.xmlcalabash.core;

import com.nwalsh.annotations.SaxonExtensionFunction;
import com.xmlcalabash.io.DocumentSequence;
import com.xmlcalabash.io.ReadablePipe;
import com.xmlcalabash.model.Step;
import com.xmlcalabash.piperack.PipelineSource;
import com.xmlcalabash.runtime.XAtomicStep;
import com.xmlcalabash.util.AxisNodes;
import com.xmlcalabash.util.Input;
import com.xmlcalabash.util.JSONtoXML;
import com.xmlcalabash.util.LogOptions;
import com.xmlcalabash.util.Output;
import com.xmlcalabash.util.S9apiUtils;
import com.xmlcalabash.util.URIUtils;
import net.sf.saxon.Configuration;
import net.sf.saxon.Version;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.functions.FunctionLibraryList;
import net.sf.saxon.om.NoElementsSpaceStrippingRule;
import net.sf.saxon.s9api.Axis;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmDestination;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmNodeKind;
import net.sf.saxon.s9api.XdmValue;
import org.atteo.classindex.ClassFilter;
import org.atteo.classindex.ClassIndex;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;

import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import static com.xmlcalabash.util.URIUtils.encode;
import static java.lang.String.format;
import static java.lang.System.getProperty;

/**
 * Created by IntelliJ IDEA.
 * User: ndw
 * Date: Nov 11, 2008
 * Time: 7:47:38 PM
 * To change this template use File | Settings | File Templates.
 */
public class XProcConfiguration {
    public static final QName _prefix = new QName("", "prefix");
    public static final QName _uri = new QName("", "uri");
    public static final QName _class_name = new QName("", "class-name");
    public static final QName _type = new QName("", "type");
    public static final QName _port = new QName("", "port");
    public static final QName _href = new QName("", "href");
    public static final QName _data = new QName("", "data");
    public static final QName _name = new QName("", "name");
    public static final QName _key = new QName("", "key");
    public static final QName _expires = new QName("", "expires");
    public static final QName _value = new QName("", "value");
    public static final QName _loader = new QName("", "loader");
    public static final QName _exclude_inline_prefixes = new QName("", "exclude-inline-prefixes");

    protected Logger logger = null;

    public String saxonProcessor = "he";
    public boolean schemaAware = false;
    public Input saxonConfig = null;
    public Hashtable nsBindings = new Hashtable ();
    public boolean debug = false;
    public boolean showMessages = false;
    public Output profile = null;
    public Hashtable> inputs = new Hashtable> ();
    public ReadablePipe pipeline = null;
    public Hashtable outputs = new Hashtable ();
    public Hashtable> params = new Hashtable> ();
    public Hashtable options = new Hashtable ();
    public boolean safeMode = false;
    public String stepName = null;
    public String entityResolver = "org.xmlresolver.Resolver";
    public String uriResolver = "org.xmlresolver.Resolver";
    public String errorListener = null;
    public Hashtable implementations = new Hashtable ();
    public Hashtable serializationOptions = new Hashtable();
    public LogOptions logOpt = LogOptions.WRAPPED;
    public HashMap extensionFunctions = new HashMap();
    /**
     * List to hold the in scope XSLT function libraries (loaded with cx:import). Libraries that are
     * added to this list become in scope, libraries that are removed become out scope.
     */
    public List inscopeXsltFunctions;
    public String foProcessor = null;
    public String cssProcessor = null;
    public String xprocConfigurer = null;
    public String htmlParser = "validator.nu";
    public String mailHost = null;
    public String mailPort = "25";
    public String mailUser = null;
    public String mailPass = null;
    public Hashtable loaders = new Hashtable ();
    public HashSet setSaxonProperties = new HashSet();

    public boolean extensionValues = false;
    public boolean xpointerOnText = false;
    public boolean transparentJSON = false;
    public boolean sequenceAsContext = false;
    public String jsonFlavor = JSONtoXML.MARKLOGIC;
    public boolean useXslt10 = false;
    public boolean htmlSerializer = false;
    public boolean allowTextResults = false;
    public Vector catalogs = new Vector ();

    public int piperackPort = 8088;
    public int piperackDefaultExpires = 300;
    public HashMap piperackDefaultPipelines = new HashMap();

    private Processor cfgProcessor = null;
    private boolean firstInput = false;
    private boolean firstOutput = false;

    public XProcConfiguration() {
        logger = LoggerFactory.getLogger(this.getClass());
        initSaxonProcessor("he", false, null);
        init();
    }

    // This constructor is historical, the (String, boolean) constructor is preferred
    public XProcConfiguration(boolean schemaAware) {
        logger = LoggerFactory.getLogger(this.getClass());
        initSaxonProcessor("he", schemaAware, null);
        init();
    }

    public XProcConfiguration(Input saxoncfg) {
        logger = LoggerFactory.getLogger(this.getClass());
        initSaxonProcessor(null, false, saxoncfg);
        init();
    }

    public XProcConfiguration(String proctype, boolean schemaAware) {
        logger = LoggerFactory.getLogger(this.getClass());
        initSaxonProcessor(proctype, schemaAware, null);
        init();
    }

    public XProcConfiguration(Processor processor) {
        logger = LoggerFactory.getLogger(this.getClass());
        cfgProcessor = processor;
        loadConfiguration();
        if (schemaAware != processor.isSchemaAware()) {
            throw new XProcException("Schema awareness in configuration conflicts with specified processor.");
        }
        init();
    }

    public Processor getProcessor() {
        return cfgProcessor;
    }

    private void initSaxonProcessor(String proctype, boolean schemaAware, Input saxoncfg) {
        if (schemaAware) {
            proctype = "ee";
        }

        createSaxonProcessor(proctype, schemaAware, saxoncfg);
        loadConfiguration();

        // If we got a schema aware processor, make sure it's reflected in our config
        // FIXME: are there other things that should be reflected this way?
        this.schemaAware = cfgProcessor.isSchemaAware();
        saxonProcessor = Version.softwareEdition.toLowerCase();

        if (saxoncfg != null) {
            // If there was a Saxon configuration, then it wins
            schemaAware = this.schemaAware;
            proctype = saxonProcessor;
        }

        if (!(proctype == null || saxonProcessor.equals(proctype))
                || schemaAware != this.schemaAware
                || (saxoncfg == null && saxonConfig != null)) {
            // Drat. We have to restart to get the right configuration.
            nsBindings.clear();
            inputs.clear();
            outputs.clear();
            params.clear();
            options.clear();
            implementations.clear();
            extensionFunctions.clear();

            createSaxonProcessor(saxonProcessor, this.schemaAware, saxonConfig);
            loadConfiguration();

            // If we got a schema aware processor, make sure it's reflected in our config
            // FIXME: are there other things that should be reflected this way?
            this.schemaAware = cfgProcessor.isSchemaAware();
            saxonProcessor = Version.softwareEdition.toLowerCase();
        }
    }

    private void init() {

        // Add inscopeXsltFunctions list to getBuiltInExtensionLibraryList. This is the most
        // convenient way to do it. A more appropriate place would be
        // getIntegratedFunctionLibrary(), but that would require using reflection and extending the
        // IntegratedFunctionLibrary class.
        FunctionLibraryList list = new FunctionLibraryList();
        cfgProcessor.getUnderlyingConfiguration()
                    .getBuiltInExtensionLibraryList()
                    .addFunctionLibrary(list);
        inscopeXsltFunctions = list.getLibraryList();

        // If we got a schema aware processor, make sure it's reflected in our config
        // FIXME: are there other things that should be reflected this way?
        this.schemaAware = cfgProcessor.isSchemaAware();
        saxonProcessor = Version.softwareEdition.toLowerCase();
        findStepClasses();
        findExtensionFunctions();

        String classPath = System.getProperty("java.class.path");
        String[] pathElements = classPath.split(System.getProperty("path.separator"));
        for (String path : pathElements) {
            // Make the path absolute wrt the cwd so that it can be opened later regardless of context
            path = new File(path).getAbsolutePath();
            String jarFileURL = URLDecoder.decode(new File(path).toURI().toString().replace("+", "%2B"));
            try {
                JarFile jar = new JarFile(path);
                ZipEntry catalog = jar.getEntry("catalog.xml");
                if (catalog != null) {
                    catalogs.add("jar:" + jarFileURL + "!/catalog.xml");
                    logger.debug("Using catalog: jar:" + jarFileURL + "!/catalog.xml");
                }
                catalog = jar.getEntry("META-INF/catalog.xml");
                if (catalog != null) {
                    catalogs.add("jar:" + jarFileURL + "!/META-INF/catalog.xml");
                    logger.debug("Using catalog: jar:" + jarFileURL + "!/META-INF/catalog.xml");
                }
            } catch (IOException e) {
                // If it's not a jar file, maybe it's a directory with a catalog
                String catfn = path;
                if (!catfn.endsWith("/")) {
                    catfn += "/";
                }
                catfn += "catalog.xml";
                File f = new File(catfn);
                if (f.exists() && f.isFile()) {
                    catalogs.add(catfn);
                    logger.debug("Using catalog: " + catfn);
                }
            }
        }
    }

    private void createSaxonProcessor(String proctype, boolean schemaAware, Input saxoncfg) {
        boolean licensed = schemaAware || !"he".equals(proctype);

        if (saxoncfg != null) {
            try {
                InputStream instream = null;
                switch (saxoncfg.getKind()) {
                    case URI:
                        URI furi = URI.create(saxoncfg.getUri());
                        instream = new FileInputStream(new File(furi));
                        break;

                    case INPUT_STREAM:
                        instream = saxoncfg.getInputStream();
                        break;

                    default:
                        throw new UnsupportedOperationException(format("Unsupported saxonConfig kind '%s'", saxoncfg.getKind()));
                }

                SAXSource source = new SAXSource(new InputSource(instream));
                cfgProcessor = new Processor(source);
            } catch (FileNotFoundException e) {
                throw new XProcException(e);
            } catch (SaxonApiException e) {
                throw new XProcException(e);
            }
        } else {
            cfgProcessor = new Processor(licensed);
        }

        cfgProcessor.getUnderlyingConfiguration().getParseOptions().setSpaceStrippingRule(NoElementsSpaceStrippingRule.getInstance());

        String actualtype = Version.softwareEdition;
        if ((proctype != null) && !"he".equals(proctype) && (!actualtype.toLowerCase().equals(proctype))) {
            System.err.println("Failed to obtain " + proctype.toUpperCase() + " processor; using " + actualtype + " instead.");
        }
    }

    private Iterable> findClasses(Class type) {
        Iterable> classes = null;
        try {
            classes = ClassFilter.only().from(ClassIndex.getAnnotated(type));
        } catch (NoClassDefFoundError e) {
            // org.atteo.classindex package does not exist
        }
        if (classes == null || !classes.iterator().hasNext()) {
            // most likely this happened because we are in OSGi context
            // fall back to finding annotations in current bundle only
            HashSet> set = new HashSet>();
            try {
                Bundle bundle = FrameworkUtil.getBundle(XProcConfiguration.class);
                if (bundle != null) {
                    String path = "META-INF/annotations/" + type.getCanonicalName();
                    URL url = bundle.getEntry(path);
                    if (url != null)
                        try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
                            String line = reader.readLine();
                            while (line != null) {
                                try {
                                    set.add(XProcConfiguration.class.getClassLoader().loadClass(line));
                                } catch (ClassNotFoundException e) {
                                    throw new RuntimeException("coding error"); // META-INF/annotations/... is corrupt
                                }
                                line = reader.readLine();
                            }
                        }
                    else
                        logger.warn("file " + path + " does not exist");
                } else {
                    // not in OSGi context after all
                }
            } catch (IOException e) {
                throw new RuntimeException("coding error");
            } catch (NoClassDefFoundError e) {
                // not in OSGi context after all
            }
            return set;
        }
        return classes;
    }

    private void findStepClasses() {
        Iterable> classes = findClasses(XMLCalabash.class);
        for (Class klass : classes) {
            XMLCalabash annotation = klass.getAnnotation(XMLCalabash.class);
            for (String clarkName: annotation.type().split("\\s+")) {
                try {
                    QName name = QName.fromClarkName(clarkName);
                    logger.trace("Found step type annotation: " + clarkName);
                    if (implementations.containsKey(name)) {
                        logger.debug("Ignoring step type annotation for configured step: " + clarkName);
                    }
                    implementations.put(name, klass);
                } catch (IllegalArgumentException iae) {
                    logger.debug("Failed to parse step annotation type: " + clarkName);
                }
            }
        }
    }

    private void findExtensionFunctions() {
        Iterable> classes = findClasses(SaxonExtensionFunction.class);
        for (Class klass : classes) {
            String name = klass.getCanonicalName();
            SaxonExtensionFunction annotation = klass.getAnnotation(SaxonExtensionFunction.class);
            logger.trace("Found Saxon extension function: " + klass.getCanonicalName());
            if (extensionFunctions.containsKey(name)) {
                logger.debug("Duplicate saxon extension function class: " + name);
            }
            extensionFunctions.put(name, annotation);
        }
    }

    private String fixUpURI(String uri) {
        File f = new File(uri);
        String fn = encode(f.getAbsolutePath());
        // FIXME: HACK!
        if ("\\".equals(getProperty("file.separator"))) {
            fn = "/" + fn;
        }
        return fn;
    }

    private void loadConfiguration() {
        URI home = URIUtils.homeAsURI();
        URI cwd = URIUtils.cwdAsURI();
        URI puri = home;

        String cfg = System.getProperty("com.xmlcalabash.config.global");
        try {
            if (cfg == null) {
                InputStream instream = getClass().getResourceAsStream("/etc/configuration.xml");
                if (instream == null) {
                    // This may happen in OSGi.
                    // Because /etc/configuration.xml is empty anyway, we ignore this error.
                    return;
                    // throw new UnsupportedOperationException("Failed to load configuration from JAR file");
                }
                // No resolver, we don't have one yet
                SAXSource source = new SAXSource(new InputSource(instream));
                DocumentBuilder builder = cfgProcessor.newDocumentBuilder();
                builder.setLineNumbering(true);
                builder.setBaseURI(puri);
                parse(builder.build(source));
            } else {
                parse(readXML(cfg, cwd.toASCIIString()));
            }
        } catch (SaxonApiException sae) {
            throw new XProcException(sae);
        }

        cfg = System.getProperty("com.xmlcalabash.config.user", ".calabash");
        if ("".equals(cfg)) {
            // skip loading the user configuration
        } else {
            try {
                XdmNode cnode = readXML(cfg, home.toASCIIString());
                parse(cnode);
            } catch (XProcException xe) {
                if (XProcConstants.dynamicError(11).equals(xe.getErrorCode())) {
                    // nop; file not found is ok
                } else {
                    throw xe;
                }
            }
        }

        cfg = System.getProperty("com.xmlcalabash.config.local", ".calabash");
        if ("".equals(cfg)) {
            // skip loading the local configuration
        } else {
            try {
                XdmNode cnode = readXML(cfg, cwd.toASCIIString());
                parse(cnode);
            } catch (XProcException xe) {
                if (XProcConstants.dynamicError(11).equals(xe.getErrorCode())) {
                    // nop; file not found is ok
                } else {
                    throw xe;
                }
            }
        }

        // What about properties?
        saxonProcessor = System.getProperty("com.xmlcalabash.saxon-processor", saxonProcessor);

        if ( !("he".equals(saxonProcessor) || "pe".equals(saxonProcessor) || "ee".equals(saxonProcessor)) ) {
            throw new XProcException("Invalid Saxon processor specified in com.xmlcalabash.saxon-processor property.");
        }

        String saxonConfigProperty = System.getProperty("com.xmlcalabash.saxon-configuration");
        if (saxonConfigProperty != null) {
            saxonConfig = new Input("file://" + fixUpURI(saxonConfigProperty));
        }

        schemaAware = "true".equals(System.getProperty("com.xmlcalabash.schema-aware", ""+schemaAware));
        debug = "true".equals(System.getProperty("com.xmlcalabash.debug", ""+debug));
        showMessages = "true".equals(System.getProperty("com.xmlcalabash.show-messages", ""+showMessages));
        String profileProperty = System.getProperty("com.xmlcalabash.profile");
        if (profileProperty != null) {
            profile = new Output("file://" + fixUpURI(profileProperty));
        }
        extensionValues = "true".equals(System.getProperty("com.xmlcalabash.general-values", ""+extensionValues));
        xpointerOnText = "true".equals(System.getProperty("com.xmlcalabash.xpointer-on-text", ""+xpointerOnText));
        transparentJSON = "true".equals(System.getProperty("com.xmlcalabash.transparent-json", ""+transparentJSON));
        sequenceAsContext = "true".equals(System.getProperty("com.xmlcalabash.sequence-as-context", ""+sequenceAsContext));
        allowTextResults = "true".equals(System.getProperty("com.xmlcalabash.allow-text-results", ""+allowTextResults));
        safeMode = "true".equals(System.getProperty("com.xmlcalabash.safe-mode", ""+safeMode));
        jsonFlavor = System.getProperty("com.xmlcalabash.json-flavor", jsonFlavor);
        useXslt10 = "true".equals(System.getProperty("com.xmlcalabash.use-xslt-10", ""+useXslt10));
        htmlSerializer = "true".equals(System.getProperty("com.xmlcalabash.html-serializer", ""+htmlSerializer));
        entityResolver = System.getProperty("com.xmlcalabash.entity-resolver", entityResolver);
        uriResolver = System.getProperty("com.xmlcalabash.uri-resolver", uriResolver);
        errorListener = System.getProperty("com.xmlcalabash.error-listener", errorListener);
        foProcessor = System.getProperty("com.xmlcalabash.fo-processor", foProcessor);
        cssProcessor = System.getProperty("com.xmlcalabash.css-processor", cssProcessor);
        xprocConfigurer = System.getProperty("com.xmlcalabash.xproc-configurer", xprocConfigurer);
        htmlParser = System.getProperty("com.xmlcalabash.html-parser", htmlParser);
        mailHost = System.getProperty("com.xmlcalabash.mail-host", mailHost);
        mailPort = System.getProperty("com.xmlcalabash.mail-port", mailPort);
        mailUser = System.getProperty("com.xmlcalabash.mail-username", mailUser);
        mailPass = System.getProperty("com.xmlcalabash.mail-password", mailPass);

        if (System.getProperty("com.xmlcalabash.log-style") != null) {
            String s = System.getProperty("com.xmlcalabash.log-style");
            if ("off".equals(s)) {
                logOpt = LogOptions.OFF;
            } else if ("plain".equals(s)) {
                logOpt = LogOptions.PLAIN;
            } else if ("wrapped".equals(s)) {
                logOpt = LogOptions.WRAPPED;
            } else if ("directory".equals(s)) {
                logOpt = LogOptions.DIRECTORY;
            } else {
                throw new XProcException("Invalid log-style specified in com.xmlcalabash.log-style property");
            }
        }

        if (System.getProperty("com.xmlcalabash.piperack-port") != null) {
            piperackPort = Integer.parseInt(System.getProperty("com.xmlcalabash.piperack-port"));
        }

        if (System.getProperty("com.xmlcalabash.piperack-default-expires") != null) {
            piperackDefaultExpires = Integer.parseInt(System.getProperty("com.xmlcalabash.piperack-port"));
        }

        String[] boolSerNames = new String[] {"byte-order-mark", "escape-uri-attributes",
                "include-content-type","indent", "omit-xml-declaration", "undeclare-prefixes"};
        String[] strSerNames = new String[] {"doctype-public", "doctype-system", "encoding",
                "media-type", "normalization-form", "version", "standalone"};

        for (String name : boolSerNames) {
            String s = System.getProperty("com.xmlcalabash.serial."+name);
            if ("true".equals(s) || "false".equals(s)) {
                serializationOptions.put(name, s);
            }
        }

        for (String name : strSerNames) {
            String s = System.getProperty("com.xmlcalabash.serial."+name);
            if (s != null) {
                serializationOptions.put(name, s);
            }
        }

        // cdata-section-elements is ignored

        String method = System.getProperty("com.xmlcalabash.serial.method");
        if ("html".equals(method) || "xhtml".equals(method) || "text".equals(method) || "xml".equals(method)) {
            serializationOptions.put(method, method);
        }
    }

    public XdmNode readXML(String href, String base) {
        Source source = null;
        href = URIUtils.encode(href);

        try {
            URI baseURI = new URI(base);
            source = new SAXSource(new InputSource(baseURI.resolve(href).toASCIIString()));
        } catch (URISyntaxException use) {
            throw new XProcException(use);
        }

        // No resolver, we don't have one yet
        DocumentBuilder builder = cfgProcessor.newDocumentBuilder();
        builder.setLineNumbering(true);

        try {
            return builder.build(source);
        } catch (SaxonApiException sae) {
            throw XProcException.dynamicError(11, sae);
        }
    }


    public void parse(XdmNode doc) {
        if (doc.getNodeKind() == XdmNodeKind.DOCUMENT) {
            doc = S9apiUtils.getDocumentElement(doc);
        }

        for (XdmNode node : new AxisNodes(null, doc, Axis.CHILD, AxisNodes.PIPELINE)) {
            String uri = node.getNodeName().getNamespaceURI();
            String localName = node.getNodeName().getLocalName();

            if (XProcConstants.NS_CALABASH_CONFIG.equals(uri)
                    || XProcConstants.NS_EXPROC_CONFIG.equals(uri)) {
                if ("implementation".equals(localName)) {
                    parseImplementation(node);
                } else if ("saxon-processor".equals(localName)) {
                    parseSaxonProcessor(node);
                } else if ("saxon-configuration".equals(localName)) {
                    parseSaxonConfiguration(node);
                } else if ("schema-aware".equals(localName)) {
                    parseSchemaAware(node);
                } else if ("namespace-binding".equals(localName)) {
                    parseNamespaceBinding(node);
                } else if ("debug".equals(localName)) {
                    parseDebug(node);
                } else if ("show-messages".equals(localName)) {
                    parseShowMessages(node);
                } else if ("profile".equals(localName)) {
                    parseProfile(node);
                } else if ("entity-resolver".equals(localName)) {
                    parseEntityResolver(node);
                } else if ("input".equals(localName)) {
                    parseInput(node);
                } else if ("output".equals(localName)) {
                    parseOutput(node);
                } else if ("with-option".equals(localName)) {
                    parseWithOption(node);
                } else if ("with-param".equals(localName)) {
                    parseWithParam(node);
                } else if ("safe-mode".equals(localName)) {
                    parseSafeMode(node);
                } else if ("step-name".equals(localName)) {
                    parseStepName(node);
                } else if ("uri-resolver".equals(localName)) {
                    parseURIResolver(node);
                } else if ("step-error-listener".equals(localName)) {
                    parseErrorListener(node);
                } else if ("pipeline".equals(localName)) {
                    parsePipeline(node);
                } else if ("serialization".equals(localName)) {
                    parseSerialization(node);
                } else if ("extension-function".equals(localName)) {
                    parseExtensionFunction(node);
                } else if ("fo-processor".equals(localName)) {
                    parseFoProcessor(node);
                } else if ("css-processor".equals(localName)) {
                    parseCssProcessor(node);
                } else if ("xproc-configurer".equals(localName)) {
                    parseXProcConfigurer(node);
                } else if ("default-system-property".equals(localName)) {
                    parseSystemProperty(node);
                } else if ("extension".equals(localName)) {
                    parseExtension(node);
                } else if ("html-parser".equals(localName)) {
                    parseHtmlParser(node);
                } else if ("sendmail".equals(localName)) {
                    parseSendMail(node);
                } else if ("saxon-configuration-property".equals(localName)) {
                    saxonConfigurationProperty(node);
                } else if ("log-style".equals(localName)) {
                    logStyle(node);
                } else if ("pipeline-loader".equals(localName)) {
                    pipelineLoader(node);
                } else if ("piperack-port".equals(localName)) {
                    piperackPort(node);
                } else if ("piperack-default-expires".equals(localName)) {
                    piperackDefaultExpires(node);
                } else if ("piperack-load-pipeline".equals(localName)) {
                    piperackLoadPipeline(node);
                } else {
                    throw new XProcException(doc, "Unexpected configuration option: " + localName);
                }
            }
        }

        firstInput = true;
        firstOutput = true;
    }


	public boolean isStepAvailable(QName type) {
        if (implementations.containsKey(type)) {
            Class klass = implementations.get(type);
            try {
                Method method = klass.getMethod("isAvailable");
                Boolean available = (Boolean) method.invoke(null);
                return available.booleanValue();
            } catch (NoSuchMethodException e) {
                return true; // Failure to implement the method...
            } catch (InvocationTargetException e) {
                return true; // ...or to implement it...
            } catch (IllegalAccessException e) {
                return true; // ...badly doesn't mean it's not available.
            }
        } else {
            return false;
        }
	}

	public XProcStep newStep(XProcRuntime runtime,XAtomicStep step){
        Class klass = implementations.get(step.getType());
        if (klass == null) {
            throw new XProcException("Misconfigured. No 'class' in configuration for " + step.getType());
        }

        String className = klass.getName();
        // FIXME: This isn't really very secure...
        if (runtime.getSafeMode() && !className.startsWith("com.xmlcalabash.")) {
            throw XProcException.dynamicError(21);
        }

		try {
			Constructor constructor = Class.forName(className).asSubclass(XProcStep.class).getConstructor(XProcRuntime.class, XAtomicStep.class);
			return constructor.newInstance(runtime,step);
		} catch (NoSuchMethodException nsme) {
			throw new UnsupportedOperationException("No such method: " + className, nsme);
		} catch (ClassNotFoundException cfne) {
			throw new UnsupportedOperationException("Class not found: " + className, cfne);
		} catch (InstantiationException ie) {
			throw new UnsupportedOperationException("Instantiation error", ie);
		} catch (IllegalAccessException iae) {
			throw new UnsupportedOperationException("Illegal access error", iae);
		} catch (InvocationTargetException ite) {
			throw new UnsupportedOperationException("Invocation target exception", ite);
        }
    }

    public static void showVersion(XProcRuntime runtime) {
        System.out.println("XML Calabash version " + XProcConstants.XPROC_VERSION + ", an XProc processor.");
        if (runtime != null) {
            System.out.print("Running on Saxon version ");
            System.out.print(runtime.getConfiguration().getProcessor().getSaxonProductVersion());
            System.out.print(", ");
            System.out.print(runtime.getConfiguration().getProcessor().getUnderlyingConfiguration().getEditionCode());
            System.out.println(" edition.");
        }
        System.out.println("Copyright (c) 2007-2013 Norman Walsh");
        System.out.println("See docs/notices/NOTICES in the distribution for licensing");
        System.out.println("See also http://xmlcalabash.com/ for more information");
        System.out.println("");
    }

    private void parseSaxonProcessor(XdmNode node) {
        String value = node.getStringValue().trim();

        if ( !("he".equals(value) || "pe".equals(value) || "ee".equals(value)) ) {
            throw new XProcException(node, "Invalid Saxon processor: " + value + ". Must be 'he', 'pe', or 'ee'.");
        }

        saxonProcessor = value;
    }

    private void parseSaxonConfiguration(XdmNode node) {
        String value = node.getStringValue().trim();
        saxonConfig = new Input("file://" + fixUpURI(value));
    }

    private void parseSchemaAware(XdmNode node) {
        String value = node.getStringValue().trim();

        if (!"true".equals(value) && !"false".equals(value)) {
            throw new XProcException(node, "Invalid configuration value for schema-aware: "+ value);
        }

        schemaAware = "true".equals(value);
    }

    private void parseNamespaceBinding(XdmNode node) {
        String aname = node.getAttributeValue(_prefix);
        String avalue = node.getAttributeValue(_uri);
        nsBindings.put(aname, avalue);
    }

    private void parseDebug(XdmNode node) {
        String value = node.getStringValue().trim();
        debug = "true".equals(value);
        if (!"true".equals(value) && !"false".equals(value)) {
            throw new XProcException(node, "Invalid configuration value for debug: "+ value);
        }
    }

    private void parseShowMessages(XdmNode node) {
        String value = node.getStringValue().trim();
        showMessages = "true".equals(value);
        if (!"true".equals(value) && !"false".equals(value)) {
            throw new XProcException(node, "Invalid configuration value for show-messages: "+ value);
        }
    }

    private void parseProfile(XdmNode node) {
        profile = new Output("file://" + fixUpURI(node.getStringValue().trim()));
    }

    private void parseEntityResolver(XdmNode node) {
        String value = node.getAttributeValue(_class_name);
        entityResolver = value;
    }

    private void parseExtensionFunction(XdmNode node) {
        String value = node.getAttributeValue(_class_name);
        extensionFunctions.put(value, null);
    }

    private void parseFoProcessor(XdmNode node) {
        String value = node.getAttributeValue(_class_name);
        foProcessor = value;
    }

    private void parseCssProcessor(XdmNode node) {
        String value = node.getAttributeValue(_class_name);
        cssProcessor = value;
    }

    private void parseXProcConfigurer(XdmNode node) {
        String value = node.getAttributeValue(_class_name);
        xprocConfigurer = value;
    }

    private void parseSystemProperty(XdmNode node) {
        String name = node.getAttributeValue(_name);
        String value = node.getAttributeValue(_value);
        if (name == null || value == null) {
            throw new XProcException("Configuration option 'default-system-property' cannot have null name or value");
        }
        if (System.getProperty(name) == null) {
            System.setProperty(name, value);
        }
    }

    private void parseExtension(XdmNode node) {
        String name = node.getAttributeValue(_name);
        String value = node.getAttributeValue(_value);
        if (name == null || value == null) {
            throw new XProcException("Configuration option 'extension' cannot have null name or value");
        }

        if ("general-values".equals(name)) {
            extensionValues = "true".equals(value);
        } else if ("xpointer-on-text".equals(name)) {
            xpointerOnText = "true".equals(value);
        } else if ("transparent-json".equals(name)) {
            transparentJSON = "true".equals(value);
        } else if ("sequence-as-context".equals(name)) {
            sequenceAsContext = "true".equals(value);
        } else if ("json-flavor".equals(name)) {
            jsonFlavor = value;
            if (! JSONtoXML.knownFlavor(jsonFlavor)) {
                throw new XProcException("Unrecognized JSON flavor: " + jsonFlavor);
            }
        } else if ("allow-text-results".equals(name)) {
            allowTextResults = "true".equals(value);
        } else if ("use-xslt-1.0".equals(name) || "use-xslt-10".equals(name)) {
            useXslt10 = "true".equals(value);
        } else if ("html-serializer".equals(name)) {
            htmlSerializer = "true".equals(value);
        } else {
            throw new XProcException("Unrecognized extension in configuration: " + name);
        }
    }

    private void parseHtmlParser(XdmNode node) {
        String value = node.getAttributeValue(_value);
        if (value == null) {
            throw new XProcException("Configuration option 'html-parser' cannot have null value");
        }

        if ("validator.nu".equals(value) || "tagsoup".equals(value)) {
            htmlParser = value;
        } else {
            throw new XProcException("Unrecognized value in html-parser: " + value);
        }
    }

    private void parseSendMail(XdmNode node) {
        String host = node.getAttributeValue(new QName("", "host"));
        String port = node.getAttributeValue(_port);
        String user = node.getAttributeValue(new QName("", "username"));
        String pass = node.getAttributeValue(new QName("", "password"));

        if (host != null) { mailHost = host; }
        if (port != null) { mailPort = port; }
        if (user != null) {
            mailUser = user;
            if (pass == null) {
                throw new XProcException("Misconfigured sendmail: user specified without password");
            }
            mailPass = pass;
        }
    }

    private void saxonConfigurationProperty(XdmNode node) {
        String value = node.getAttributeValue(_value);
        String key = node.getAttributeValue(_key);
        String type = node.getAttributeValue(_type);
        Object valueObj = null;
        if (key == null || value == null) {
            throw new XProcException("Configuration option 'saxon-configuration-property' cannot have a null key or value");
        }

        if ("boolean".equals(type)) {
            valueObj = "true".equals(value);
        } else if ("integer".equals(type)) {
            valueObj = Integer.parseInt(value);
        } else {
            valueObj = value;
        }

        try {
            setSaxonProperties.add(key);
            cfgProcessor.setConfigurationProperty(key, valueObj);
        } catch (Exception e) {
            throw new XProcException(e);
        }
    }

    private void logStyle(XdmNode node) {
        String style = node.getAttributeValue(_value);
        if ("off".equals(style)) {
            logOpt = LogOptions.OFF;
        } else if ("plain".equals(style)) {
            logOpt = LogOptions.PLAIN;
        } else if ("wrapped".equals(style)) {
            logOpt = LogOptions.WRAPPED;
        } else if ("directory".equals(style)) {
            logOpt = LogOptions.DIRECTORY;
        } else {
            throw new XProcException("Configuration option 'log-style' must be one of 'off', 'plain', 'wrapped', or 'directory'");
        }
    }

    private void pipelineLoader(XdmNode node) {
        String data = node.getAttributeValue(_data);
        String href = node.getAttributeValue(_href);
        String loader = node.getAttributeValue(_loader);
        if ((data == null && href == null) || (data != null && href != null)) {
            throw new XProcException("Configuration option 'pipeline-loader' must have one of 'href' or 'data'");
        }
        if (loader == null) {
            throw new XProcException("Configuration option 'pipeline-loader' must specify a 'loader'");
        }

        if (data == null) {
            loaders.put("href:" + href, loader);
        } else {
            loaders.put("data:" + data, loader);
        }
    }

    private void piperackPort(XdmNode node) {
        String portno = node.getStringValue().trim();
        piperackPort = Integer.parseInt(portno);
    }

    private void piperackDefaultExpires(XdmNode node) {
        String secs = node.getStringValue().trim();
        piperackDefaultExpires = Integer.parseInt(secs);
    }

    private void piperackLoadPipeline(XdmNode node) {
        String uri = node.getStringValue().trim();
        String name = node.getAttributeValue(_name);
        int expires = -1;

        String s = node.getAttributeValue(_expires);
        if (s != null) {
            expires = Integer.parseInt(s);
        }

        if (name == null) {
            throw new XProcException(node, "You must specify a name for default pipelines.");
        }

        PipelineSource src = new PipelineSource(uri, name, expires);
        piperackDefaultPipelines.put(name, src);
    }

    private void parseInput(XdmNode node) {
        String port = node.getAttributeValue(_port);
        String href = node.getAttributeValue(_href);
        Vector docnodes = new Vector ();
        boolean sawElement = false;

        // FIXME: shouldn't this test for a "document" that doesn't have any document element?
        for (XdmNode child : new AxisNodes(null, node, Axis.CHILD, AxisNodes.ALL)) {
            if (child.getNodeKind() == XdmNodeKind.ELEMENT) {
                if (sawElement) {
                    throw new XProcException(node, "Invalid configuration value for input '" + port + "': content is not a valid XML document.");
                }
                sawElement = true;
            }
            docnodes.add(child);
        }

        if (firstInput) {
            inputs.clear();
            firstInput = false;
        }

        if (!inputs.containsKey(port)) {
            inputs.put(port, new Vector ());
        }

        Vector documents = inputs.get(port);

        if (href != null) {
            if (docnodes.size() > 0) {
                throw new XProcException(node, "Invalid configuration value for input '" + port + "': href and content on input.");
            }

            documents.add(new ConfigDocument(href, node.getBaseURI().toASCIIString()));
        } else {
            HashSet excludeURIs = S9apiUtils.excludeInlinePrefixes(node, node.getAttributeValue(_exclude_inline_prefixes));
            documents.add(new ConfigDocument(docnodes, excludeURIs));
        }
    }

    private void parsePipeline(XdmNode node) {
        String href = node.getAttributeValue(_href);
        Vector docnodes = new Vector ();
        boolean sawElement = false;

        for (XdmNode child : new AxisNodes(null, node, Axis.CHILD, AxisNodes.PIPELINE)) {
            if (child.getNodeKind() == XdmNodeKind.ELEMENT) {
                if (sawElement) {
                    throw new XProcException(node, "Content of pipeline is not a valid XML document.");
                }
                sawElement = true;
            }
            docnodes.add(child);
        }

        if (href != null) {
            if (docnodes.size() > 0) {
                throw new XProcException(node, "XProcConfiguration error: href and content on pipeline");
            }
            pipeline = new ConfigDocument(href, node.getBaseURI().toASCIIString());
        } else {
            HashSet excludeURIs = S9apiUtils.excludeInlinePrefixes(node, node.getAttributeValue(_exclude_inline_prefixes));
            pipeline = new ConfigDocument(docnodes, excludeURIs);
        }
    }

    private void parseOutput(XdmNode node) {
        String port = node.getAttributeValue(_port);
        String href = node.getAttributeValue(_href);

        for (XdmNode child : new AxisNodes(null, node, Axis.CHILD, AxisNodes.PIPELINE)) {
            if (child.getNodeKind() == XdmNodeKind.ELEMENT) {
                throw new XProcException(node, "Output must be empty.");
            }
        }

        if (firstOutput) {
            outputs.clear();
            firstOutput = false;
        }

        href = node.getBaseURI().resolve(href).toASCIIString();

        if ("-".equals(href) || href.startsWith("http:") || href.startsWith("https:") || href.startsWith("file:")) {
            outputs.put(port, href);
        } else {
            File f = new File(href);
            String fn = URIUtils.encode(f.getAbsolutePath());
            // FIXME: HACK!
            if ("\\".equals(System.getProperty("file.separator"))) {
                fn = "/" + fn;
            }
            outputs.put(port, "file://" + fn);
        }
    }

    private void parseWithOption(XdmNode node) {
        String nameStr = node.getAttributeValue(_name);
        String value = node.getAttributeValue(_value);

        QName name = new QName(nameStr,node);

        options.put(name,value);
    }

    private void parseWithParam(XdmNode node) {
        String port = node.getAttributeValue(_port);
        String nameStr = node.getAttributeValue(_name);
        String value = node.getAttributeValue(_value);

        QName name = new QName(nameStr,node);

        if (port == null) {
            port = "*";
        }

        Hashtable pvalues;
        if (params.containsKey(port)) {
            pvalues = params.get(port);
        } else {
            pvalues = new Hashtable ();
        }

        pvalues.put(name, value);

        params.put(port,pvalues);
    }

    private void parseSafeMode(XdmNode node) {
        String value = node.getStringValue().trim();

        safeMode = "true".equals(value);
        if (!"true".equals(value) && !"false".equals(value)) {
            throw new XProcException(node, "Unexpected configuration value for safe-mode: "+ value);
        }
    }


    private void parseStepName(XdmNode node) {
        String value = node.getStringValue().trim();
        stepName = value;
    }

    private void parseURIResolver(XdmNode node) {
        String value = node.getAttributeValue(_class_name);
        uriResolver = value;
    }

    private void parseErrorListener(XdmNode node) {
        String value = node.getAttributeValue(_class_name);
        errorListener = value;
    }

    private void parseImplementation(XdmNode node) {
        String nameStr = node.getAttributeValue(_type);
        String value = node.getAttributeValue(_class_name);

        if (nameStr == null || value == null) {
            throw new XProcException(node, "Unexpected implementation in configuration; must have both type and class-name attributes");
        }

        for (String tname : nameStr.split("\\s+")) {
            QName name = new QName(tname,node);
            try {
                Class klass = Class.forName(value);
                implementations.put(name, klass);
            } catch (ClassNotFoundException e) {
                logger.debug("Class not found: " + value);
            } catch (NoClassDefFoundError e) {
                String msg = e.getMessage();
                logger.debug("Cannot instantiate " + value + ", missing class: " + msg);
            }
        }
    }

    private void parseSerialization(XdmNode node) {
        String[] attributeNames = new String[] {"byte-order-mark", "cdata-section-elements",
                "doctype-public", "doctype-system", "encoding", "escape-uri-attributes",
                "include-content-type", "indent", "media-type", "method", "normalization-form",
                "omit-xml-declaration", "standalone", "undeclare-prefixes", "version"};

        checkAttributes(node, attributeNames , false);

        for (String name : attributeNames) {
            String value = node.getAttributeValue(new QName(name));
            if (value == null) {
                continue;
            }

            if ("byte-order-mark".equals(name) || "escape-uri-attributes".equals(name)
                    || "include-content-type".equals(name) || "indent".equals(name)
                    || "omit-xml-declaration".equals(name) || "undeclare-prefixes".equals(name)) {
                checkBoolean(node, name, value);
                serializationOptions.put(name, value);
            } else if ("method".equals(name)) {
                QName methodName = new QName(value, node);
                if ("".equals(methodName.getPrefix())) {
                    String method = methodName.getLocalName();
                    if ("html".equals(method) || "xhtml".equals(method) || "text".equals(method) || "xml".equals(method)) {
                        serializationOptions.put(name, method);
                    } else {
                        throw new XProcException(node, "Configuration error: only the xml, xhtml, html, and text serialization methods are supported.");
                    }
                } else {
                    throw new XProcException(node, "Configuration error: only the xml, xhtml, html, and text serialization methods are supported.");
                }
            } else {
                serializationOptions.put(name, value);
            }

            for (XdmNode snode : new AxisNodes(null, node, Axis.CHILD, AxisNodes.PIPELINE)) {
                throw new XProcException(node, "Configuration error: serialization must be empty");
            }
        }
    }

    private HashSet checkAttributes(XdmNode node, String[] attrs, boolean optionShortcutsOk) {
        HashSet hash = null;
        if (attrs != null) {
            hash = new HashSet ();
            for (String attr : attrs) {
                hash.add(attr);
            }
        }
        HashSet options = null;

        for (XdmNode attr : new AxisNodes(node, Axis.ATTRIBUTE)) {
            QName aname = attr.getNodeName();
            if ("".equals(aname.getNamespaceURI())) {
                if (hash.contains(aname.getLocalName())) {
                // ok
                } else if (optionShortcutsOk) {
                    if (options == null) {
                        options = new HashSet ();
                    }
                    options.add(aname.getLocalName());
                } else {
                    throw new XProcException(node, "Configuration error: attribute \"" + aname + "\" not allowed on " + node.getNodeName());
                }
            } else if (XProcConstants.NS_XPROC.equals(aname.getNamespaceURI())) {
                throw new XProcException(node, "Configuration error: attribute \"" + aname + "\" not allowed on " + node.getNodeName());
            }
            // Everything else is ok
        }

        return options;
    }

    private void checkBoolean(XdmNode node, String name, String value) {
        if (value != null && !"true".equals(value) && !"false".equals(value)) {
            throw new XProcException(node, "Configuration error: " + name + " on serialization must be 'true' or 'false'");
        }
    }

    private class ConfigDocument implements ReadablePipe {
        private String href = null;
        private String base = null;
        private Vector nodes = null;
        private boolean read = false;
        private XdmNode doc = null;
        private HashSet excludeUris = null;

        public ConfigDocument(String href, String base) {
            this.href = href;
            this.base = base;
        }

        public ConfigDocument(Vector nodes, HashSet excludeUris) {
            this.nodes = nodes;
            this.excludeUris = excludeUris;
        }

        public void canReadSequence(boolean sequence) {
            // nop; always false
        }

        public boolean readSequence() {
            return false;
        }

        public XdmNode read() throws SaxonApiException {
            read = true;

            if (doc != null) {
                return doc;
            }

            if (nodes != null) {
                // Find the document element so we can get the base URI
                XdmNode node = null;
                for (int pos = 0; pos < nodes.size() && node == null; pos++) {
                    if (((XdmNode) nodes.get(pos)).getNodeKind() == XdmNodeKind.ELEMENT) {
                        node = (XdmNode) nodes.get(pos);
                    }
                }

                XdmDestination dest = new XdmDestination();
                try {
                    S9apiUtils.writeXdmValue(cfgProcessor, nodes, dest, node.getBaseURI());
                    doc = dest.getXdmNode();
                    if (excludeUris.size() != 0) {
                        doc = S9apiUtils.removeNamespaces(cfgProcessor, doc, excludeUris, true);
                    }
                } catch (SaxonApiException sae) {
                    throw new XProcException(sae);
                }
            } else {
                doc = readXML(href, base);
            }

            return doc;
        }

        public void setReader(Step step) {
            // I don't care
        }

        public void setNames(String stepName, String portName) {
            // nop;
        }

        public void resetReader() {
            read = false;
        }

        public boolean moreDocuments() {
            return !read;
        }

        public boolean closed() {
            return false;
        }

        public int documentCount() {
            return 1;
        }

        public DocumentSequence documents() {
            throw new XProcException("You can't get the document sequence of an input from the config file!");
        }
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy