blob: 7ab6242ae03946da3a5a366cfb6823492f817649 [file] [log] [blame]
/* 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.store;
import org.apache.xmlbeans.*;
import org.apache.xmlbeans.impl.common.DefaultClassLoaderResourceLoader;
import org.apache.xmlbeans.impl.common.XPath;
import org.apache.xmlbeans.impl.common.XPath.XPathCompileException;
import org.apache.xmlbeans.impl.common.XPathExecutionContext;
import org.w3c.dom.Node;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
// TODO - This class handled query *and* path ... rename it?
public abstract class Path {
public static final String PATH_DELEGATE_INTERFACE = "PATH_DELEGATE_INTERFACE";
public static String _useDelegateForXpath = "use delegate for xpath";
public static String _useXdkForXpath = "use xdk for xpath";
public static String _useXqrlForXpath = "use xqrl for xpath";
public static String _useXbeanForXpath = "use xbean for xpath";
public static String _forceXqrl2002ForXpathXQuery = "use xqrl-2002 for xpath";
private static final int USE_XBEAN = 0x01;
private static final int USE_XQRL = 0x02;
private static final int USE_DELEGATE = 0x04;
private static final int USE_XQRL2002 = 0x08;
private static final int USE_XDK = 0x10;
private static final Map<String, WeakReference<Path>> _xbeanPathCache = new WeakHashMap<>();
private static final Map<String, WeakReference<Path>> _xdkPathCache = new WeakHashMap<>();
private static final Map<String, WeakReference<Path>> _xqrlPathCache = new WeakHashMap<>();
private static final Map<String, WeakReference<Path>> _xqrl2002PathCache = new WeakHashMap<>();
private static Method _xdkCompilePath;
private static Method _xqrlCompilePath;
private static Method _xqrl2002CompilePath;
private static boolean _xdkAvailable = true;
private static boolean _xqrlAvailable = true;
private static boolean _xqrl2002Available = true;
private static final String _delIntfName;
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
static {
String id = "META-INF/services/org.apache.xmlbeans.impl.store.PathDelegate.SelectPathInterface";
InputStream in = new DefaultClassLoaderResourceLoader().getResourceAsStream(id);
String name = null;
if (in != null) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
name = br.readLine().trim();
br.close();
} catch (Exception e) {
// set nothing
}
}
_delIntfName = name;
}
protected final String _pathKey;
Path(String key) {
_pathKey = key;
}
interface PathEngine {
void release();
boolean next(Cur c);
}
abstract PathEngine execute(Cur c, XmlOptions options);
//
//
//
static String getCurrentNodeVar(XmlOptions options) {
String currentNodeVar = "this";
options = XmlOptions.maskNull(options);
String cnv = options.getXqueryCurrentNodeVar();
if (cnv != null) {
currentNodeVar = cnv;
if (currentNodeVar.startsWith("$")) {
throw new IllegalArgumentException("Omit the '$' prefix for the current node variable");
}
}
return currentNodeVar;
}
public static Path getCompiledPath(String pathExpr, XmlOptions options) {
options = XmlOptions.maskNull(options);
int force =
options.hasOption(_useDelegateForXpath) ? USE_DELEGATE
: options.hasOption(_useXqrlForXpath) ? USE_XQRL
: options.hasOption(_useXdkForXpath) ? USE_XDK
: options.hasOption(_useXbeanForXpath) ? USE_XBEAN
: options.hasOption(_forceXqrl2002ForXpathXQuery) ? USE_XQRL2002
: USE_XBEAN | USE_XQRL | USE_XDK | USE_DELEGATE; //set all bits except XQRL2002
String delIntfName =
options.hasOption(PATH_DELEGATE_INTERFACE) ?
(String) options.get(PATH_DELEGATE_INTERFACE) : _delIntfName;
return getCompiledPath(pathExpr, force, getCurrentNodeVar(options), delIntfName);
}
static Path getCompiledPath(String pathExpr, int force,
String currentVar, String delIntfName) {
Path path = null;
WeakReference<Path> pathWeakRef = null;
Map<String, String> namespaces = (force & USE_DELEGATE) != 0 ? new HashMap<>() : null;
lock.readLock().lock();
try {
if ((force & USE_XBEAN) != 0) {
pathWeakRef = _xbeanPathCache.get(pathExpr);
}
if (pathWeakRef == null && (force & USE_XQRL) != 0) {
pathWeakRef = _xqrlPathCache.get(pathExpr);
}
if (pathWeakRef == null && (force & USE_XDK) != 0) {
pathWeakRef = _xdkPathCache.get(pathExpr);
}
if (pathWeakRef == null && (force & USE_XQRL2002) != 0) {
pathWeakRef = _xqrl2002PathCache.get(pathExpr);
}
if (pathWeakRef != null) {
path = pathWeakRef.get();
}
if (path != null) {
return path;
}
} finally {
lock.readLock().unlock();
}
lock.writeLock().lock();
try {
if ((force & USE_XBEAN) != 0) {
pathWeakRef = _xbeanPathCache.get(pathExpr);
if (pathWeakRef != null) {
path = pathWeakRef.get();
}
if (path == null) {
path = getCompiledPathXbean(pathExpr, currentVar, namespaces);
}
}
if (path == null && (force & USE_XQRL) != 0) {
pathWeakRef = _xqrlPathCache.get(pathExpr);
if (pathWeakRef != null) {
path = pathWeakRef.get();
}
if (path == null) {
path = getCompiledPathXqrl(pathExpr, currentVar);
}
}
if (path == null && (force & USE_XDK) != 0) {
pathWeakRef = _xdkPathCache.get(pathExpr);
if (pathWeakRef != null) {
path = pathWeakRef.get();
}
if (path == null) {
path = getCompiledPathXdk(pathExpr, currentVar);
}
}
if (path == null && (force & USE_DELEGATE) != 0) {
path = getCompiledPathDelegate(pathExpr, currentVar, namespaces, delIntfName);
}
if (path == null && (force & USE_XQRL2002) != 0) {
pathWeakRef = _xqrl2002PathCache.get(pathExpr);
if (pathWeakRef != null) {
path = pathWeakRef.get();
}
if (path == null) {
path = getCompiledPathXqrl2002(pathExpr, currentVar);
}
}
if (path == null) {
StringBuilder errMessage = new StringBuilder();
if ((force & USE_XBEAN) != 0) {
errMessage.append(" Trying XBeans path engine...");
}
if ((force & USE_XQRL) != 0) {
errMessage.append(" Trying XQRL...");
}
if ((force & USE_XDK) != 0) {
errMessage.append(" Trying XDK...");
}
if ((force & USE_DELEGATE) != 0) {
errMessage.append(" Trying delegated path engine...");
}
if ((force & USE_XQRL2002) != 0) {
errMessage.append(" Trying XQRL2002...");
}
throw new RuntimeException(errMessage.toString() + " FAILED on " + pathExpr);
}
} finally {
lock.writeLock().unlock();
}
return path;
}
static private Path getCompiledPathXdk(String pathExpr, String currentVar) {
Path path = createXdkCompiledPath(pathExpr, currentVar);
if (path != null) {
_xdkPathCache.put(path._pathKey, new WeakReference<>(path));
}
return path;
}
static private Path getCompiledPathXqrl(String pathExpr, String currentVar) {
Path path = createXqrlCompiledPath(pathExpr, currentVar);
if (path != null) {
_xqrlPathCache.put(path._pathKey, new WeakReference<>(path));
}
return path;
}
static private Path getCompiledPathXqrl2002(String pathExpr, String currentVar) {
Path path = createXqrl2002CompiledPath(pathExpr, currentVar);
if (path != null) {
_xqrl2002PathCache.put(path._pathKey, new WeakReference<>(path));
}
return path;
}
static private Path getCompiledPathXbean(String pathExpr,
String currentVar, Map<String, String> namespaces) {
Path path = XbeanPath.create(pathExpr, currentVar, namespaces);
if (path != null) {
_xbeanPathCache.put(path._pathKey, new WeakReference<>(path));
}
return path;
}
static private Path getCompiledPathDelegate(String pathExpr, String currentVar, Map<String, String> namespaces, String delIntfName) {
if (namespaces == null) {
namespaces = new HashMap<>();
}
try {
XPath.compileXPath(pathExpr, currentVar, namespaces);
} catch (XPath.XPathCompileException e) {
//do nothing, this function is only called to populate the namespaces map
}
int offset = Integer.parseInt(namespaces.getOrDefault(XPath._NS_BOUNDARY, "0"));
namespaces.remove(XPath._NS_BOUNDARY);
return DelegatePathImpl.create(delIntfName,
pathExpr.substring(offset),
currentVar,
namespaces);
}
public static String compilePath(String pathExpr, XmlOptions options) {
return getCompiledPath(pathExpr, options)._pathKey;
}
//
// Xbean store specific implementation of compiled path
//
private static final class XbeanPath extends Path {
static Path create(String pathExpr, String currentVar, Map<String, String> namespaces) {
try {
return new XbeanPath(pathExpr, currentVar,
XPath.compileXPath(pathExpr, currentVar, namespaces));
} catch (XPathCompileException e) {
return null;
}
}
private XbeanPath(String pathExpr, String currentVar, XPath xpath) {
super(pathExpr);
_currentVar = currentVar;
_compiledPath = xpath;
}
PathEngine execute(Cur c, XmlOptions options) {
options = XmlOptions.maskNull(options);
String delIntfName =
options.hasOption(PATH_DELEGATE_INTERFACE) ?
(String) options.get(PATH_DELEGATE_INTERFACE) : _delIntfName;
// The builtin XPath engine works only on containers. Delegate to
// xqrl otherwise. Also, if the path had a //. at the end, the
// simple xpath engine can't do the generate case, it only handles
// attrs and elements.
if (!c.isContainer() || _compiledPath.sawDeepDot()) {
int force = USE_DELEGATE | USE_XQRL | USE_XDK;
return getCompiledPath(_pathKey, force, _currentVar, delIntfName).execute(c, options);
}
return new XbeanPathEngine(_compiledPath, c);
}
private final String _currentVar;
private final XPath _compiledPath;
public Map<String, String> namespaces;
}
private static Path createXdkCompiledPath(String pathExpr, String currentVar) {
if (!_xdkAvailable) {
return null;
}
if (_xdkCompilePath == null) {
try {
Class xdkImpl = Class.forName("org.apache.xmlbeans.impl.store.OXQXBXqrlImpl");
_xdkCompilePath =
xdkImpl.getDeclaredMethod("compilePath",
new Class[]{String.class, String.class, Boolean.class});
} catch (ClassNotFoundException e) {
_xdkAvailable = false;
return null;
} catch (Exception e) {
_xdkAvailable = false;
throw new RuntimeException(e.getMessage(), e);
}
}
Object[] args = new Object[]{pathExpr, currentVar, Boolean.TRUE};
try {
return (Path) _xdkCompilePath.invoke(null, args);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
throw new RuntimeException(t.getMessage(), t);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private static Path createXqrlCompiledPath(String pathExpr, String currentVar) {
if (!_xqrlAvailable) {
return null;
}
if (_xqrlCompilePath == null) {
try {
Class xqrlImpl = Class.forName("org.apache.xmlbeans.impl.store.XqrlImpl");
_xqrlCompilePath =
xqrlImpl.getDeclaredMethod("compilePath",
new Class[]{String.class, String.class, Boolean.class});
} catch (ClassNotFoundException e) {
_xqrlAvailable = false;
return null;
} catch (Exception e) {
_xqrlAvailable = false;
throw new RuntimeException(e.getMessage(), e);
}
}
Object[] args = new Object[]{pathExpr, currentVar, Boolean.TRUE};
try {
return (Path) _xqrlCompilePath.invoke(null, args);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
throw new RuntimeException(t.getMessage(), t);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private static Path createXqrl2002CompiledPath(String pathExpr, String currentVar) {
if (!_xqrl2002Available) {
return null;
}
if (_xqrl2002CompilePath == null) {
try {
Class xqrlImpl = Class.forName("org.apache.xmlbeans.impl.store.Xqrl2002Impl");
_xqrl2002CompilePath =
xqrlImpl.getDeclaredMethod("compilePath",
new Class[]{String.class, String.class, Boolean.class});
} catch (ClassNotFoundException e) {
_xqrl2002Available = false;
return null;
} catch (Exception e) {
_xqrl2002Available = false;
throw new RuntimeException(e.getMessage(), e);
}
}
Object[] args = new Object[]{pathExpr, currentVar, Boolean.TRUE};
try {
return (Path) _xqrl2002CompilePath.invoke(null, args);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
throw new RuntimeException(t.getMessage(), t);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private static final class XbeanPathEngine
extends XPathExecutionContext
implements PathEngine {
XbeanPathEngine(XPath xpath, Cur c) {
assert c.isContainer();
_version = c._locale.version();
_cur = c.weakCur(this);
_cur.push();
init(xpath);
int ret = start();
if ((ret & HIT) != 0) {
c.addToSelection();
}
doAttrs(ret, c);
if ((ret & DESCEND) == 0 || !Locale.toFirstChildElement(_cur)) {
release();
}
}
private void advance(Cur c) {
assert _cur != null;
if (_cur.isFinish()) {
if (_cur.isAtEndOfLastPush()) {
release();
} else {
end();
_cur.next();
}
} else if (_cur.isElem()) {
int ret = element(_cur.getName());
if ((ret & HIT) != 0) {
c.addToSelection(_cur);
}
doAttrs(ret, c);
if ((ret & DESCEND) == 0 || !Locale.toFirstChildElement(_cur)) {
end();
_cur.skip();
}
} else {
do {
_cur.next();
}
while (!_cur.isContainerOrFinish());
}
}
private void doAttrs(int ret, Cur c) {
assert _cur.isContainer();
if ((ret & ATTRS) != 0) {
if (_cur.toFirstAttr()) {
do {
if (attr(_cur.getName())) {
c.addToSelection(_cur);
}
}
while (_cur.toNextAttr());
_cur.toParent();
}
}
}
public boolean next(Cur c) {
if (_cur != null && _version != _cur._locale.version()) {
throw new ConcurrentModificationException("Document changed during select");
}
int startCount = c.selectionCount();
while (_cur != null) {
advance(c);
if (startCount != c.selectionCount()) {
return true;
}
}
return false;
}
public void release() {
if (_cur != null) {
_cur.release();
_cur = null;
}
}
private final long _version;
private Cur _cur;
}
private static final class DelegatePathImpl
extends Path {
private PathDelegate.SelectPathInterface _xpathImpl;
static Path create(String implClassName, String pathExpr, String currentNodeVar, Map namespaceMap) {
assert !currentNodeVar.startsWith("$"); // cezar review with ericvas
PathDelegate.SelectPathInterface impl =
PathDelegate.createInstance(implClassName, pathExpr, currentNodeVar, namespaceMap);
if (impl == null) {
return null;
}
return new DelegatePathImpl(impl, pathExpr);
}
private DelegatePathImpl(PathDelegate.SelectPathInterface xpathImpl,
String pathExpr) {
super(pathExpr);
_xpathImpl = xpathImpl;
}
protected PathEngine execute(Cur c, XmlOptions options) {
return new DelegatePathEngine(_xpathImpl, c);
}
private static class DelegatePathEngine
extends XPathExecutionContext
implements PathEngine {
// full datetime format: yyyy-MM-dd'T'HH:mm:ss'Z'
private final DateFormat xmlDateFormat = new SimpleDateFormat("yyyy-MM-dd");
DelegatePathEngine(PathDelegate.SelectPathInterface xpathImpl,
Cur c) {
_engine = xpathImpl;
_version = c._locale.version();
_cur = c.weakCur(this);
}
public boolean next(Cur c) {
if (!_firstCall) {
return false;
}
_firstCall = false;
if (_cur != null && _version != _cur._locale.version()) {
throw new ConcurrentModificationException("Document changed during select");
}
List resultsList;
Object context_node = _cur.getDom();
resultsList = _engine.selectPath(context_node);
int i;
for (i = 0; i < resultsList.size(); i++) {
//simple type function results
Object node = resultsList.get(i);
Cur pos = null;
if (!(node instanceof Node)) {
Object obj = resultsList.get(i);
String value;
if (obj instanceof Date) {
value = xmlDateFormat.format((Date) obj);
} else if (obj instanceof BigDecimal) {
value = ((BigDecimal) obj).toPlainString();
} else {
value = obj.toString();
}
//we cannot leave the cursor's locale, as
//everything is done in the selections of this cursor
Locale l = c._locale;
try {
pos = l.load("<xml-fragment/>").tempCur();
pos.setValue(value);
SchemaType type = getType(node);
Locale.autoTypeDocument(pos, type, null);
//move the cur to the actual text
pos.next();
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
assert (node instanceof DomImpl.Dom) :
"New object created in XPATH!";
pos = ((DomImpl.Dom) node).tempCur();
}
c.addToSelection(pos);
pos.release();
}
release();
_engine = null;
return true;
}
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 _cur;
private PathDelegate.SelectPathInterface _engine;
private boolean _firstCall = true;
private long _version;
}
}
}