package com.bagri.xquery.saxon; import static com.bagri.core.Constants.*; import static com.bagri.xquery.saxon.SaxonUtils.*; import static javax.xml.xquery.XQConstants.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.Collection; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.TimeZone; import javax.xml.transform.Source; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xquery.XQException; import javax.xml.xquery.XQItem; import javax.xml.xquery.XQSequence; import javax.xml.xquery.XQStaticContext; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.InputSource; import com.bagri.core.api.SchemaRepository; import com.bagri.core.xquery.api.XQProcessorBase; import com.bagri.support.util.XMLUtils; import com.bagri.xquery.saxon.ext.doc.GetDocumentContent; import com.bagri.xquery.saxon.ext.doc.QueryDocumentUris; import com.bagri.xquery.saxon.ext.doc.RemoveCollectionDocuments; import com.bagri.xquery.saxon.ext.doc.RemoveDocument; import com.bagri.xquery.saxon.ext.doc.StoreDocument; import com.bagri.xquery.saxon.ext.http.HttpGet; import com.bagri.xquery.saxon.ext.tx.BeginTransaction; import com.bagri.xquery.saxon.ext.tx.CommitTransaction; import com.bagri.xquery.saxon.ext.tx.RollbackTransaction; import com.bagri.xquery.saxon.ext.util.GetUuid; import com.bagri.xquery.saxon.ext.util.LogOutput; import net.sf.saxon.Configuration; import net.sf.saxon.dom.DocumentOverNodeInfo; import net.sf.saxon.dom.NodeOverNodeInfo; import net.sf.saxon.expr.instruct.GlobalParameterSet; import net.sf.saxon.expr.instruct.GlobalVariable; import net.sf.saxon.lib.Logger; import net.sf.saxon.lib.StandardLogger; import net.sf.saxon.lib.Validation; import net.sf.saxon.om.DocumentInfo; import net.sf.saxon.om.Item; import net.sf.saxon.om.SequenceIterator; import net.sf.saxon.om.SequenceTool; import net.sf.saxon.om.StructuredQName; import net.sf.saxon.query.DynamicQueryContext; import net.sf.saxon.query.QueryResult; import net.sf.saxon.query.StaticQueryContext; import net.sf.saxon.query.XQueryExpression; import net.sf.saxon.trans.XPathException; import net.sf.saxon.value.DateTimeValue; import net.sf.saxon.value.EmptySequence; import net.sf.saxon.value.ObjectValue; public abstract class XQProcessorImpl extends XQProcessorBase { protected Configuration config; protected StaticQueryContext sqc; protected DynamicQueryContext dqc; protected Properties properties = new Properties(); public XQProcessorImpl() { config = Configuration.newConfiguration(); //config.setSchemaValidationMode(Validation.STRIP); //config.setConfigurationProperty(FeatureKeys.PRE_EVALUATE_DOC_FUNCTION, Boolean.TRUE); sqc = config.newStaticQueryContext(); // supported in Saxon-EE only //sqc.setUpdatingEnabled(true); dqc = new DynamicQueryContext(config); dqc.setApplyFunctionConversionRulesToExternalVariables(false); //sqc. cvr = new StandardObjectConverter(); //JPConverter.allocate(XQItem.class, null, config); } public String getProperty(String propName) { return properties.getProperty(propName); } public Properties getProperties() { return properties; } public void setProperties(Properties props) { this.properties.clear(); properties.putAll(props); try { setStaticContext(sqc, properties); } catch (XQException ex) { logger.error("setProperties.error", ex); } } public boolean isFeatureSupported(int feature) { switch (feature) { case xqf_Module: case xqf_Update: case xqf_Full_Axis: case xqf_Serialization: case xqf_Transaction: case xqf_XQuery_Encoding_Decl: return true; case xqf_XQueryX: case xqf_Schema_Import: case xqf_Schema_Validation: case xqf_User_Defined_XML_Schema_Type: // next two not supported by Saxon itself :( case xqf_Static_Typing: case xqf_Static_Typing_Extensions: // next four from MetaData2 interface case xqf_XQuery_Update_Facility: case xqf_XQuery_Full_Text: case xqf_XQuery_30: case xqf_XA: return false; } return false; } public boolean isQueryReadOnly(final String query) throws XQException { return (query.indexOf(bg_remove_cln_documents) < 0) && (query.indexOf(bg_remove_document) < 0) && (query.indexOf(bg_store_document) < 0); } protected void setStaticContext(StaticQueryContext sqc, XQStaticContext ctx) throws XQException { // !! sqc.setSchemaAware(false); sqc.setBaseURI(ctx.getBaseURI()); //ctx.getBindingMode() sqc.setPreserveBoundarySpace(ctx.getBoundarySpacePolicy() == BOUNDARY_SPACE_PRESERVE); if (ctx.getConstructionMode() == CONSTRUCTION_MODE_PRESERVE) { sqc.setConstructionMode(Validation.PRESERVE); } else { sqc.setConstructionMode(Validation.STRIP); } // ctx.getContextItemStaticType() -> contextItemStaticType //if (contextItemStaticType != null) { // sqc.setRequiredContextItemType(contextItemStaticType.getSaxonItemType()); //} sqc.setInheritNamespaces(ctx.getCopyNamespacesModeInherit() == COPY_NAMESPACES_MODE_INHERIT); sqc.setPreserveNamespaces(ctx.getCopyNamespacesModePreserve() == COPY_NAMESPACES_MODE_PRESERVE); sqc.declareDefaultCollation(ctx.getDefaultCollation()); sqc.setDefaultElementNamespace(ctx.getDefaultElementTypeNamespace()); sqc.setDefaultFunctionNamespace(ctx.getDefaultFunctionNamespace()); sqc.setEmptyLeast(ctx.getDefaultOrderForEmptySequences() == DEFAULT_ORDER_FOR_EMPTY_SEQUENCES_LEAST); sqc.clearNamespaces(); String[] prefixes = ctx.getNamespacePrefixes(); for (String prefix: prefixes) { sqc.declareNamespace(prefix, ctx.getNamespaceURI(prefix)); } //ctx.getHoldability() //ctx.getOrderingMode() if (ctx.getQueryLanguageTypeAndVersion() == LANGTYPE_XQUERY) { sqc.setLanguageVersion(saxon_xquery_version); } //ctx.getQueryTimeout() //ctx.getScrollability() //config.setConfigurationProperty(FeatureKeys.PRE_EVALUATE_DOC_FUNCTION, Boolean.TRUE); } protected void setStaticContext(StaticQueryContext sqc, Properties props) throws XQException { logger.trace("setStaticContext.enter; got props: {}", props); // !! sqc.setSchemaAware(false); String value = props.getProperty(pn_xqj_baseURI); if (value != null && !value.isEmpty()) { sqc.setBaseURI(value); } //props.getProperty(pn_bindingMode) value = props.getProperty(pn_xqj_boundarySpacePolicy); if (value != null) { sqc.setPreserveBoundarySpace(String.valueOf(BOUNDARY_SPACE_PRESERVE).equals(value)); } value = props.getProperty(pn_xqj_constructionMode); if (value != null) { if (String.valueOf(CONSTRUCTION_MODE_PRESERVE).equals(value)) { sqc.setConstructionMode(Validation.PRESERVE); } else { sqc.setConstructionMode(Validation.STRIP); } } // ctx.getContextItemStaticType() -> contextItemStaticType //if (contextItemStaticType != null) { // sqc.setRequiredContextItemType(contextItemStaticType.getSaxonItemType()); //} value = props.getProperty(pn_xqj_defaultCollationUri); if (value != null) { sqc.declareDefaultCollation(value); } value = props.getProperty(pn_xqj_defaultElementTypeNamespace); if (value != null) { sqc.setDefaultElementNamespace(value); } value = props.getProperty(pn_xqj_defaultFunctionNamespace); if (value != null) { sqc.setDefaultFunctionNamespace(value); } value = props.getProperty(pn_xqj_defaultOrderForEmptySequences); if (value != null) { sqc.setEmptyLeast(String.valueOf(DEFAULT_ORDER_FOR_EMPTY_SEQUENCES_LEAST).equals(value)); } value = props.getProperty(pn_xqj_copyNamespacesModeInherit); if (value != null) { sqc.setInheritNamespaces(String.valueOf(COPY_NAMESPACES_MODE_INHERIT).equals(value)); } value = props.getProperty(pn_xqj_copyNamespacesModePreserve); if (value != null) { sqc.setPreserveNamespaces(String.valueOf(COPY_NAMESPACES_MODE_PRESERVE).equals(value)); } value = props.getProperty(pn_xqj_defaultNamespaces); if (value != null) { sqc.clearNamespaces(); StringTokenizer st = new StringTokenizer(value, " "); while (st.hasMoreTokens()) { String namespace = st.nextToken(); int idx = namespace.indexOf(":"); sqc.declareNamespace(namespace.substring(0, idx), namespace.substring(idx + 1)); } } //props.getProperty(pn_holdability) //props.getProperty(pn_orderingMode) value = props.getProperty(pn_xqj_queryLanguageTypeAndVersion); if (value != null) { if (String.valueOf(LANGTYPE_XQUERY).equals(value)) { sqc.setLanguageVersion(saxon_xquery_version); } } //props.getProperty(pn_queryTimeout) //props.getProperty(pn_scrollability) if (logger.isTraceEnabled()) { StringBuilder ns = new StringBuilder(); Iterator<String> itr = sqc.iterateDeclaredPrefixes(); while (itr.hasNext()) { String prefix = itr.next(); ns.append(prefix).append(":").append(sqc.getNamespaceForPrefix(prefix)).append(" "); } if (ns.length() > 1) { ns.deleteCharAt(ns.length() - 1); } logger.trace("setStaticContext.exit; built context: [baseURI: {}; preserveBoundarySpace: {}; constructionMode: {}; defaultCollationName: {}; " + "defaultElementTypeNamespace: {}; defaultFunctionNamespace: {}; emptyLeast: {}; inheritNamespaces: {}; " + "preserveNamespaces: {}; defaultNamespaces: {}; queryLanguageTypeAndVersion: {}]", sqc.getBaseURI(), sqc.isPreserveBoundarySpace(), sqc.getConstructionMode(), sqc.getDefaultCollationName(), sqc.getDefaultElementNamespace(), sqc.getDefaultFunctionNamespace(), sqc.isEmptyLeast(), sqc.isInheritNamespaces(), sqc.isPreserveNamespaces(), ns.toString(), sqc.getLanguageVersion()); } } @Override public void setRepository(SchemaRepository xRepo) { super.setRepository(xRepo); config.registerExtensionFunction(new GetUuid()); config.registerExtensionFunction(new LogOutput()); config.registerExtensionFunction(new HttpGet()); config.registerExtensionFunction(new GetDocumentContent(xRepo.getDocumentManagement())); config.registerExtensionFunction(new RemoveDocument(xRepo.getDocumentManagement())); config.registerExtensionFunction(new StoreDocument(xRepo.getDocumentManagement())); config.registerExtensionFunction(new RemoveCollectionDocuments(xRepo.getDocumentManagement())); config.registerExtensionFunction(new QueryDocumentUris(xRepo.getQueryManagement())); config.registerExtensionFunction(new BeginTransaction(xRepo.getTxManagement())); config.registerExtensionFunction(new CommitTransaction(xRepo.getTxManagement())); config.registerExtensionFunction(new RollbackTransaction(xRepo.getTxManagement())); if (xRepo instanceof com.bagri.core.server.api.SchemaRepository) { logger.debug("setRepository; registering extensions"); XQCompilerImpl.registerExtensions(config, ((com.bagri.core.server.api.SchemaRepository) xRepo).getLibraries()); } else { logger.debug("setRepository; client side repo - has no access to extensions"); } } public Document convertToDocument(String xml) throws XQException { String baseURI = sqc.getBaseURI(); StringReader sr = new StringReader(xml); InputSource is = new InputSource(sr); is.setSystemId(baseURI); Source source = new SAXSource(is); source.setSystemId(baseURI); try { DocumentInfo doc = config.buildDocument(source); return (Document) DocumentOverNodeInfo.wrap(doc); } catch (XPathException ex) { throw new XQException(ex.getMessage()); } } public String convertToString(Object item, Properties props) throws XQException { if (item instanceof NodeOverNodeInfo) { try { if (props == null) { return QueryResult.serialize(((NodeOverNodeInfo) item).getUnderlyingNodeInfo()); } else { Writer writer = new StringWriter(); QueryResult.serialize(((NodeOverNodeInfo) item).getUnderlyingNodeInfo(), new StreamResult(writer), props); writer.close(); return writer.toString(); } } catch (IOException | XPathException ex) { throw new XQException(ex.getMessage()); } } else if (item instanceof Node) { try { return XMLUtils.nodeToString((Node) item, props); } catch (Exception ex) { throw new XQException(ex.getMessage()); } } else if (item instanceof ObjectValue) { return convertToString(((ObjectValue) item).getObject(), props); } else if (item instanceof XQSequence) { //return ((XQSequence) item).getSequenceAsString(props); Writer writer = new StringWriter(); SequenceIterator itr = new XQSequenceIterator((XQSequence) item, config); try { QueryResult.serializeSequence(itr, config, writer, props); writer.close(); } catch (IOException | XPathException ex) { throw new XQException(ex.getMessage()); } return writer.toString(); } else if (item instanceof XQItem) { return ((XQItem) item).getItemAsString(props); } else { return item.toString(); } } //@Override public void bindVariable(String varName, Object var) throws XQException { try { if (var == null) { dqc.setParameter(getStructuredQName(varName), EmptySequence.getInstance()); } else { Item item; if (var instanceof XQItem) { item = convertXQItem((XQItem) var, config); } else { item = objectToItem(var, config); } dqc.setParameter(getStructuredQName(varName), item); } } catch (XPathException ex) { throw new XQException(ex.getMessage()); } } //@Override public void unbindVariable(String varName) throws XQException { dqc.setParameter(getStructuredQName(varName), null); } private static StructuredQName getStructuredQName(String vName) { //return new StructuredQName(qname.getPrefix(), qname.getNamespaceURI(), qname.getLocalPart()); return StructuredQName.fromClarkName(vName); } // why it is not <QName, Object> ?? // because it is used in QueryBuilder where params identified by plain Strings protected Map<String, Object> getObjectParams() throws XPathException { GlobalParameterSet params = dqc.getParameters(); Map<String, Object> bindings = new HashMap<>(params.getNumberOfKeys()); for (StructuredQName name: params.getKeys()) { Object value = params.get(name); if (value instanceof EmptySequence) { value = null; } else { value = itemToObject((Item) value); //value = SequenceTool.convertToJava((Item) value); } bindings.put(name.getClarkName(), value); logger.trace("getParams; name: {}; value: {}", name, value); } return bindings; } protected String explainQuery(XQueryExpression exp) throws XPathException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); Logger log = new StandardLogger(ps); exp.getExpression().explain(log); String res = new String(baos.toByteArray(), Charset.defaultCharset()); log.close(); ps.close(); try { baos.close(); } catch (IOException ex) { throw new XPathException(ex); } return res; } // this is for test only? public void parseXQuery(String query) throws XQException { try { final XQueryExpression exp = sqc.compileQuery(query); // why do we do this? to populate dqc with params?? List results = exp.evaluate(dqc); for (Object result: results) { logger.trace("result: {}; class: {}", result, result.getClass().getName()); } } catch (XPathException ex) { logger.error("parseXQuery.error: ", ex); throw new XQException(ex.getMessage()); } } //@Override public Collection<String> prepareXQuery(String query, XQStaticContext ctx) throws XQException { setStaticContext(sqc, ctx); try { final XQueryExpression exp = sqc.compileQuery(query); if (logger.isTraceEnabled()) { logger.trace("prepareXQuery; query: \n{}", explainQuery(exp)); } Set<String> result = new HashSet<>(); Iterator<GlobalVariable> itr = exp.getMainModule().getModuleVariables(); while (itr.hasNext()) { result.add(itr.next().getVariableQName().getClarkName()); } return result; } catch (XPathException ex) { logger.error("prepareXQuery.error: ", ex); throw new XQException(ex.getMessage()); } } public void setTimeZone(TimeZone timeZone) throws XQException { GregorianCalendar now = new GregorianCalendar(timeZone); try { dqc.setCurrentDateTime(new DateTimeValue(now, true)); } catch (XPathException ex) { throw new XQException(ex.getMessage()); } } }