package com.bagri.xquery.saxon; import static com.bagri.core.Constants.*; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import javax.xml.xquery.XQException; import javax.xml.xquery.XQItemAccessor; import javax.xml.xquery.XQQueryException; import javax.xml.xquery.XQStaticContext; import com.bagri.core.api.DocumentManagement; import com.bagri.core.api.ResultCursor; import com.bagri.core.api.SchemaRepository; import com.bagri.core.api.BagriException; import com.bagri.core.model.Document; import com.bagri.core.model.Query; import com.bagri.core.query.QueryBuilder; import com.bagri.core.server.api.QueryManagement; import com.bagri.core.xquery.api.XQProcessor; import net.sf.saxon.expr.Expression; import net.sf.saxon.expr.Operand; import net.sf.saxon.expr.UserFunctionCall; import net.sf.saxon.functions.IntegratedFunctionCall; import net.sf.saxon.lib.ModuleURIResolver; import net.sf.saxon.om.NamePool; import net.sf.saxon.om.SequenceIterator; import net.sf.saxon.query.XQueryExpression; import net.sf.saxon.trans.XPathException; import net.sf.saxon.tree.util.DocumentNumberAllocator; public class XQProcessorServer extends XQProcessorImpl implements XQProcessor { private ResultCursor cursor; private CollectionFinderImpl clnFinder; // local cache for XQueryExpressions. // may be make it static, synchronized? for XQProcessorServer instances.. private Map<Integer, XQueryExpression> queries = new HashMap<>(); private static NamePool defNamePool = new NamePool(); private static DocumentNumberAllocator defDocNumberAllocator = new DocumentNumberAllocator(); public XQProcessorServer() { super(); config.setNamePool(defNamePool); config.setDocumentNumberAllocator(defDocNumberAllocator); } public XQProcessorServer(SchemaRepository xRepo) { this(); logger.trace("<init>; got Repo: {}", xRepo); setRepository(xRepo); } @Override public void cancelExecution() throws XQException { logger.info("cancelExecution; not implemented on the server side."); } @Override public void setRepository(SchemaRepository xRepo) { super.setRepository(xRepo); clnFinder = new CollectionFinderImpl((com.bagri.core.server.api.SchemaRepository) xRepo); config.setCollectionFinder(clnFinder); config.setDefaultCollection(""); SourceResolverImpl sResolver = new SourceResolverImpl(xRepo); config.setSourceResolver(sResolver); //config.registerExternalObjectModel(sResolver); config.setURIResolver(sResolver); ModuleURIResolver mResolver = new ModuleURIResolverImpl((com.bagri.core.server.api.SchemaRepository) xRepo); config.setModuleURIResolver(mResolver); dqc.setUnparsedTextURIResolver(sResolver); //sqc.setCodeInjector(new CodeInjectorImpl()); } @Override public Iterator<Object> executeXCommand(String command, Map<String, Object> bindings, XQStaticContext ctx) throws XQException { //setStaticContext(sqc, ctx); return executeXCommand(command, bindings, (Properties) null); } @Override public Iterator<Object> executeXCommand(String command, Map<String, Object> params, Properties props) throws XQException { // TODO: rewrite it (use client impl)!? // and think about command results!.. DocumentManagement dMgr = getRepository().getDocumentManagement(); try { if (command.startsWith("storeDocument")) { XQItemAccessor item = getBoundItem(params, "uri"); String uri = item.getAtomicValue(); item = getBoundItem(params, "doc"); String xml = item.getItemAsString(null); // validate document ? // add/pass other params ?! Document doc = dMgr.storeDocumentFromString(uri, xml, null); List<Object> result = new ArrayList<>(1); result.add(doc); return result.iterator(); } else if (command.startsWith("removeDocument")) { XQItemAccessor item = getBoundItem(params, "uri"); String uri = item.getAtomicValue(); dMgr.removeDocument(uri); return Collections.emptyIterator(); } else { throw new XQException("unknown command: " + command); } } catch (BagriException ex) { throw new XQException(ex.getMessage()); } } private Iterator<Object> execQuery(final String query) throws XQException { logger.trace("execQuery.enter; this: {}", this); long stamp = System.currentTimeMillis(); QueryManagement qMgr = (QueryManagement) getQueryManagement(); Query xQuery = qMgr.getQuery(query); Integer qKey = qMgr.getQueryKey(query); try { XQueryExpression xqExp = getXQuery(qKey, query, null); Map<String, Object> params = getObjectParams(); if (xQuery == null) { clnFinder.setQuery(null); } else { QueryBuilder xdmQuery = xQuery.getXdmQuery(); if (!(params == null || params.isEmpty())) { xdmQuery.resetParams(params); } clnFinder.setQuery(xdmQuery); } clnFinder.setExpression(xqExp); stamp = System.currentTimeMillis() - stamp; logger.trace("execQuery; xQuery: {}; params: {}; time taken: {}", xQuery, params, stamp); stamp = System.currentTimeMillis(); SequenceIterator itr = xqExp.iterator(dqc); //Result r = new StreamResult(); //xqExp.run(dqc, r, null); stamp = System.currentTimeMillis() - stamp; logger.trace("execQuery.exit; time taken: {}", stamp); return new XQIterator(getXQDataFactory(), itr); } catch (XPathException xpe) { logger.error("execQuery.error: ", xpe); throw convertXPathException(xpe); } } @Override public Iterator<Object> executeXQuery(String query, Properties props) throws XQException { setStaticContext(sqc, props); return execQuery(query); } @Override public ResultCursor executeXQuery(String query, XQStaticContext ctx) throws XQException { // implement it? what for..? throw new XQException("Not implemented on the server side. Use another executeXQuery method taking Properties as a parameter instead"); } @Override public Query getCurrentQuery(final String query) throws XQException { if (clnFinder.getQuery() == null) { // not 'collection' query? // TODO: yes, fix it for updating query! return null; } return new Query(query, isQueryReadOnly(query), clnFinder.getQuery()); } @Override public ResultCursor getResults() { return cursor; } @Override public void setResults(ResultCursor cursor) { this.cursor = cursor; } @Override public boolean isQueryReadOnly(final String query, Properties props) throws XQException { boolean result = super.isQueryReadOnly(query); if (result) { int qKey = getQueryManagement().getQueryKey(query); XQueryExpression xqExp; try { xqExp = getXQuery(qKey, query, props); } catch (XPathException xpe) { logger.error("isQueryReadOnly.error: ", xpe); throw convertXPathException(xpe); } result = !isUpdatingExpression(xqExp.getExpression()); } return result; } @Override public String toString() { return "XQProcessorServer[" + getRepository().getClientId() + "]"; } private XQException convertXPathException(XPathException xpe) { XQException xqe; if (xpe.getErrorCodeQName() == null) { xqe = new XQException(xpe.getMessage()); } else { if (xpe.getLocator() == null) { xqe = new XQQueryException(xpe.getMessage(), xpe.getErrorCodeQName().toJaxpQName()); } else { xqe = new XQQueryException(xpe.getMessage(), xpe.getErrorCodeQName().toJaxpQName(), xpe.getLocator().getLineNumber(), xpe.getLocator().getColumnNumber(), 0); } } return xqe; } private XQItemAccessor getBoundItem(Map<String, Object> bindings, String varName) throws XQException { if (bindings.size() == 0) { throw new XQException("bindings not provided"); } XQItemAccessor item = (XQItemAccessor) bindings.get(varName); if (item == null) { throw new XQException("variable '" + varName + "' not bound"); } return item; } private XQueryExpression getXQuery(int queryKey, String query, Properties props) throws XPathException, XQException { XQueryExpression xqExp = queries.get(queryKey); if (xqExp == null) { if (props != null) { setStaticContext(sqc, props); } sqc.setModuleURIResolver(config.getModuleURIResolver()); xqExp = sqc.compileQuery(query); if (logger.isTraceEnabled()) { logger.trace("getXQuery; query: \n{}; \nexpression: {}", explainQuery(xqExp), xqExp.getExpression().getExpressionName()); } queries.put(queryKey, xqExp); } return xqExp; } private boolean isUpdatingExpression(Expression ex) { //logger.trace("isUpdatingExpression; got ex: {}; {}", ex.getClass().getName(), ex); if (ex.isUpdatingExpression()) { logger.debug("isUpdatingExpression; got updating ex: {}", ex); return true; } if (ex instanceof IntegratedFunctionCall) { String qName = ex.getExpressionName(); if (bg_remove_document.equals(qName) || bg_remove_cln_documents.equals(qName) || bg_store_document.equals(qName)) { logger.trace("isUpdatingExpression; got updating UDF: {}", qName); return true; } } else if (ex instanceof UserFunctionCall) { UserFunctionCall ufc = (UserFunctionCall) ex; ex = ufc.getFunction().getBody(); } Iterator<Operand> itr = ex.operands().iterator(); while(itr.hasNext()) { Expression e = itr.next().getChildExpression(); if (isUpdatingExpression(e)) { return true; } } return false; } }