How to keep xml attribute in fasterXml Jackson XmlMapper? - java

I am writing test cases which test generated xml structures. I am supplying the xml structures via an xml file. I am using currently FasterXMLs Jackson XmlMapper for reading and testing for expected xml.
Java: adoptopenjdk 11
Maven: 3.6.3
JUnit (Jupiter): 5.7.1 (JUnit Jupiter)
Mapper: com.fasterxml.jackson.dataformat.xml.XmlMapper
Dependency: <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.11.4</version>
</dependency>
I have an xml file which contains expected xml (e.g.: /test/testcases.xml:
<testcases>
<testcase1>
<response>
<sizegroup-list>
<sizeGroup id="1">
<sizes>
<size>
<technicalSize>38</technicalSize>
<textSize>38</textSize>
<size>
<size>
<technicalSize>705</technicalSize>
<textSize>110cm</textSize>
<size>
</sizes>
</sizeGroup-list>
</response>
</testcase1>
</testcases>
My code looks like this (simplified):
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class Testcases {
private static final String OBJECT_NODE_START_TAG = "<ObjectNode>";
private static final String OBJECT_NODE_CLOSE_TAG = "</ObjectNode>";
private static final String TESTCASES_XML = "/test/testcases.xml";
private static final XmlMapper XML_MAPPER = new XmlMapper();
#Test
void testcase1() throws Exception {
final String nodePtr = "/testcase1/response";
try (InputStream inputStream = new FileInputStream(TESTCASES_XML)) {
JsonNode rootNode = XML_MAPPER.readTree(inputStream);
JsonNode subNode = rootNode.at(nodePtr);
if (subNode.isMissingNode()) {
throw new IllegalArgumentException(
"Node '" + nodePtr + "' not found in file " + TESTCASES_XML);
}
String expectedXml = XML_MAPPER.writeValueAsString(subNode);
expectedXml = unwrapObjectNode(expectedXml);
// Testcalls, e.g. someService.generateXmlData()
String generatedXml = "...";
assertEquals(expectedXml, generatedXml);
};
}
// FIXME: Ugly: Tell XmlMapper to unwrap ObjectNode automatically
private String unwrapObjectNode(String xmlString) {
if(StringUtils.isBlank(xmlString)) {
return xmlString;
}
if(xmlString.startsWith(OBJECT_NODE_START_TAG)) {
xmlString = xmlString.substring(OBJECT_NODE_START_TAG.length());
if(xmlString.endsWith(OBJECT_NODE_CLOSE_TAG)) {
xmlString = xmlString.substring(0, xmlString.length() - OBJECT_NODE_CLOSE_TAG.length());
}
}
return xmlString;
}
}
But the returned expected xml looks like this:
<sizegroup-list>
<sizeGroup>
<id>1</id>
<sizes>
<size>
<technicalSize>38</technicalSize>
<textSize>38</textSize>
<size>
<size>
<technicalSize>705</technicalSize>
<textSize>110cm</textSize>
<size>
</sizes>
</sizeGroup-list>
The former attribute id of the element sizeGroup gets mapped as a sub element and fails my test. How can I tell XmlMapper to keep the attributes of xml elements?
Best regards,
David

i was not able to tell XmlMapper to keep the attributes of xml tags from the loaded xml file. But i have found another way by parsing xml test data with xPath expressions.
A simple String.equals(...) proofed to be unreliable if expected and actual xml contain different whitespaces or xml tag order. Luckily there is a library for comparing xml. XmlUnit!
Additional dependency (seems to be present as transitive dependency as of Spring Boot 2.6.x):
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<!-- version transitive in spring-boot-starter-parent 2.6.7 -->
<version>2.8.4</version>
<scope>test</test>
</dependency>
ResourceUtil.java:
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
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.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URL;
public class ResourceUtil {
private static final DocumentBuilderFactory XML_DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final XPathFactory X_PATH_FACTORY = XPathFactory.newInstance();
private ResourceUtil() {}
/** Reads an xml file named after the testcase class (e.g. MyTestcase.class
* -> MyTestcase.xml) and parses the data at the supplied xPath expression. */
public static String xmlData(Class<?> testClass, String xPathExpression) {
return getXmlDocumentAsString(testClass, testClass.getSimpleName() + ".xml", xPathExpression);
}
/** Reads the specified xml file and parses the data at the supplied xPath
* expression. The xml file is expected in the same package/directory as
* the testcase class. */
private static String getXmlDocumentAsString(Class<?> ctxtClass, String fileName, String xPathExpression) {
Document xmlDocument = getXmlDocument(ctxtClass, fileName);
XPath xPath = X_PATH_FACTORY.newXPath();
try {
Node subNode = (Node)xPath.compile(xPathExpression).evaluate(xmlDocument, XPathConstants.NODE);
return nodeToString(subNode.getChildNodes());
} catch (TransformerException | XPathExpressionException var6) {
throw new IllegalArgumentException("Unable to read value of '" + xPathExpression + "' from file " + fileName, var6);
}
}
/** Reads the specified xml file and returns a Document instance of the
* xml data. The xml file is expected in the same package/directory as
* the testcase class. */
private static Document getXmlDocument(Class<?> ctxtClass, String xmlFileName) {
InputStream inputStream = getResourceFile(ctxtClass, xmlFileName);
try {
DocumentBuilder builder = XML_DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
return builder.parse(inputStream);
} catch (SAXException | IOException | ParserConfigurationException var4) {
throw new IllegalStateException("Unable to read xml content from file '" + xmlFileName + "'.", var4);
}
}
/** Returns an InputStream of the specified xml file. The xml file is
* expected in the same package/directory as the testcase class. */
private static InputStream getResourceFile(Class<?> ctxtClass, String fileName) {
String pkgPath = StringUtils.replaceChars(ctxtClass.getPackage().getName(), ".", "/");
String filePath = "/" + pkgPath + "/" + fileName;
URL url = ctxtClass.getResource(filePath);
if (url == null) {
throw new IllegalArgumentException("Resource file not found: " + filePath);
}
return ResourceTestUtil.class.getResourceAsStream(filePath);
}
/** Deserializes a NodeList to a String with (formatted) xml. */
private static String nodeToString(NodeList nodeList) throws TransformerException {
StringWriter buf = new StringWriter();
Transformer xform = TransformerFactory.newInstance().newTransformer(getXsltAsResource());
xform.setOutputProperty("omit-xml-declaration", "yes");
xform.setOutputProperty("indent", "no");
for(int i = 0; i < nodeList.getLength(); ++i) {
xform.transform(new DOMSource(nodeList.item(i)), new StreamResult(buf));
}
return buf.toString().trim();
}
/** Returns a Source of an XSLT file for formatting xml data */
private static Source getXsltAsResource() {
return new StreamSource(ResourceTestUtil.class.getResourceAsStream("xmlstylesheet.xslt"));
}
xmlstylesheet.xslt (works for me, you may alter to your preferences):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
MyTestcase.java:
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.DefaultNodeMatcher;
import org.xmlunit.diff.Diff;
import org.xmlunit.diff.ElementSelectors;
import static ResourceUtil.xmldata;
public class MyTestcase {
#Test
void testcase1() {
// Execute logic to generate xml
String xml = ...
assertXmlEquals(xmlData(getClass(), "/test/testcase1/result"), xml);
}
/** Compare xml using XmlUnit assertion. Expected and actual xml need
* to be equal in content (ignoring whitespace and xml tag order) */
void assertXmlEquals(String expectedXml, String testXml) {
Diff diff = DiffBuilder.compare(expectedXml)
.withTest(testXml)
.ignoreWhitespace()
.checkForSimilar()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText, ElementSelectors.byName))
.build();
assertFalse(diff.fullDescription(), diff.hasDifferences());
}
}
MyTestcase.xml:
<test>
<testcase1>
<result>
<myData>
...
</myData>
</result>
</testcase1>
</test>
Best regards,
David

Related

Retrieve element with Jdom / XPath and "

I'm working on an application that has these kinds of xml file (document.xml):
<root>
<subRoot myAttribute="CN=Ok">
Ok
</subRoot>
<subRoot myAttribute="CN="Problem"">
Problem
</subRoot>
</root>
I need to retrieve Element's using XPath expressions. I'm not able to retrieve the second element, which I need to select using the value of myAttribute. This is due to the " character ...
Here is a test class. The second assertion is throwing an AssertionError because the object is null.
import static org.junit.Assert.assertNotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;
import org.junit.Test;
public class XPathTest {
#Test
public void quotesXpath() throws JDOMException, IOException {
Document document = getDocumentFromContent(getClasspathResource("document.xml"));
String okXPath = "/root/subRoot[#myAttribute=\"CN=Ok\"]";
assertNotNull(getElement(document, okXPath)); // Ok ...
String problemXPath = "/root/subRoot[#myAttribute=\"CN="Problem"\"]";
assertNotNull(getElement(document, problemXPath)); // Why null ?
}
public String getClasspathResource(String filePath) throws IOException {
try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(filePath)) {
return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
}
public static Document getDocumentFromContent(String content) throws IOException, JDOMException {
try (InputStream is = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
SAXBuilder builder = new SAXBuilder();
return builder.build(is);
}
}
public Element getElement(Document document, String xpathExpression) throws JDOMException {
XPath xpath = XPath.newInstance(xpathExpression);
return (Element) xpath.selectSingleNode(document);
}
}
The application is using Jdom 1.1.3
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
</dependency>
How can I change my xpath expression so that the second element is returned ? Is this possible with this version of Jdom ?
Thank you for your help !
Try this expression:
String problemXPath = "/root/subRoot[#myAttribute='CN=\"Problem\"']";
Firstly, when the document is parsed, the entity " is replaced with the " character, so that should be used directly in the XPath expression.
Secondly, in XPath you can use either single or double quotes for string constants, which is convenient if you have strings that contain quotes.

Using JAXB to extract content of several XML elements as text

I have the following XML file
<items>
<title>blabla</title>
<text>123</text>
</items>
I'm unmarshalling the XML to the next java object by JAXB and XmlAnyElement annotation with two classes implementing DOMHandler. I want to extract the inner XML of elements "title" and "text" as Strings.
public class Item implements Serializable {
private String title;
private String text;
public String getTitle() {
return title;
}
#XmlAnyElement(value = TitleHandler.class)
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
#XmlAnyElement(value = TextHandler.class)
public void setText(String text) {
this.text = text;
}
}
But when i put a breakpoints in the method "String getElement(StreamResult rt)" of the TitleHandler and the TextHandler, both of elements use TextHandler.class for unmarshalling. Element "title" use TextHandler instead of TitleHandler.
Any help will be greatly appriciated
UPDATE
Restriction usage constraints for XmlAnyElement annotation:
There can be only one XmlAnyElement annotated JavaBean property in a class and its super classes.
The #XmlAnyElement annotation is used as a catch-all for elements in the XML input that aren't mapped by name to some specific property. That's why there can be only one such annotation per class (including inherited properties). What you want is this:
public class Item implements Serializable {
private String title;
private String text;
public String getTitle() {
return title;
}
#XmlElement(name = "title")
#XmlJavaTypeAdapter(value = TitleHandler.class)
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
#XmlElement(name = "text")
#XmlJavaTypeAdapter(value = TextHandler.class)
public void setText(String text) {
this.text = text;
}
}
The #XmlElement annotation indicates that the corresponding property is mapped to elements with that name. So the Java text property derives from the XML <text> element, and the title property from the <title> element. Since the names of the properties and the elements are the same, this is also the default behavior without the #XmlElement annotations, so you could leave them out.
In order to handle the conversion from XML content to a String instead of an actual structure (like a Title class or Text class) you'll need an adapter. that's what the #XmlJavaTypeAdapter annotation is for. It specifies how marshalling/unmarshalling for that property must be handled.
See this useful answer: https://stackoverflow.com/a/18341694/630136
An example of how you could implement TitleHandler.
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class TitleHandler extends XmlAdapter<Object, String> {
/**
* Factory for building DOM documents.
*/
private final DocumentBuilderFactory docBuilderFactory;
/**
* Factory for building transformers.
*/
private final TransformerFactory transformerFactory;
public TitleHandler() {
docBuilderFactory = DocumentBuilderFactory.newInstance();
transformerFactory = TransformerFactory.newInstance();
}
#Override
public String unmarshal(Object v) throws Exception {
// The provided Object is a DOM Element
Element titleElement = (Element) v;
// Getting the "a" child elements
NodeList anchorElements = titleElement.getElementsByTagName("a");
// If there's none or multiple, return empty string
if (anchorElements.getLength() != 1) {
return "";
}
Element anchor = (Element) anchorElements.item(0);
// Creating a DOMSource as input for the transformer
DOMSource source = new DOMSource(anchor);
// Default transformer: identity tranformer (doesn't alter input)
Transformer transformer = transformerFactory.newTransformer();
// This is necessary to avoid the <?xml ...?> prolog
transformer.setOutputProperty("omit-xml-declaration", "yes");
// Transform to a StringWriter
StringWriter stringWriter = new StringWriter();
StreamResult result = new StreamResult(stringWriter);
transformer.transform(source, result);
// Returning result as string
return stringWriter.toString();
}
#Override
public Object marshal(String v) throws Exception {
// DOM document builder
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
// Creating a new empty document
Document doc = docBuilder.newDocument();
// Creating the <title> element
Element titleElement = doc.createElement("title");
// Setting as the document root
doc.appendChild(titleElement);
// Creating a DOMResult as output for the transformer
DOMResult result = new DOMResult(titleElement);
// Default transformer: identity tranformer (doesn't alter input)
Transformer transformer = transformerFactory.newTransformer();
// String reader from the input and source
StringReader stringReader = new StringReader(v);
StreamSource source = new StreamSource(stringReader);
// Transforming input string to the DOM
transformer.transform(source, result);
// Return DOM root element (<title>) for JAXB marshalling to XML
return doc.getDocumentElement();
}
}
If the type for unmarshalling input/marshalling output is left as Object, JAXB will provide DOM nodes. The above uses XSLT transformations (though without an actual stylesheet, just an "identity" transform) to turn the DOM input into a String and vice-versa. I've tested it on a minimal input document and it works for both XML to an Item object and the other way around.
EDIT:
The following version will handle any XML content in <title> rather than expecting a single <a> element. You'll probably want to turn this into an abstract class and then have TitleHander and TextHandler extend it, so that the currently hardcoded <title> tags are provided by the implementation.
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class TitleHandler extends XmlAdapter<Object, String> {
/**
* Factory for building DOM documents.
*/
private final DocumentBuilderFactory docBuilderFactory;
/**
* Factory for building transformers.
*/
private final TransformerFactory transformerFactory;
/**
* XSLT that will strip the root element. Used to only take the content of an element given
*/
private final static String UNMARSHAL_XSLT = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
"<xsl:transform xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" version=\"1.0\">\n" +
"\n" +
" <xsl:output method=\"xml\" omit-xml-declaration=\"yes\" />\n" +
"\n" +
" <xsl:template match=\"/*\">\n" +
" <xsl:apply-templates select=\"#*|node()\"/>\n" +
" </xsl:template>\n" +
"\n" +
" <xsl:template match=\"#*|node()\">\n" +
" <xsl:copy>\n" +
" <xsl:apply-templates select=\"#*|node()\"/>\n" +
" </xsl:copy>\n" +
" </xsl:template>\n" +
" \n" +
"</xsl:transform>";
public TitleHandler() {
docBuilderFactory = DocumentBuilderFactory.newInstance();
transformerFactory = TransformerFactory.newInstance();
}
#Override
public String unmarshal(Object v) throws Exception {
// The provided Object is a DOM Element
Element rootElement = (Element) v;
// Creating a DOMSource as input for the transformer
DOMSource source = new DOMSource(rootElement);
// Creating a transformer that will strip away the root element
StreamSource xsltSource = new StreamSource(new StringReader(UNMARSHAL_XSLT));
Transformer transformer = transformerFactory.newTransformer(xsltSource);
// Transform to a StringWriter
StringWriter stringWriter = new StringWriter();
StreamResult result = new StreamResult(stringWriter);
transformer.transform(source, result);
// Returning result as string
return stringWriter.toString();
}
#Override
public Object marshal(String v) throws Exception {
// DOM document builder
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
// Creating a new empty document
Document doc = docBuilder.newDocument();
// Creating a DOMResult as output for the transformer
DOMResult result = new DOMResult(doc);
// Default transformer: identity tranformer (doesn't alter input)
Transformer transformer = transformerFactory.newTransformer();
// String reader from the input and source
StringReader stringReader = new StringReader("<title>" + v + "</title>");
StreamSource source = new StreamSource(stringReader);
// Transforming input string to the DOM
transformer.transform(source, result);
// Return DOM root element for JAXB marshalling to XML
return doc.getDocumentElement();
}
}

JAXB marshlling is not ignoring namespace

I have spent some time to investigate what is the problem but I couldn't solve it. When I unmarshal below XML and marshal back I see different XML.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<one>test</one>
<three>\MySG\test.jsp</three>
<two>
<st>
<seeta>
<Source>
<problemtag xmlns="uuid:B89290D2-36FB-4EBC-A581-69B16D59EB92">
<p>deploy_test_page_renderingMetadata</p>
</problemtag>
</Source>
</seeta>
<Template id="tcm:1-63-32" title="Smart Compound Component Template"/>
<Publication id="tcm:0-1-1" title="Publication"/>
</st>
</two>
</root>
In the above xml only one tag (first one) expected remaining all (including namespace) are unexpected elements. Another application sends the above XML.
My Mapping are like this
package com.seeta.xml;
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.XmlAnyElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="root")
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlElement(name="one")
private String one;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
#XmlElement(name="three")
private String three;
#XmlAnyElement
private List<Object> remaining = new ArrayList<Object>();
public String getThree() {
return three;
}
public void setThree(String three) {
this.three = three;
}
public List<Object> getRemaining() {
return remaining;
}
public void setRemaining(List<Object> remaining) {
this.remaining = remaining;
}
public String toString() {
return String.format("One [%s]-> Number of remaing elements [%d]-> three [%s]", one, remaining.size(), three);
}
}
Here is my simple code
package com.seeta.xml;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLDecoder;
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.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class JaxbSample {
public Document getDOMDocument(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
if (inputStream != null) {
return documentBuilder.parse(new InputSource(inputStream));
} else {
return documentBuilder.newDocument();
}
}
public Root unmarshall(Document document) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Root root = (Root) unmarshaller.unmarshal(document);
return root;
}
public Document marshall(Root root) throws JAXBException, ParserConfigurationException, SAXException, IOException {
JAXBContext context = JAXBContext.newInstance(Root.class);
Marshaller marshaller = context.createMarshaller();
Document document = getDOMDocument(null);
marshaller.marshal(root, document);
return document;
}
private String transform(Document document) throws TransformerException {
StringWriter sw = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new DOMSource(document), new StreamResult(sw));
return sw.toString();
}
public void testUnmarshallMarshallUsingDocument() throws ParserConfigurationException, SAXException, IOException, JAXBException, TransformerException {
InputStream inputStream = this.getClass().getResourceAsStream("jaxb.xml");
Document document = getDOMDocument(inputStream);
Root root = unmarshall(document);
Document documentAfterMarshal = marshall(root);
String output = transform(documentAfterMarshal);
System.out.println(output);
}
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, JAXBException, TransformerException {
JaxbSample jaxbTest = new JaxbSample();
jaxbTest.testUnmarshallMarshallUsingDocument();
}
}
output is
<root>
<one>test</one>
<three>\MySG\test.jsp</three>
<two>
<st>
<seeta>
<Source>
<problemtag:problemtag xmlns="uuid:B89290D2-36FB-4EBC-A581-69B16D59EB92" xmlns:problemtag="uuid:B89290D2-36FB-4EBC-A581-69B16D59EB92">
<p>deploy_test_page_renderingMetadata</p>
</problemtag:problemtag>
</Source>
</seeta>
<Template id="tcm:1-63-32" title="Smart Compound Component Template"/>
<Publication id="tcm:0-1-1" title="Publication"/>
</st>
</two>
</root>
And also I tried following
I tried with NamespacePrefixMapper. I can able to give different namespace but not empty(""). I don't want any namespace at all.
new NamespacePrefixMapper() {
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
return "";
}
};
We don't have any xsd ( at least I don't know) in our project for trying unqualified thing
I really didn't understand QName thing
If all you want is to preserve the unused elements and marshall them back, I think you ought to be able to do something like this:
#XmlRootElement(name="Root")
#XmlAccessorType(XmlAccessType.FIELD)
class Root {
#XmlElement(name="One")
private String one;
#XmlAnyElement
private List<Any> otherElements;
}
class AnyAdapter extends XmlAdapter<Element,Any> {
#Override
public Any unmarshal(Element element) throws Exception {
return new Any(element);
}
#Override
public Element marshal(Any any) throws Exception {
return any.element;
}
}
#XmlJavaTypeAdapter(AnyAdapter.class)
class Any {
Element element;
Any(Element element) {
this.element = element;
}
}
I don't want any namespace at all.
You won't be able to accomplish this with JAXB alone. The #XmlAnyElement tells the unmarshaller to dump the elements that it can't process into your list. Those elements have namespaces attached. When you then marshal those elements, they'll be written with their namespaces.
One option is for you to parse the incoming XML with a namespace-unaware DOM parser, then unmarshall it using the DOM tree. There's an example of this in the Unmarshaller JavaDoc (which uses a namespace-aware parser; it should be obvious what to change to make it namespace-unaware).
I really didn't understand QName thing
Do you mean that you don't understand why the output is a qualified name, or why it picked the particular prefix? Or what QNames mean?
It's a qualified name because that's the most unambiguous way to represent the element.
I can't tell you why it picked this particular prefix; the JAXP serializer picks short names like "ns1", "ns2", and so on.

Problem with conversion of org.dom4j.Document to org.w3c.dom.Document and XML Signature

I have some classes that already use DOM4J to read XML files and provide
getter methods to the data. Now, I need to add the possibility of checking XML digital
signatures.
Using org.w3c.dom and following http://java.sun.com/developer/technicalArticles/xml/dig_signature_api/
everything works correctly.
So, I try to use DOMWriter to convert from org.dom4j.Document to
org.w3c.dom.Document, but after this the signature validation doesn't work. I think it
happens because DOMWiter is changing the XML tree (as doc4.asXML() seems to show).
I try to find something to set in order to mantain the integrity of the document, but
DOMWriter don't have such methods.
Below is the code demonstrating the asymmetric conversion.
The file used for tests is http://www.robertodiasduarte.com.br/files/nfe/131090007910044_v1.10-procNFe.xml
Does someone know reasons/workarounds to this?
Thanks (and sorry my poor english).
package testevalidanfe;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import javax.swing.JOptionPane;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.dom4j.io.XMLWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class Testevalidanfe {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.parse("exemplo-nfe.xml");
Node no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), no);
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
JOptionPane.showMessageDialog(null, "Validation using org.w3c.dom: " + signature.validate(valContext));
org.dom4j.io.DOMReader domreader = new org.dom4j.io.DOMReader();
org.dom4j.Document doc4 = domreader.read(d);
org.dom4j.io.DOMWriter domwriter = new org.dom4j.io.DOMWriter();
d = domwriter.write(doc4);
String after = doc4.asXML();
PrintWriter writer = new PrintWriter(new File("after-convertion.xml"));
writer.print(after);
writer.close();
no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
valContext = new DOMValidateContext(new X509KeySelector(), no);
fac = XMLSignatureFactory.getInstance("DOM");
signature = fac.unmarshalXMLSignature(valContext);
JOptionPane.showMessageDialog(null, "Validation after convert: " + signature.validate(valContext));
}
}
package testevalidanfe;
import java.security.Key;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.X509Data;
public class X509KeySelector extends KeySelector {
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context)
throws KeySelectorException {
Iterator ki = keyInfo.getContent().iterator();
while (ki.hasNext()) {
XMLStructure info = (XMLStructure) ki.next();
if (!(info instanceof X509Data))
continue;
X509Data x509Data = (X509Data) info;
Iterator xi = x509Data.getContent().iterator();
while (xi.hasNext()) {
Object o = xi.next();
if (!(o instanceof X509Certificate))
continue;
final PublicKey key = ((X509Certificate)o).getPublicKey();
if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
return new KeySelectorResult() {
public Key getKey() { return key; }
};
}
}
}
throw new KeySelectorException("No key found!");
}
static boolean algEquals(String algURI, String algName) {
if ((algName.equalsIgnoreCase("DSA") &&
algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
(algName.equalsIgnoreCase("RSA") &&
algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
return true;
} else {
return false;
}
}
}
For example, if the original XML starts with:
<nfeProc versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
<infNFe Id="NFe31090807301671000131550010001000216008030809" versao="1.10" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
doc4.asXML() return this:
<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.10">
<NFe>
<infNFe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="NFe31090807301671000131550010001000216008030809" versao="1.10">
...
I had a closer look at this, and it turns out that DOM4J DOMWriter is doing something odd w.r.t. namespaces that obviously confuses the canonicalization process. I haven't pin pointed the exact reason, but I think it has to do with DOMWriter inserting extra xmlns attributes in the DOM elements. You can see the effect if you turn on logging for the XML digital signature API (as described in the article you refer to), the canonicalized <SignedInfo> element lacks namespace declaration in the DOM document produced by DOM4J.
However, instead of using DOMWriter, you can produce a DOM document by transformation, using a DOM4J DocumentSource and a DOMResult.
/**
* Create a DOM document from a DOM4J document
*/
static Document copy(org.dom4j.Document orig) {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
DOMResult result = new DOMResult();
t.transform(new DocumentSource(orig), result);
return (Document) result.getNode();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Using the resulting DOM document, the validation works.

Using JDOM to Parse XML file with external DTD that has not been declared in the XML file

In my XML file I have some entities such as ’
So I have created a DTD tag for my XML document to define these entities. Below is the Java code used to read the XML file.
SAXBuilder builder = new SAXBuilder();
URL url = new URL("http://127.0.0.1:8080/sample/subject.xml");
InputStream stream = url.openStream();
org.jdom.Document document = builder.build(stream);
Element root = document.getRootElement();
Element name = root.getChild("name");
result = name.getText();
System.err.println(result);
How can I change the Java code to retrieve a DTD over HTTP to allow the parsing of my XML document to be error free?
Simplified example of the xml document.
<main>
<name>hello ‘ world ’ foo & bar </name>
</main>
One way to do this would be to read the document and then validate it with the transformer:
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class ValidateWithExternalDTD {
private static final String URL = "http://127.0.0.1:8080/sample/subject.xml";
private static final String DTD = "http://127.0.0.1/YourDTD.dtd";
public static void main(String args[]) {
try {
DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance();
factory.setValidating(true);
DocumentBuilder builder = factory.newDocumentBuilder();
// Set the error handler
builder.setErrorHandler(new org.xml.sax.ErrorHandler() {
public void fatalError(SAXParseException spex)
throws SAXException {
// output error and exit
spex.printStackTrace();
System.exit(0);
}
public void error(SAXParseException spex)
throws SAXParseException {
// output error and continue
spex.printStackTrace();
}
public void warning(SAXParseException spex)
throws SAXParseException {
// output warning and continue
spex.printStackTrace();
}
});
// Read the document
URL url = new URL(ValidateWithExternalDTD.URL);
Document xmlDocument = builder.parse(url.openStream());
DOMSource source = new DOMSource(xmlDocument);
// Use the tranformer to validate the document
StreamResult result = new StreamResult(System.out);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, ValidateWithExternalDTD.DTD);
transformer.transform(source, result);
// Process your document if everything is OK
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Another way would be to replace the XML title with the XML title plus the DTD reference
Replace this:
<?xml version = "1.0"?>
with this:
<?xml version = "1.0"?><!DOCTYPE ...>
Of course you would replace the first occurance only and not try to go through the whole xml document
You have to instantiate the SAXBuilder by passing true(validate) to its constructor:
SAXBuilder builder = new SAXBuilder(true);
or call:
builder.setValidation(true)

Categories