| /* 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; |
| } |
| } |
| } |