From cf0db96314863376254bacbff4eefd13e3527707 Mon Sep 17 00:00:00 2001 From: Joe Wang Date: Thu, 1 May 2025 17:03:29 +0000 Subject: [PATCH] 8354084: Streamline XPath API's extension function control Reviewed-by: rriggs, naoto --- .../internal/functions/FuncExtFunction.java | 10 +- .../internal/jaxp/JAXPExtensionsProvider.java | 34 +-- .../xpath/internal/jaxp/XPathImplUtil.java | 7 +- .../internal/res/XPATHErrorResources.java | 10 +- src/java.xml/share/classes/module-info.java | 8 +- .../unittest/xpath/SecureProcessingTest.java | 195 ----------------- .../unittest/xpath/SecureProcessingTest.xml | 2 - .../unittest/xpath/XPathFunctionTest.java | 198 ++++++++++++++++++ .../jaxp/unittest/xpath/XPathFunctionTest.xml | 12 ++ 9 files changed, 223 insertions(+), 253 deletions(-) delete mode 100644 test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.java delete mode 100644 test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.xml create mode 100644 test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.java create mode 100644 test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.xml diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java index 6fe57d28d2f..9f2a7e78951 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -41,7 +41,7 @@ import javax.xml.transform.TransformerException; * the expression executes, it calls ExtensionsTable#extFunction, and then * converts the result to the appropriate XObject. * @xsl.usage advanced - * @LastModified: May 2022 + * @LastModified: Apr 2025 */ public class FuncExtFunction extends Function { @@ -186,12 +186,6 @@ public class FuncExtFunction extends Function */ public XObject execute(XPathContext xctxt) throws TransformerException { - if (xctxt.isSecureProcessing()) - throw new javax.xml.transform.TransformerException( - XPATHMessages.createXPATHMessage( - XPATHErrorResources.ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED, - new Object[] {toString()})); - XObject result; List argVec = new ArrayList<>(); int nArgs = m_argVec.size(); diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPExtensionsProvider.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPExtensionsProvider.java index 6d9d882f075..5622a0e3db0 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPExtensionsProvider.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPExtensionsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -37,26 +37,16 @@ import jdk.xml.internal.JdkXmlFeatures; /** * * @author Ramesh Mandava - * @LastModified: Nov 2017 + * @LastModified: Apr 2025 */ public class JAXPExtensionsProvider implements ExtensionsProvider { private final XPathFunctionResolver resolver; - private boolean extensionInvocationDisabled = false; public JAXPExtensionsProvider(XPathFunctionResolver resolver) { this.resolver = resolver; - this.extensionInvocationDisabled = false; } - public JAXPExtensionsProvider(XPathFunctionResolver resolver, - boolean featureSecureProcessing, JdkXmlFeatures featureManager ) { - this.resolver = resolver; - if (featureSecureProcessing && - !featureManager.getFeature(JdkXmlFeatures.XmlFeature.ENABLE_EXTENSION_FUNCTION)) { - this.extensionInvocationDisabled = true; - } - } /** * Is the extension function available? @@ -111,16 +101,6 @@ public class JAXPExtensionsProvider implements ExtensionsProvider { //Find the XPathFunction corresponding to namespace and funcName javax.xml.namespace.QName myQName = new QName( ns, funcName ); - // JAXP 1.3 spec says When XMLConstants.FEATURE_SECURE_PROCESSING - // feature is set then invocation of extension functions need to - // throw XPathFunctionException - if ( extensionInvocationDisabled ) { - String fmsg = XSLMessages.createXPATHMessage( - XPATHErrorResources.ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED, - new Object[] { myQName.toString() } ); - throw new XPathFunctionException ( fmsg ); - } - // Assuming user is passing all the needed parameters ( including // default values ) int arity = argVec.size(); @@ -167,16 +147,6 @@ public class JAXPExtensionsProvider implements ExtensionsProvider { javax.xml.namespace.QName myQName = new javax.xml.namespace.QName( namespace, functionName ); - // JAXP 1.3 spec says When XMLConstants.FEATURE_SECURE_PROCESSING - // feature is set then invocation of extension functions need to - // throw XPathFunctionException - if ( extensionInvocationDisabled ) { - String fmsg = XSLMessages.createXPATHMessage( - XPATHErrorResources.ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED, - new Object[] { myQName.toString() } ); - throw new XPathFunctionException ( fmsg ); - } - XPathFunction xpathFunction = resolver.resolveFunction( myQName, arity ); diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java index e81523b5607..a92090900fa 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,7 +54,7 @@ import org.xml.sax.SAXException; * This class contains several utility methods used by XPathImpl and * XPathExpressionImpl * - * @LastModified: Jan 2022 + * @LastModified: Apr 2025 */ class XPathImplUtil { XPathFunctionResolver functionResolver; @@ -85,8 +85,7 @@ class XPathImplUtil { new Object[] {})); } if (functionResolver != null) { - JAXPExtensionsProvider jep = new JAXPExtensionsProvider( - functionResolver, featureSecureProcessing, featureManager); + JAXPExtensionsProvider jep = new JAXPExtensionsProvider(functionResolver); xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext(jep); } else { xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext(); diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java index 433a0f85389..716a4bcf63b 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -31,7 +31,7 @@ import java.util.ListResourceBundle; * Also you need to update the count of messages(MAX_CODE)or * the count of warnings(MAX_WARNING) [ Information purpose only] * @xsl.usage advanced - * @LastModified: Nov 2024 + * @LastModified: Apr 2025 */ public class XPATHErrorResources extends ListResourceBundle { @@ -305,7 +305,6 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED = public static final String ER_XPATH_ERROR = "ER_XPATH_ERROR"; //BEGIN: Keys needed for exception messages of JAXP 1.3 XPath API implementation - public static final String ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED = "ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED"; public static final String ER_RESOLVE_VARIABLE_RETURNS_NULL = "ER_RESOLVE_VARIABLE_RETURNS_NULL"; public static final String ER_NO_XPATH_VARIABLE_RESOLVER = "ER_NO_XPATH_VARIABLE_RESOLVER"; public static final String ER_NO_XPATH_FUNCTION_PROVIDER = "ER_NO_XPATH_FUNCTION_PROVIDER"; @@ -766,11 +765,6 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED = //BEGIN: Definitions of error keys used in exception messages of JAXP 1.3 XPath API implementation - /** Field ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED */ - - { ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED, - "Extension function: ''{0}'' can not be invoked when the XMLConstants.FEATURE_SECURE_PROCESSING feature is set to true."}, - /** Field ER_RESOLVE_VARIABLE_RETURNS_NULL */ { ER_RESOLVE_VARIABLE_RETURNS_NULL, diff --git a/src/java.xml/share/classes/module-info.java b/src/java.xml/share/classes/module-info.java index a12fd3e8f45..1b4f345eb72 100644 --- a/src/java.xml/share/classes/module-info.java +++ b/src/java.xml/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -868,19 +868,19 @@ * * * {@systemProperty jdk.xml.enableExtensionFunctions} - * Determines if XSLT and XPath extension functions are to be allowed. + * Determines whether extension functions in the Transform API are to be allowed. + * The extension functions in the XPath API are not affected by this property. * * yes * Boolean * * true or false. True indicates that extension functions are allowed; False otherwise. * - * true + * false * false * Yes * * Transform
- * XPath * * Method 2 * 8 diff --git a/test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.java b/test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.java deleted file mode 100644 index 21fc436226b..00000000000 --- a/test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package xpath; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; -import java.util.List; - -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; -import javax.xml.namespace.QName; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import javax.xml.xpath.XPathFactoryConfigurationException; -import javax.xml.xpath.XPathFunction; -import javax.xml.xpath.XPathFunctionException; -import javax.xml.xpath.XPathFunctionResolver; - -import org.testng.Assert; -import org.testng.annotations.Test; -import org.w3c.dom.Document; -import org.xml.sax.SAXException; - -/* - * @test - * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest - * @run testng/othervm xpath.SecureProcessingTest - * @summary Test when FEATURE_SECURE_PROCESSING is true, calling an external function will cause XPathFunctionException. - */ -@Test -public class SecureProcessingTest { - public final void testSecureProcessing() { - boolean _isSecureMode = System.getSecurityManager() != null; - - final String XPATH_EXPRESSION = "ext:helloWorld()"; - - // the xml source - InputStream xmlStream = this.getClass().getResourceAsStream("SecureProcessingTest.xml"); - - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder documentBuilder = null; - Document document = null; - - try { - documentBuilder = documentBuilderFactory.newDocumentBuilder(); - document = documentBuilder.parse(xmlStream); - } catch (ParserConfigurationException parserConfigurationException) { - parserConfigurationException.printStackTrace(); - Assert.fail(parserConfigurationException.toString()); - } catch (SAXException saxException) { - saxException.printStackTrace(); - Assert.fail(saxException.toString()); - } catch (IOException ioException) { - ioException.printStackTrace(); - Assert.fail(ioException.toString()); - } - - // the XPath - XPathFactory xPathFactory = null; - XPath xPath = null; - String xPathResult = null; - - // SECURE_PROCESSING == false - // evaluate an expression with a user defined function with a non-secure - // XPath - // expect success - if (!_isSecureMode) { // jaxp secure feature can not be turned off when - // security manager is present - try { - xPathFactory = xPathFactory.newInstance(); - xPathFactory.setXPathFunctionResolver(new MyXPathFunctionResolver()); - xPathFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false); - - xPath = xPathFactory.newXPath(); - xPath.setNamespaceContext(new MyNamespaceContext()); - - xPathResult = xPath.evaluate(XPATH_EXPRESSION, document); - } catch (XPathFactoryConfigurationException xPathFactoryConfigurationException) { - xPathFactoryConfigurationException.printStackTrace(); - Assert.fail(xPathFactoryConfigurationException.toString()); - } catch (XPathExpressionException xPathExpressionException) { - xPathExpressionException.printStackTrace(); - Assert.fail(xPathExpressionException.toString()); - } - - // expected success - System.out.println("XPath result (SECURE_PROCESSING == false) = \"" + xPathResult + "\""); - } - // now try with SECURE_PROCESSING == true - // evaluate an expression with a user defined function with a secure - // XPath - // expect Exception - boolean securityException = false; - try { - xPathFactory = xPathFactory.newInstance(); - xPathFactory.setXPathFunctionResolver(new MyXPathFunctionResolver()); - xPathFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - - xPath = xPathFactory.newXPath(); - xPath.setNamespaceContext(new MyNamespaceContext()); - - xPathResult = xPath.evaluate(XPATH_EXPRESSION, document); - } catch (XPathFactoryConfigurationException xPathFactoryConfigurationException) { - xPathFactoryConfigurationException.printStackTrace(); - Assert.fail(xPathFactoryConfigurationException.toString()); - } catch (XPathFunctionException xPathFunctionException) { - // expected security exception - securityException = true; - xPathFunctionException.printStackTrace(System.out); - } catch (XPathExpressionException xPathExpressionException) { - xPathExpressionException.printStackTrace(); - Assert.fail(xPathExpressionException.toString()); - } - - // expected Exception - if (!securityException) { - Assert.fail("XPath result (SECURE_PROCESSING == true) = \"" + xPathResult + "\""); - } - } - - private class MyXPathFunctionResolver implements XPathFunctionResolver { - - public XPathFunction resolveFunction(QName functionName, int arity) { - - // not a real ewsolver, always return a default XPathFunction - return new MyXPathFunction(); - } - } - - private class MyXPathFunction implements XPathFunction { - - public Object evaluate(List list) throws XPathFunctionException { - - return "Hello World"; - } - } - - private class MyNamespaceContext implements NamespaceContext { - - public String getNamespaceURI(String prefix) { - if (prefix == null) { - throw new IllegalArgumentException("The prefix cannot be null."); - } - - if (prefix.equals("ext")) { - return "http://ext.com"; - } else { - return null; - } - } - - public String getPrefix(String namespace) { - - if (namespace == null) { - throw new IllegalArgumentException("The namespace uri cannot be null."); - } - - if (namespace.equals("http://ext.com")) { - return "ext"; - } else { - return null; - } - } - - public Iterator getPrefixes(String namespace) { - return null; - } - } -} diff --git a/test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.xml b/test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.xml deleted file mode 100644 index bc916a958f9..00000000000 --- a/test/jaxp/javax/xml/jaxp/unittest/xpath/SecureProcessingTest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.java b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.java new file mode 100644 index 00000000000..d0f0a1284c1 --- /dev/null +++ b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package xpath; + +import java.util.List; +import java.util.stream.Stream; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.*; +import jaxp.library.JUnitTestUtil; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/* + * @test + * @bug 8354084 + * @summary Verifies that extensions to XPathFunction. + * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest /test/lib + * @run junit xpath.XPathFunctionTest + */ +public class XPathFunctionTest { + /* + * Arguments for XPath Extension Function Test, refer to the test below. + */ + private static Stream testData() { + // expected result when a resolver is set properly + String result = "id=2 price=20"; + return Stream.of( + // cases where the result is as expected when a resolver is set + Arguments.of(true, false, false, false, false, result, null), + Arguments.of(true, true, false, false, false, result, null), + Arguments.of(true, true, false, true, false, result, null), + Arguments.of(true, true, false, true, true, result, null), + Arguments.of(true, true, true, true, true, result, null), + // cases XPathExpressionException was thrown before the change even though there's a resolver + Arguments.of(true, true, true, false, false, result, null), + Arguments.of(true, true, true, true, false, result, null), + // XPathExpressionException will continue to be thrown due to missing resolver, though it was + // thrown for a different reason (FSP is turned on) before the change + Arguments.of(false, false, false, false, false, result, XPathExpressionException.class), + Arguments.of(false, true, true, false, false, result, XPathExpressionException.class), + Arguments.of(false, true, true, true, false, result, XPathExpressionException.class) + ); + } + + /** + * Verifies the control over XPath Extension Functions. + * @param useResolver indicates whether there is a custom resolver + * @param setFSP indicates whether FSP is to be set + * @param FSPValue the FSP value + * @param setProperty indicates whether the property {@code jdk.xml.enableExtensionFunctions} + * is to be set + * @param propertyValue the property value + * @param expected the expected result + * @param expectedType the expected throw type + * @throws Exception if the test fails other than the expected Exception, which + * would indicate an issue in configuring the test + */ + @ParameterizedTest + @MethodSource("testData") + public void test(boolean useResolver, boolean setFSP, boolean FSPValue, + boolean setProperty, boolean propertyValue, String expected, Class expectedType) throws Exception { + if (expectedType != null) { + assertThrows(expectedType, () -> findToy(useResolver, setFSP, FSPValue, setProperty, propertyValue, expectedType)); + } else { + String result = findToy(useResolver, setFSP, FSPValue, setProperty, propertyValue, expectedType); + assertEquals(expected, result); + } + } + + public String findToy(boolean useResolver, boolean setFSP, boolean FSPValue, + boolean setProperty, boolean propertyValue, Class expectedType) + throws Exception { + + Document doc = getDocument(JUnitTestUtil.SRC_DIR + "/XPathFunctionTest.xml"); + XPathFactory xpf = XPathFactory.newDefaultInstance(); + if (useResolver) { + xpf.setXPathFunctionResolver(new FunctionResolver(doc)); + } + if (setFSP) { + xpf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, FSPValue); + } + if (setProperty) { + xpf.setFeature("jdk.xml.enableExtensionFunctions", propertyValue); + } + + XPath xPath = xpf.newXPath(); + XPathExpression exp = xPath.compile("ext:findToy('name', 'Another toy')"); + Node toyNode = (Node)exp.evaluate(doc, XPathConstants.NODE); + String id = "", price = ""; + if (toyNode != null && toyNode.getNodeType() == Node.ELEMENT_NODE) { + Element toyElement = (Element)toyNode; + id = toyElement.getAttribute("id"); + price = toyElement.getElementsByTagName("price").item(0).getTextContent(); + + } + return "id=" + id + " price=" + price; + + } + + /** + * Returns a DOM Document. + * @param xmlFile the XML document to be parsed + * @return a DOM Document + * @throws Exception if error occurs + */ + Document getDocument(String xmlFile) + throws Exception { + try { + DocumentBuilder builder = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder(); + Document out = builder.parse(xmlFile); + return out; + } catch (Exception e) { + // won't happen, parsing a valid file + } + return null; + } + + // XPathFunctionResolver customized for the FindFunction + class FunctionResolver implements XPathFunctionResolver { + private final Document doc; + + public FunctionResolver(Document doc) { + this.doc = doc; + } + + @Override + public XPathFunction resolveFunction(QName functionName, int arity) { + if ("findToy".equals(functionName.getLocalPart()) && arity == 2) { + return new FindFunction(doc); + } + + return null; + } + } + + // The Find function + class FindFunction implements XPathFunction { + private final Document doc; + + public FindFunction(Document doc) { + this.doc = doc; + } + + @SuppressWarnings("rawtypes") + public Object evaluate(List list) throws XPathFunctionException { + if (list == null || list.size() != 2) { + throw new XPathFunctionException("FindToy requires two args: name and value"); + } + + String eleName = (String)list.get(0); + String eleValue = (String)list.get(1); + NodeList toys = doc.getElementsByTagName("toy"); + + for (int i = 0; i 0) { + String text = children.item(0).getTextContent(); + if (eleValue.equals(text)) { + return toy; + } + } + } + return null; + } + } +} diff --git a/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.xml b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.xml new file mode 100644 index 00000000000..52c677fd72e --- /dev/null +++ b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathFunctionTest.xml @@ -0,0 +1,12 @@ + + + + + Some toy + 10 + + + Another toy + 20 + +