/** * 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.transformer; import org.apache.log4j.Logger; import org.orbeon.dom.Document; import org.orbeon.dom.io.DocumentSource; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.util.XPath; import org.orbeon.oxf.xml.XMLParsing; import org.orbeon.oxf.xml.XMLReceiver; import org.orbeon.oxf.processor.*; import org.orbeon.oxf.processor.transformer.xslt.StringErrorListener; import org.orbeon.oxf.processor.transformer.xslt.XSLTTransformer; import org.orbeon.oxf.properties.PropertySet; import org.orbeon.oxf.properties.PropertyStore; import org.orbeon.oxf.xml.XMLConstants; import org.orbeon.oxf.xml.dom4j.Dom4jUtils; import org.orbeon.saxon.Configuration; import org.orbeon.saxon.query.DynamicQueryContext; import org.orbeon.saxon.query.StaticQueryContext; import org.orbeon.saxon.query.XQueryExpression; import javax.xml.transform.URIResolver; import javax.xml.transform.sax.SAXResult; import java.util.Collections; import java.util.Map; /** * XQuery processor based on the Saxon engine. * * TODO: should work like the XSLT processor, and handle: * * - caching [BE CAREFUL WITH NOT CACHING TransformerURIResolver!] * - errors * - additional inputs * - etc. * * To get there, should maybe abstract what's in XSLT processor and derive from it here. */ public class SaxonXQueryProcessor extends ProcessorImpl { private static Logger logger = Logger.getLogger(SaxonXQueryProcessor.class); // This input determines attributes to set on the Configuration private static final String INPUT_ATTRIBUTES = "attributes"; public SaxonXQueryProcessor() { addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG)); addInputInfo(new ProcessorInputOutputInfo(INPUT_ATTRIBUTES, XSLTTransformer.XSLT_PREFERENCES_CONFIG_NAMESPACE_URI)); addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA)); } @Override public ProcessorOutput createOutput(String name) { final ProcessorOutput output = new ProcessorOutputImpl(SaxonXQueryProcessor.this, name) { public void readImpl(final PipelineContext pipelineContext, XMLReceiver xmlReceiver) { try { final Document dataDocument = readInputAsOrbeonDom(pipelineContext, INPUT_DATA); // Create XQuery configuration (depends on attributes input) final URIResolver uriResolver = new TransformerURIResolver(SaxonXQueryProcessor.this, pipelineContext, INPUT_DATA, XMLParsing.ParserConfiguration.PLAIN); // TODO: once caching is in place, make sure cached object does not contain a reference to the URIResolver final Configuration configuration = XPath.newConfiguration(); { configuration.setErrorListener(new StringErrorListener(logger)); // 2007-07-05 MK says: "fetching of query modules is done by the ModuleURIResolver in the // static context, fetching of doc() is done by the URIResolver in the dynamic context; the // URIResolver in the Configuration is just a fallback." // config.setURIResolver(uriResolver); // Read attributes final Map<String, Boolean> attributesFromProperties; { // Read attributes input only if connected if (getConnectedInputs().get(INPUT_ATTRIBUTES) != null) { // Read input as an attribute Map and cache it attributesFromProperties = readCacheInputAsObject(pipelineContext, getInputByName(INPUT_ATTRIBUTES), new CacheableInputReader<Map<String, Boolean>>() { public Map<String, Boolean> read(PipelineContext context, ProcessorInput input) { final Document preferencesDocument = readInputAsOrbeonDom(context, input); final PropertyStore propertyStore = new PropertyStore(preferencesDocument); final PropertySet propertySet = propertyStore.getGlobalPropertySet(); return propertySet.getBooleanProperties(); } }); } else attributesFromProperties = Collections.emptyMap(); } // Set configuration attributes if any for (Map.Entry<String, Boolean> entry: attributesFromProperties.entrySet()) configuration.setConfigurationProperty(entry.getKey(), entry.getValue()); } // Create static context final StaticQueryContext staticContext = new StaticQueryContext(configuration); // Create XQuery expression (depends on config input and static context) // TODO: caching of query must also depend on attributes input XQueryExpression xqueryExpression = readCacheInputAsObject(pipelineContext, getInputByName(INPUT_CONFIG), new CacheableInputReader<XQueryExpression>() { public XQueryExpression read(PipelineContext context, ProcessorInput input) { try { // Read XQuery into String final Document xqueryDocument = readCacheInputAsDOM4J(pipelineContext, INPUT_CONFIG); String xqueryBody; if (XMLConstants.XS_STRING_QNAME.equals(Dom4jUtils.extractAttributeValueQName(xqueryDocument.getRootElement(), XMLConstants.XSI_TYPE_QNAME, false))) { // Content is text under an XML root element xqueryBody = xqueryDocument.getRootElement().getStringValue(); } else { // Content is XQuery embedded into XML xqueryBody = Dom4jUtils.domToString(xqueryDocument); xqueryBody = xqueryBody.substring(xqueryBody.indexOf(">") + 1); xqueryBody = xqueryBody.substring(0, xqueryBody.lastIndexOf("<")); } // 2007-07-05 MK says: "fetching of query modules is done by the ModuleURIResolver in the // static context, fetching of doc() is done by the URIResolver in the dynamic context; the // URIResolver in the Configuration is just a fallback." // Clear URI resolver from static context as this must not end up in the cache // staticContext.getConfiguration().setURIResolver(null); return staticContext.compileQuery(xqueryBody); } catch (Exception e) { throw new OXFException(e); } } }); // Create dynamic context and run query final DynamicQueryContext dynamicContext = new DynamicQueryContext(configuration); dynamicContext.setContextItem(staticContext.buildDocument(new DocumentSource(dataDocument))); dynamicContext.setURIResolver(uriResolver); // TODO: use xqueryExpression.getStaticContext() when Saxon is upgraded final SAXResult saxResult = new SAXResult(xmlReceiver); saxResult.setLexicalHandler(xmlReceiver); xqueryExpression.run(dynamicContext, saxResult, new java.util.Properties()); } catch (Exception e) { throw new OXFException(e); } } }; addOutput(name, output); return output; } }