/* Copyright 2004 The Apache Software Foundation | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.xmlbeans.impl.xpath.saxon; | |
import net.sf.saxon.Configuration; | |
import net.sf.saxon.dom.DocumentWrapper; | |
import net.sf.saxon.dom.NodeOverNodeInfo; | |
import net.sf.saxon.ma.map.HashTrieMap; | |
import net.sf.saxon.om.Item; | |
import net.sf.saxon.om.NodeInfo; | |
import net.sf.saxon.om.StructuredQName; | |
import net.sf.saxon.query.DynamicQueryContext; | |
import net.sf.saxon.query.StaticQueryContext; | |
import net.sf.saxon.query.XQueryExpression; | |
import net.sf.saxon.type.BuiltInAtomicType; | |
import net.sf.saxon.value.*; | |
import org.apache.logging.log4j.LogManager; | |
import org.apache.logging.log4j.Logger; | |
import org.apache.xmlbeans.*; | |
import org.apache.xmlbeans.impl.store.Cur; | |
import org.apache.xmlbeans.impl.store.Cursor; | |
import org.apache.xmlbeans.impl.store.Locale; | |
import org.apache.xmlbeans.impl.xpath.XQuery; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.Node; | |
import javax.xml.datatype.DatatypeConstants; | |
import javax.xml.datatype.Duration; | |
import javax.xml.datatype.XMLGregorianCalendar; | |
import javax.xml.namespace.QName; | |
import javax.xml.transform.TransformerException; | |
import javax.xml.xpath.XPathException; | |
import java.math.BigDecimal; | |
import java.math.BigInteger; | |
import java.net.URI; | |
import java.util.Date; | |
import java.util.List; | |
import java.util.ListIterator; | |
import java.util.Map; | |
public class SaxonXQuery implements XQuery { | |
private static final Logger LOG = LogManager.getLogger(SaxonXQuery.class); | |
private final XQueryExpression xquery; | |
private final String contextVar; | |
private final Configuration config; | |
private Cur _cur; | |
private long _version; | |
private XmlOptions _options; | |
/** | |
* Construct given an XQuery expression string. | |
* | |
* @param query The XQuery expression | |
* @param contextVar The name of the context variable | |
* @param boundary The offset of the end of the prolog | |
*/ | |
public SaxonXQuery(final String query, String contextVar, Integer boundary, XmlOptions xmlOptions) { | |
assert !(contextVar.startsWith(".") || contextVar.startsWith("..")); | |
_options = xmlOptions; | |
config = new Configuration(); | |
StaticQueryContext sc = config.newStaticQueryContext(); | |
Map<String, String> nsMap = xmlOptions.getLoadAdditionalNamespaces(); | |
if (nsMap != null) { | |
nsMap.forEach(sc::declareNamespace); | |
} | |
this.contextVar = contextVar; | |
//Saxon requires external variables at the end of the prolog... | |
try { | |
xquery = sc.compileQuery( | |
query.substring(0, boundary) + " declare variable $" + contextVar + " external;" + query.substring(boundary) | |
); | |
} catch (TransformerException e) { | |
throw new XmlRuntimeException(e); | |
} | |
} | |
public XmlObject[] objectExecute(Cur c, XmlOptions options) { | |
_version = c.getLocale().version(); | |
_cur = c.weakCur(this); | |
this._options = options; | |
Map<String, Object> bindings = XmlOptions.maskNull(_options).getXqueryVariables(); | |
List<Object> resultsList = execQuery(_cur.getDom(), bindings); | |
XmlObject[] result = new XmlObject[resultsList.size()]; | |
for (int i = 0; i < resultsList.size(); i++) { | |
//copy objects into the locale | |
Locale l = Locale.getLocale(_cur.getLocale().getSchemaTypeLoader(), _options); | |
l.enter(); | |
Object node = resultsList.get(i); | |
Cur res; | |
try { | |
//typed function results of XQuery | |
if (!(node instanceof Node)) { | |
res = l.load("<xml-fragment/>").tempCur(); | |
res.setValue(node.toString()); | |
SchemaType type = getType(node); | |
Locale.autoTypeDocument(res, type, null); | |
result[i] = res.getObject(); | |
} else { | |
res = loadNode(l, (Node) node); | |
} | |
result[i] = res.getObject(); | |
} catch (XmlException e) { | |
throw new RuntimeException(e); | |
} finally { | |
l.exit(); | |
} | |
res.release(); | |
} | |
release(); | |
return result; | |
} | |
public XmlCursor cursorExecute(Cur c, XmlOptions options) { | |
_version = c.getLocale().version(); | |
_cur = c.weakCur(this); | |
this._options = options; | |
Map<String, Object> bindings = XmlOptions.maskNull(_options).getXqueryVariables(); | |
List<Object> resultsList = execQuery(_cur.getDom(), bindings); | |
int i; | |
Locale locale = Locale.getLocale(_cur.getLocale().getSchemaTypeLoader(), _options); | |
locale.enter(); | |
Locale.LoadContext _context = new Cur.CurLoadContext(locale, _options); | |
Cursor resultCur = null; | |
try { | |
for (i = 0; i < resultsList.size(); i++) { | |
loadNodeHelper(locale, (Node) resultsList.get(i), _context); | |
} | |
Cur c2 = _context.finish(); | |
Locale.associateSourceName(c, _options); | |
Locale.autoTypeDocument(c, null, _options); | |
resultCur = new Cursor(c2); | |
} catch (XmlException e) { | |
LOG.atInfo().withThrowable(e).log("Can't autotype document"); | |
} finally { | |
locale.exit(); | |
} | |
release(); | |
return resultCur; | |
} | |
public List<Object> execQuery(Object node, Map<String,Object> variableBindings) { | |
try { | |
Node contextNode = (Node) node; | |
Document dom = (contextNode.getNodeType() == Node.DOCUMENT_NODE) | |
? (Document) contextNode : contextNode.getOwnerDocument(); | |
DocumentWrapper docWrapper = new DocumentWrapper(dom, null, config); | |
NodeInfo root = docWrapper.wrap(contextNode); | |
DynamicQueryContext dc = new DynamicQueryContext(config); | |
dc.setContextItem(root); | |
dc.setParameter(new StructuredQName("", null, contextVar), root); | |
// Set the other variables | |
if (variableBindings != null) { | |
for (Map.Entry<String, Object> me : variableBindings.entrySet()) { | |
StructuredQName key = new StructuredQName("", null, me.getKey()); | |
Object value = me.getValue(); | |
if (value instanceof XmlTokenSource) { | |
Node paramObject = ((XmlTokenSource) value).getDomNode(); | |
dc.setParameter(key, docWrapper.wrap(paramObject)); | |
} else { | |
try { | |
dc.setParameter(key, objectToItem(value, config)); | |
} catch (XPathException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
} | |
List<Object> saxonNodes = xquery.evaluate(dc); | |
for (ListIterator<Object> it = saxonNodes.listIterator(); it.hasNext(); ) { | |
Object o = it.next(); | |
if (o instanceof NodeInfo) { | |
Node n = NodeOverNodeInfo.wrap((NodeInfo) o); | |
it.set(n); | |
} | |
} | |
return saxonNodes; | |
} catch (TransformerException e) { | |
throw new RuntimeException("Error binding " + contextVar, e); | |
} | |
} | |
private static Item objectToItem(Object value, Configuration config) throws XPathException, net.sf.saxon.trans.XPathException { | |
if (value == null) { | |
return null; | |
} | |
// convert to switch.. | |
if (value instanceof Boolean) { | |
return BooleanValue.get((Boolean) value); | |
} else if (value instanceof byte[]) { | |
return new HexBinaryValue((byte[]) value); | |
} else if (value instanceof Byte) { | |
return new Int64Value((Byte) value, BuiltInAtomicType.BYTE, false); | |
} else if (value instanceof Float) { | |
return new FloatValue((Float) value); | |
} else if (value instanceof Double) { | |
return new DoubleValue((Double) value); | |
} else if (value instanceof Integer) { | |
return new Int64Value((Integer) value, BuiltInAtomicType.INT, false); | |
} else if (value instanceof Long) { | |
return new Int64Value((Long) value, BuiltInAtomicType.LONG, false); | |
} else if (value instanceof Short) { | |
return new Int64Value((Short) value, BuiltInAtomicType.SHORT, false); | |
} else if (value instanceof String) { | |
return new StringValue((String) value); | |
} else if (value instanceof BigDecimal) { | |
return new BigDecimalValue((BigDecimal) value); | |
} else if (value instanceof BigInteger) { | |
return new BigIntegerValue((BigInteger) value); | |
} else if (value instanceof SaxonDuration) { | |
return ((SaxonDuration) value).getDurationValue(); | |
} else if (value instanceof Duration) { | |
// this is simpler and safer (but perhaps slower) than extracting all the components | |
//return DurationValue.makeDuration(value.toString()).asAtomic(); | |
Duration dv = (Duration) value; | |
return new DurationValue(dv.getSign() >= 0, dv.getYears(), dv.getMonths(), dv.getDays(), | |
dv.getHours(), dv.getMinutes(), dv.getSeconds(), 0); // take correct millis.. | |
} else if (value instanceof SaxonXMLGregorianCalendar) { | |
return ((SaxonXMLGregorianCalendar) value).toCalendarValue(); | |
} else if (value instanceof XMLGregorianCalendar) { | |
XMLGregorianCalendar g = (XMLGregorianCalendar) value; | |
QName gtype = g.getXMLSchemaType(); | |
if (gtype.equals(DatatypeConstants.DATETIME)) { | |
return DateTimeValue.makeDateTimeValue(value.toString(), config.getConversionRules()).asAtomic(); | |
} else if (gtype.equals(DatatypeConstants.DATE)) { | |
return DateValue.makeDateValue(value.toString(), config.getConversionRules()).asAtomic(); | |
} else if (gtype.equals(DatatypeConstants.TIME)) { | |
return TimeValue.makeTimeValue(value.toString()).asAtomic(); | |
} else if (gtype.equals(DatatypeConstants.GYEAR)) { | |
return GYearValue.makeGYearValue(value.toString(), config.getConversionRules()).asAtomic(); | |
} else if (gtype.equals(DatatypeConstants.GYEARMONTH)) { | |
return GYearMonthValue.makeGYearMonthValue(value.toString(), config.getConversionRules()).asAtomic(); | |
} else if (gtype.equals(DatatypeConstants.GMONTH)) { | |
// a workaround for W3C schema bug | |
String val = value.toString(); | |
if (val.endsWith("--")) { | |
val = val.substring(0, val.length() - 2); | |
} | |
return GMonthValue.makeGMonthValue(val).asAtomic(); | |
} else if (gtype.equals(DatatypeConstants.GMONTHDAY)) { | |
return GMonthDayValue.makeGMonthDayValue(value.toString()).asAtomic(); | |
} else if (gtype.equals(DatatypeConstants.GDAY)) { | |
return GDayValue.makeGDayValue(value.toString()).asAtomic(); | |
} else { | |
throw new AssertionError("Unknown Gregorian date type"); | |
} | |
} else if (value instanceof QName) { | |
QName q = (QName) value; | |
return new QNameValue(q.getPrefix(), q.getNamespaceURI(), q.getLocalPart()); //BuiltInAtomicType.QNAME, null); | |
} else if (value instanceof URI) { | |
return new AnyURIValue(value.toString()); | |
} else if (value instanceof Map) { | |
HashTrieMap htm = new HashTrieMap(); | |
for (Map.Entry<?, ?> me : ((Map<?, ?>) value).entrySet()) { | |
htm.initialPut( | |
(AtomicValue) objectToItem(me.getKey(), config), | |
objectToItem(me.getValue(), config)); | |
} | |
return htm; | |
} else { | |
return new ObjectValue<>(value); | |
} | |
} | |
private SchemaType getType(Object node) { | |
SchemaType type; | |
if (node instanceof Integer) { | |
type = XmlInteger.type; | |
} else if (node instanceof Double) { | |
type = XmlDouble.type; | |
} else if (node instanceof Long) { | |
type = XmlLong.type; | |
} else if (node instanceof Float) { | |
type = XmlFloat.type; | |
} else if (node instanceof BigDecimal) { | |
type = XmlDecimal.type; | |
} else if (node instanceof Boolean) { | |
type = XmlBoolean.type; | |
} else if (node instanceof String) { | |
type = XmlString.type; | |
} else if (node instanceof Date) { | |
type = XmlDate.type; | |
} else { | |
type = XmlAnySimpleType.type; | |
} | |
return type; | |
} | |
public void release() { | |
if (_cur != null) { | |
_cur.release(); | |
_cur = null; | |
} | |
} | |
private Cur loadNode(Locale locale, Node node) { | |
Locale.LoadContext context = new Cur.CurLoadContext(locale, _options); | |
try { | |
loadNodeHelper(locale, node, context); | |
Cur c = context.finish(); | |
Locale.associateSourceName(c, _options); | |
Locale.autoTypeDocument(c, null, _options); | |
return c; | |
} catch (Exception e) { | |
throw new XmlRuntimeException(e.getMessage(), e); | |
} | |
} | |
private void loadNodeHelper(Locale locale, Node node, Locale.LoadContext context) { | |
if (node.getNodeType() == Node.ATTRIBUTE_NODE) { | |
QName attName = new QName(node.getNamespaceURI(), | |
node.getLocalName(), | |
node.getPrefix()); | |
context.attr(attName, node.getNodeValue()); | |
} else { | |
locale.loadNode(node, context); | |
} | |
} | |
} |