Skip to content

Commit 9dcc916

Browse files
author
ehennum
committed
Bug:25956 glue for XPath interface to DOM documents
git-svn-id: svn+ssh://svn.marklogic.com/project/engsvn/client-api/java/branches/b2_0@162644 62cac252-8da6-4816-9e9d-6dc37b19578c
1 parent 973ea40 commit 9dcc916

File tree

5 files changed

+188
-9
lines changed

5 files changed

+188
-9
lines changed

src/main/java/com/marklogic/client/example/cookbook/AllCookbookExamples.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.IOException;
1919

2020
import javax.xml.bind.JAXBException;
21+
import javax.xml.xpath.XPathExpressionException;
2122

2223
import com.marklogic.client.FailedRequestException;
2324
import com.marklogic.client.ForbiddenUserException;
@@ -32,7 +33,7 @@
3233
*/
3334
public class AllCookbookExamples {
3435
public static void main(String[] args)
35-
throws IOException, JAXBException, ResourceNotFoundException, ForbiddenUserException, FailedRequestException, ResourceNotResendableException {
36+
throws IOException, JAXBException, ResourceNotFoundException, ForbiddenUserException, FailedRequestException, ResourceNotResendableException, XPathExpressionException {
3637
ExampleProperties props = Util.loadProperties();
3738

3839
// execute the examples

src/main/java/com/marklogic/client/example/cookbook/DocumentRead.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.io.IOException;
2020
import java.io.InputStream;
2121

22+
import javax.xml.xpath.XPathExpressionException;
23+
2224
import org.w3c.dom.Document;
2325

2426
import com.marklogic.client.DatabaseClient;
@@ -32,11 +34,13 @@
3234
* DocumentReader illustrates how to read the content of a database document.
3335
*/
3436
public class DocumentRead {
35-
public static void main(String[] args) throws IOException {
37+
public static void main(String[] args)
38+
throws IOException, XPathExpressionException {
3639
run(Util.loadProperties());
3740
}
3841

39-
public static void run(ExampleProperties props) throws IOException {
42+
public static void run(ExampleProperties props)
43+
throws IOException, XPathExpressionException {
4044
System.out.println("example: "+DocumentRead.class.getName());
4145

4246
// create the client
@@ -69,7 +73,8 @@ public static void runShortcut(DatabaseClient client) throws IOException {
6973
String rootName = document.getDocumentElement().getTagName();
7074
System.out.println("(Shortcut) Read "+docId+" content with the <"+rootName+"/> root element");
7175
}
72-
public static void runStrongTyped(DatabaseClient client) throws IOException {
76+
public static void runStrongTyped(DatabaseClient client)
77+
throws IOException, XPathExpressionException {
7378
// create a manager for XML documents
7479
XMLDocumentManager docMgr = client.newXMLDocumentManager();
7580

@@ -83,9 +88,12 @@ public static void runStrongTyped(DatabaseClient client) throws IOException {
8388
docMgr.read(docId, handle);
8489
Document document = handle.get();
8590

91+
// apply an XPath 1.0 expression to the document
92+
String productName = handle.evaluateXPath("string(/product/name)", String.class);
93+
8694
// access the document content
8795
String rootName = document.getDocumentElement().getTagName();
88-
System.out.println("(Strong Typed) Read /example/"+docId+" content with the <"+rootName+"/> root element");
96+
System.out.println("(Strong Typed) Read /example/"+docId+" content with the <"+rootName+"/> root element for the "+productName+" product");
8997
}
9098

9199
// set up by writing document content for the example to read

src/main/java/com/marklogic/client/io/DOMHandle.java

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,21 @@
2222
import java.io.OutputStream;
2323
import java.io.UnsupportedEncodingException;
2424

25+
import javax.xml.namespace.QName;
2526
import javax.xml.parsers.DocumentBuilderFactory;
2627
import javax.xml.parsers.ParserConfigurationException;
28+
import javax.xml.xpath.XPath;
29+
import javax.xml.xpath.XPathConstants;
30+
import javax.xml.xpath.XPathExpression;
31+
import javax.xml.xpath.XPathExpressionException;
32+
import javax.xml.xpath.XPathFactory;
2733

2834
import org.slf4j.Logger;
2935
import org.slf4j.LoggerFactory;
3036
import org.w3c.dom.DOMException;
3137
import org.w3c.dom.Document;
38+
import org.w3c.dom.Node;
39+
import org.w3c.dom.NodeList;
3240
import org.w3c.dom.ls.DOMImplementationLS;
3341
import org.w3c.dom.ls.LSException;
3442
import org.w3c.dom.ls.LSInput;
@@ -60,6 +68,7 @@ public class DOMHandle
6068
private LSResourceResolver resolver;
6169
private Document content;
6270
private DocumentBuilderFactory factory;
71+
private XPath xpathProcessor;
6372

6473
/**
6574
* Creates a factory to create a DOMHandle instance for a DOM document.
@@ -221,6 +230,133 @@ protected DocumentBuilderFactory makeDocumentBuilderFactory() throws ParserConfi
221230
return factory;
222231
}
223232

233+
/**
234+
* Get the processor used to evaluate XPath expressions.
235+
* You might get the XPath processor to configure it. For instance,
236+
* you can configure the XPath processor to declare namespace
237+
* bindings or to set a function or variable resolver.
238+
* @see com.marklogic.client.util.EditableNamespaceContext
239+
* @return the XPath expression processor
240+
*/
241+
public XPath getXPathProcessor() {
242+
if (xpathProcessor == null)
243+
xpathProcessor = makeXPathProcessorFactory().newXPath();
244+
return xpathProcessor;
245+
}
246+
/**
247+
* Specifies the processor used to evaluate XPath expressions against
248+
* the document.
249+
* @param xpathProcessor the XPath expression processor
250+
*/
251+
public void setXPathProcessor(XPath xpathProcessor) {
252+
this.xpathProcessor = xpathProcessor;
253+
}
254+
protected XPathFactory makeXPathProcessorFactory() {
255+
return XPathFactory.newInstance();
256+
}
257+
258+
/**
259+
* Evaluate a string XPath expression against the retrieved document.
260+
* An XPath expression can return a Node or subinterface such as
261+
* Element or Text, a NodeList, or a Boolean, Number, or String value.
262+
* @param xpathExpression the XPath expression as a string
263+
* @param as the type of the value
264+
* @return the value produced by the XPath expression
265+
*/
266+
public <T> T evaluateXPath(String xpathExpression, Class<T> as)
267+
throws XPathExpressionException {
268+
return evaluateXPath(xpathExpression, get(), as);
269+
}
270+
/**
271+
* Evaluate a string XPath expression relative to a node such as a node
272+
* returned by a previous XPath expression.
273+
* An XPath expression can return a Node or subinterface such as
274+
* Element or Text, a NodeList, or a Boolean, Number, or String value.
275+
* @param xpathExpression the XPath expression as a string
276+
* @param context the node for evaluating the expression
277+
* @param as the type of the value
278+
* @return the value produced by the XPath expression
279+
*/
280+
public <T> T evaluateXPath(String xpathExpression, Node context, Class<T> as)
281+
throws XPathExpressionException {
282+
checkContext(context);
283+
return castAs(
284+
getXPathProcessor().evaluate(xpathExpression, context, returnXPathConstant(as)),
285+
as
286+
);
287+
}
288+
/**
289+
* Compile an XPath string expression for efficient evaluation later.
290+
* @param xpathExpression the XPath expression as a string
291+
* @return the compiled XPath expression
292+
*/
293+
public XPathExpression compileXPath(String xpathExpression)
294+
throws XPathExpressionException {
295+
return getXPathProcessor().compile(xpathExpression);
296+
}
297+
/**
298+
* Evaluate a compiled XPath expression against the retrieved document.
299+
* An XPath expression can return a Node or subinterface such as
300+
* Element or Text, a NodeList, or a Boolean, Number, or String value.
301+
* @param xpathExpression an XPath expression compiled previously
302+
* @param as the type of the value
303+
* @return the value produced by the XPath expression
304+
*/
305+
public <T> T evaluateXPath(XPathExpression xpathExpression, Class<T> as)
306+
throws XPathExpressionException {
307+
return evaluateXPath(xpathExpression, get(), as);
308+
}
309+
/**
310+
* Evaluate a compiled XPath expression relative to a node such as a node
311+
* returned by a previous XPath expression.
312+
* An XPath expression can return a Node or subinterface such as
313+
* Element or Text, a NodeList, or a Boolean, Number, or String value.
314+
* @param xpathExpression an XPath expression compiled previously
315+
* @param context the node for evaluating the expression
316+
* @param as the type of the value
317+
* @return the value produced by the XPath expression
318+
*/
319+
public <T> T evaluateXPath(XPathExpression xpathExpression, Node context, Class<T> as)
320+
throws XPathExpressionException {
321+
checkContext(context);
322+
return castAs(
323+
xpathExpression.evaluate(context, returnXPathConstant(as)),
324+
as
325+
);
326+
}
327+
protected void checkContext(Node context) {
328+
if (context == null) {
329+
throw new IllegalStateException("Cannot process empty context");
330+
}
331+
}
332+
protected QName returnXPathConstant(Class<?> as) {
333+
if (as == null) {
334+
throw new IllegalArgumentException("cannot execute XPath as null");
335+
} else if (Node.class.isAssignableFrom(as)) {
336+
return XPathConstants.NODE;
337+
} else if (NodeList.class.isAssignableFrom(as)) {
338+
return XPathConstants.NODESET;
339+
} else if (String.class.isAssignableFrom(as)) {
340+
return XPathConstants.STRING;
341+
} else if (Number.class.isAssignableFrom(as)) {
342+
return XPathConstants.NUMBER;
343+
} else if (Boolean.class.isAssignableFrom(as)) {
344+
return XPathConstants.BOOLEAN;
345+
}
346+
throw new IllegalArgumentException("cannot execute XPath as "+as.getName());
347+
}
348+
protected <T> T castAs(Object result, Class<?> as) {
349+
if (result == null) {
350+
return null;
351+
}
352+
if (!as.isAssignableFrom(result.getClass())) {
353+
throw new IllegalArgumentException("cannot cast "+result.getClass().getName()+" to "+as.getName());
354+
}
355+
@SuppressWarnings("unchecked")
356+
T typedResult = (T) result;
357+
return typedResult;
358+
}
359+
224360
@Override
225361
protected Class<InputStream> receiveAs() {
226362
return InputStream.class;

src/test/java/com/marklogic/client/test/DOMSearchResultTest.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,24 @@
1616
package com.marklogic.client.test;
1717

1818
import static org.junit.Assert.assertNotNull;
19+
import static org.junit.Assert.assertEquals;
1920

2021
import java.io.IOException;
2122

2223
import javax.xml.parsers.ParserConfigurationException;
24+
import javax.xml.xpath.XPathExpression;
25+
import javax.xml.xpath.XPathExpressionException;
2326

2427
import org.junit.AfterClass;
2528
import org.junit.BeforeClass;
2629
import org.junit.Test;
2730
import org.w3c.dom.Document;
31+
import org.w3c.dom.Element;
32+
import org.w3c.dom.NodeList;
2833

2934
import com.marklogic.client.query.QueryManager;
3035
import com.marklogic.client.query.StringQueryDefinition;
36+
import com.marklogic.client.util.EditableNamespaceContext;
3137
import com.marklogic.client.io.DOMHandle;
3238

3339
public class DOMSearchResultTest {
@@ -42,14 +48,38 @@ public static void afterClass() {
4248
}
4349

4450
@Test
45-
public void testStringSearch() throws IOException, ParserConfigurationException {
51+
public void testStringSearch()
52+
throws IOException, ParserConfigurationException, XPathExpressionException {
4653
// This test really just exists to show how to get search results in other formats.
4754
QueryManager queryMgr = Common.client.newQueryManager();
4855
StringQueryDefinition qdef = queryMgr.newStringDefinition(null);
4956
qdef.setCriteria("leaf3");
5057

51-
DOMHandle results = queryMgr.search(qdef, new DOMHandle());
52-
Document doc = results.get();
53-
assertNotNull(doc);
58+
DOMHandle responseHandle = queryMgr.search(qdef, new DOMHandle());
59+
Document doc = responseHandle.get();
60+
assertNotNull("null response document", doc);
61+
62+
// configure namespace bindings for the XPath processor
63+
EditableNamespaceContext namespaces = new EditableNamespaceContext();
64+
namespaces.setNamespaceURI("search", "http://marklogic.com/appservices/search");
65+
responseHandle.getXPathProcessor().setNamespaceContext(namespaces);
66+
67+
// string expression against the document
68+
String total = responseHandle.evaluateXPath("string(/search:response/@total)", String.class);
69+
assertNotNull("null total", total);
70+
71+
// compiled expression against the document
72+
XPathExpression resultsExpression = responseHandle.compileXPath("/search:response/search:result");
73+
NodeList resultList = responseHandle.evaluateXPath(resultsExpression, NodeList.class);
74+
assertNotNull("null result list", resultList);
75+
assertEquals("result count mismatch", resultList.getLength(), Integer.parseInt(total));
76+
77+
// string expression against an element
78+
Element firstResult = responseHandle.evaluateXPath("/search:response/search:result[1]", Element.class);
79+
assertNotNull("null first result", firstResult);
80+
81+
String firstResultUri = responseHandle.evaluateXPath("string(@uri)", firstResult, String.class);
82+
String firstItemUri = ((Element) resultList.item(0)).getAttribute("uri");
83+
assertEquals("first uri mismatch", firstResultUri, firstItemUri);
5484
}
5585
}

src/test/java/com/marklogic/client/test/example/cookbook/DocumentReadTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import java.io.IOException;
2121

22+
import javax.xml.xpath.XPathExpressionException;
23+
2224
import org.junit.Test;
2325

2426
import com.marklogic.client.example.cookbook.DocumentRead;
@@ -32,6 +34,8 @@ public void testMain() {
3234
succeeded = true;
3335
} catch (IOException e) {
3436
e.printStackTrace();
37+
} catch (XPathExpressionException e) {
38+
e.printStackTrace();
3539
}
3640
assertTrue("DocumentRead example failed", succeeded);
3741
}

0 commit comments

Comments
 (0)