| /* 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 java.io.*; |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.*; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.ref.WeakReference; |
| import java.math.BigDecimal; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| 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.XPath.ExecutionContext; |
| |
| import org.apache.xmlbeans.*; |
| import org.w3c.dom.Node; |
| |
| |
| // 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 Map _xbeanPathCache = new WeakHashMap(); |
| private static Map _xdkPathCache = new WeakHashMap(); |
| private static Map _xqrlPathCache = new WeakHashMap(); |
| private static Map _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); |
| |
| if (options.hasOption(XmlOptions.XQUERY_CURRENT_NODE_VAR)) { |
| currentNodeVar = (String) options.get(XmlOptions.XQUERY_CURRENT_NODE_VAR); |
| |
| 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 pathWeakRef = null; |
| Map namespaces = (force & USE_DELEGATE) != 0 ? new HashMap() : null; |
| lock.readLock().lock(); |
| try { |
| if ((force & USE_XBEAN) != 0) |
| pathWeakRef = (WeakReference)_xbeanPathCache.get(pathExpr); |
| if (pathWeakRef == null && (force & USE_XQRL) != 0) |
| pathWeakRef = (WeakReference)_xqrlPathCache.get(pathExpr); |
| if (pathWeakRef == null && (force & USE_XDK) != 0) |
| pathWeakRef = (WeakReference)_xdkPathCache.get(pathExpr); |
| if (pathWeakRef == null && (force & USE_XQRL2002) != 0) |
| pathWeakRef = (WeakReference)_xqrl2002PathCache.get(pathExpr); |
| |
| if (pathWeakRef!=null) |
| path = (Path)pathWeakRef.get(); |
| if (path != null) |
| return path; |
| } finally { |
| lock.readLock().unlock(); |
| } |
| lock.writeLock().lock(); |
| try { |
| if ((force & USE_XBEAN) != 0) { |
| pathWeakRef = (WeakReference)_xbeanPathCache.get(pathExpr); |
| if (pathWeakRef != null) |
| path = (Path)pathWeakRef.get(); |
| if (path==null) |
| path = getCompiledPathXbean(pathExpr, currentVar, namespaces); |
| } |
| if (path == null && (force & USE_XQRL) != 0) { |
| pathWeakRef = (WeakReference)_xqrlPathCache.get(pathExpr); |
| if (pathWeakRef != null) |
| path = (Path)pathWeakRef.get(); |
| if (path==null) |
| path = getCompiledPathXqrl(pathExpr, currentVar); |
| } |
| if (path == null && (force & USE_XDK) != 0) { |
| pathWeakRef = (WeakReference)_xdkPathCache.get(pathExpr); |
| if (pathWeakRef != null) |
| path = (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 = (WeakReference)_xqrl2002PathCache.get(pathExpr); |
| if (pathWeakRef != null) |
| path = (Path)pathWeakRef.get(); |
| if (path==null) |
| path = getCompiledPathXqrl2002(pathExpr, currentVar); |
| } |
| if (path == null) |
| { |
| StringBuffer errMessage = new StringBuffer(); |
| 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 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 namespaces, String delIntfName) |
| { |
| Path path = null; |
| 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 = |
| namespaces.get(XPath._NS_BOUNDARY) == null ? |
| 0 : |
| ((Integer) namespaces.get(XPath._NS_BOUNDARY)).intValue(); |
| namespaces.remove(XPath._NS_BOUNDARY); |
| path = DelegatePathImpl.create(delIntfName, |
| pathExpr.substring(offset), |
| currentVar, |
| namespaces); |
| |
| return path; |
| } |
| |
| |
| 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 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 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, new 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, new 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, new 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 ExecutionContext |
| 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 XPath.ExecutionContext |
| 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; |
| |
| 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; |
| } |
| } |
| } |