Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » EclipseLink » MOXy XPath expression compatibility with DOM and javax.xml.xpath.XPath(MOXy has an excellent facility to traverse JAXB object trees, but there seem to be a few incompatibilities with how you would code XPaths for traversing a DOM)
MOXy XPath expression compatibility with DOM and javax.xml.xpath.XPath [message #1234724] Wed, 22 January 2014 12:06 Go to next message
J F is currently offline J F
Messages: 242
Registered: July 2009
Senior Member
MOXy has a useful mechanism to allow XPath expressions to be used on JAXB annotated POJO. However there some XPath expressions do not seem to function in the same way when used against a org.w3c.dom.Document or Node.

The problem is ofcourse that whereas an XPath constructed against a Node has access to the DOM, because a Node normally has a parent, there is no equivalent in a POJO containment hierarchy. In my examples, Child and Leaf have no 'back pointers' to their parents for example. So unless some context were kept from the unmarshalling on a File then there is no way to do '//x' (i.e. select all 'x's from the document element root) or '..'. If some context were kept then this would imply some sort of overhead, and when I am streaming I do not want that (see the restrictions that XSLT has in its streaming mode). If I am just using POJOs then I don't have any context anyway. So the 'edge cases' I encountered below do not seem feasible. The fact that '.' does not work is a bit disappointing though.

I have left this 'question' incase any other users are walking backwards into pojo navigation from DOM/XSLT and XPath like me.

1) A POJO hierarchy has no 'invisible' Document Element Root
2) Traversing 'outside' the current context.
3) Lack of support for '.'

I have included examples to show what I mean, if someone can tell me where I am going wrong, or whether there is any chance that changes to make things more compatible would ever be contemplated, I would be most grateful.



Here are the JAXB annotated POJOs. They are in the same package and are specified via jaxb.index

package uk.co.his.test.jaxb.model;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType( XmlAccessType.FIELD )
@XmlRootElement(name="Root")
public class Root {
	@XmlElement(name="name")
	public String name;
	@XmlElement(name="children")
	List<Child> children = new ArrayList<Child>();
	
	public void addChild(Child child) {
		children.add(child);
	}
	
	public void printOut(PrintStream w) {
		w.println("Root " + name);
		for(Child c: children) {
			c.printOut(w);
		}
	}
}


package uk.co.his.test.jaxb.model;

import java.io.PrintStream;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType( XmlAccessType.FIELD )
@XmlType
public class Child {
	@XmlElement(name="name")
	public String name;
	@XmlElement(name="Leaf")
	public Leaf leaf;
	
	public void printOut(PrintStream w) {
		w.println("\tChild " + name);
		if(leaf != null) {
			leaf.printOut(w);
		}
		else {
			w.println("\t\tNo Leaf");
		}
	}
	
}

package uk.co.his.test.jaxb.model;

import java.io.PrintStream;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType( XmlAccessType.FIELD )
@XmlType
public class Leaf {
	@XmlElement(name="name")
	public String name;
	
	public void printOut(PrintStream w) {
		w.println("\t\tLeaf " + name);
	}

}




And here is the test case;

package uk.co.his.test.jaxb.junit;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import junit.framework.Assert;

import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import uk.co.his.test.jaxb.model.Child;
import uk.co.his.test.jaxb.model.Leaf;
import uk.co.his.test.jaxb.model.Root;

public class TestMoxy3 {
	
	public static final File test1 = new File("test-output/Test1.xml");

	
	@BeforeClass
	public static void boostrap() throws JAXBException, IOException, SAXException, TransformerException {
		createFile(test1);
	}
	
	@Test
	public void testXPathAndMoxy() throws JAXBException, SAXException, IOException, ParserConfigurationException, XPathExpressionException {
		
		org.eclipse.persistence.jaxb.JAXBContext c2 = createJAXBContext();
		Unmarshaller u = c2.createUnmarshaller();
		Root r2 = (Root) u.unmarshal(test1);
		
		DocumentBuilder b = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document d = b.parse(test1);
		XPath xp = XPathFactory.newInstance().newXPath();
		
		
		System.out.println("========= TESTS ===============");
		
		//Edge case (1) The current context in Moxy is already the instance of Root
		String xPath = "Root/children[1]/Leaf/name/text()";
		XPathExpression xpr = xp.compile(xPath);
		String domResultStr = xpr.evaluate(d);
		String jaxbResultStr = c2.getValueByXPath(r2, xPath, null, String.class);
		System.out.println("1) " + xPath + " gives \"" + domResultStr + "\" using DOM and \""+ jaxbResultStr + "\" using Moxy");
		
		//Edge case (2) Xpath expressions using javax.xml.xpath and DOM have a global scope regardless of current context
		//First get some child somewhere in the middle of the hierarchy
		String xPathA = "//children[1]";
		xpr = xp.compile(xPathA);
		Node domResultNode = (Node) xpr.evaluate(d, XPathConstants.NODE);
		Child jaxbResultChild = c2.getValueByXPath(r2, xPathA, null, Child.class);
		
		//Now find out whether we can navigate 'back up' the hierachy
		xPath = "//children[2]/name/text()";
		Assert.assertTrue(xPathA + " gave unexpected null result for javax.xml.xpath and DOM", domResultNode != null);
		xpr = xp.compile(xPath);
		domResultStr = xpr.evaluate(domResultNode);
		
		Assert.assertTrue(xPathA + " gave unexpected null result for MOXy", jaxbResultChild != null);
		jaxbResultStr = c2.getValueByXPath(jaxbResultChild, xPath, null, String.class);
		System.out.println("2) " + xPath + " via " + xPathA + " gives \"" + domResultStr + "\" using DOM and \""+ jaxbResultStr + "\" using Moxy");
		
		//Edge case (3) '/' means the Document Element Root in XPath, but evaluates to the current node in Moxy
		xPath = "/name/text()";
		xpr = xp.compile(xPath);
		domResultStr = xpr.evaluate(domResultNode);
		jaxbResultStr = c2.getValueByXPath(jaxbResultChild, xPath, null, String.class);
		System.out.println("3) " + xPath + " via " + xPathA + " gives \"" + domResultStr + "\" using DOM and \""+ jaxbResultStr + "\" using Moxy");
		

		//BUG (4) '.' means the current context in XPath, but not in MOXy
		xPath = "./name/text()";
		xpr = xp.compile(xPath);
		domResultStr = xpr.evaluate(domResultNode);
		jaxbResultStr = c2.getValueByXPath(jaxbResultChild, xPath, null, String.class);
		System.out.println("4) " + xPath + " via " + xPathA + " gives \"" + domResultStr + "\" using DOM and \""+ jaxbResultStr + "\" using Moxy");
		
		/* This works
		xPath = "name/text()";
		xpr = xp.compile(xPath);
		domResultStr = xpr.evaluate(domResultNode);
		jaxbResultStr = c2.getValueByXPath(jaxbResultChild, xPath, null, String.class);
		System.out.println("4b) " + xPath + " via " + xPathA + " gives \"" + domResultStr + "\" using DOM and \""+ jaxbResultStr + "\" using Moxy");
		*/
	}
	
	
	private static void createFile(File named) throws JAXBException, IOException, SAXException, TransformerException {
		
		Root r = createRoot();
		JAXBContext c1 = createJAXBContext();
		Marshaller m = c1.createMarshaller();
		m.marshal(r, test1);
		System.out.println("===== Here is the target Object Hierachy =======");
		r.printOut(System.out);
		System.out.println("===== Here is resultant XML file contents =======");
		echoFile(test1, System.out);
	}

	private static Root createRoot() {
		Root r = new Root();
		r.name = "RootA";
		for (int i = 1; i <= 10; i++) {
			Child c = new Child();
			c.name = "A" + i;
			Leaf l = new Leaf();
			l.name = "a" + i;
			c.leaf = l;
			r.addChild(c);
		}
		return r;
	}
	
	
	public static void echoFile(File which, PrintStream out) throws SAXException, IOException, TransformerException
	{
		TransformerFactory transFactory = TransformerFactory.newInstance();
		try
		{
			transFactory.setAttribute("indent-number", new Integer(2));
		}
		catch (IllegalArgumentException ignored) //If whatever we pick up doesn't support it, just carry on
		{ 
			System.err.println("Default TransformerFactory " + transFactory.getClass() + " does not support indent-number"); 
		} 
		Transformer transformer = transFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		try
		{
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
		}
		catch (IllegalArgumentException ignored) //If whatever we pick up doesn't support it, just carry on
		{ 
			System.err.println("Default TransformerFactory " + transFactory.getClass() + " does not support {http://xml.apache.org/xslt}indent-amount"); 
		} 
		try
		{
			transformer.transform(new StreamSource(which), new StreamResult(out));
		}
		catch (TransformerException e)
		{
			throw e;
		}
	}
	
	private static org.eclipse.persistence.jaxb.JAXBContext createJAXBContext() throws JAXBException {
		return (org.eclipse.persistence.jaxb.JAXBContext) org.eclipse.persistence.jaxb.JAXBContextFactory.createContext("uk.co.his.test.jaxb.model", null);
	}
	
	
}


  • Attachment: MoxyMix.zip
    (Size: 14.84KB, Downloaded 15 times)

[Updated on: Wed, 22 January 2014 16:54]

Report message to a moderator

Re: MOXy XPath expression compatibility with DOM and javax.xml.xpath.XPath [message #1235158 is a reply to message #1234724] Thu, 23 January 2014 12:33 Go to previous messageGo to next message
Blaise Doughan is currently offline Blaise Doughan
Messages: 163
Registered: July 2009
Senior Member

Hi J F,

Quote:
MOXy has a useful mechanism to allow XPath expressions to be used on JAXB annotated POJO. However there some XPath expressions do not seem to function in the same way when used against a org.w3c.dom.Document or Node.


Thanks, XPath based mapping from day 1 has been our focus. What we support for mapping is a subset of XPath. This support is based on something that can be used for both marshalling and unmarshalling a document efficiently. Below are some samples of XPath that are supported:

  • Attribute - "@id"
  • Element - "address"
  • Element by Position - "address[1]"
  • Element by Predicate - "address[@type='mailing']"
  • Element Text - "name/text()"
  • Text - "text()"
  • Self - "."
  • Combination - "personal-info/name[2]/text()"


Quote:
The problem is ofcourse that whereas an XPath constructed against a Node has access to the DOM, because a Node normally has a parent, there is no equivalent in a POJO containment hierarchy.


MOXy supports relative XPaths. The XPath is always interpreted relative to the node that is currently being processed. This is done so that the document can be marshalled and unmarshalled as efficiently as possible using a single top-down traversal. It would be difficult to support marshalling to SAX/StAX/Stream using any other method without first building an intermediate DOM which would have performance implications.

-Blaise
Re: MOXy XPath expression compatibility with DOM and javax.xml.xpath.XPath [message #1235178 is a reply to message #1235158] Thu, 23 January 2014 13:36 Go to previous messageGo to next message
J F is currently offline J F
Messages: 242
Registered: July 2009
Senior Member
Hi, Thanks for replying, and thanks for your useful blogs which has often helped me understand something, and thanks for the work that MOXy is doing pushing JAXB forward.



Quote:

MOXy supports relative XPaths. The XPath is always interpreted relative to the node that is currently being processed. This is done so that the document can be marshalled and unmarshalled as efficiently as possible using a single top-down traversal. It would be difficult to support marshalling to SAX/StAX/Stream using any other method without first building an intermediate DOM which would have performance implications.


So if I had used more MOXy customizations when annotating and serializing I could have later navigated from my Child instance to the Root? But I would have to use something like @XmlInverseReference yes?

Thanks.
Re: MOXy XPath expression compatibility with DOM and javax.xml.xpath.XPath [message #1244588 is a reply to message #1235178] Wed, 12 February 2014 10:06 Go to previous message
J F is currently offline J F
Messages: 242
Registered: July 2009
Senior Member
Any comments?
Previous Topic:[SOLVED] For each call a new connection? Need only one when using RCP with Gemini or Swing
Next Topic:Query for entity.collection returns List with one element which is null (instead of empty list)
Goto Forum:
  


Current Time: Tue Jul 29 02:41:53 EDT 2014

Powered by FUDForum. Page generated in 0.02066 seconds