I need use a XmlAdapter because I want do not have empty XLM object:
<TACHES>
<TACHE>
<NB_HEURE>7.75</NB_HEURE>
<NOTES>foo</NOTES>
<CODE_TACHE>1234</CODE_TACHE>
</TACHE>
<TACHE> <- I Want delete this part
<NB_HEURE/> <- I Want delete this part
<NOTES/> <- I Want delete this part
<CODE_TACHE/> <- I Want delete this part
</TACHE> <- I Want delete this part
</TACHES>
My POJO is:
#XmlRootElement(name = "TACHES")
public class Tasks {
private List<Task> tasks;
#XmlJavaTypeAdapter(TaskAdapter.class)
#XmlElement(name = "TACHE")
public List<Task> getTasks() {
return tasks;
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
}
}
My XmlAdapter is:
public class TaskAdapter extends XmlAdapter<String, Task> {
#Override
public Task unmarshal(String v) throws Exception {
return null;
}
#Override
public String marshal(Task t) throws Exception {
JAXBContext context = JAXBContext.newInstance(Task.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setProperty(Marshaller.JAXB_FRAGMENT, true);
m.setProperty("jaxb.encoding", "Unicode");
StringWriter sw = new StringWriter();
if ("".equals(t.getNotes()) && "".equals(t.getHourCount()) && "".equals(t.getTaskCode())) {
return null;
}
m.marshal(t, sw);
String s = sw.toString();
s = s.substring(s.indexOf("<TACHE>") + 7, s.indexOf("</TACHE>"));
System.out.println(s);
return s;
}
}
System.out.println of XmlAdapter print:
<NB_HEURE>7.75</NB_HEURE>
<NOTES>foo</NOTES>
<CODE_TACHE>1234</CODE_TACHE>
but, when I marshal global POJO, I have:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
...
<TACHES>
<TACHE>
<NB_HEURE>7.75</NB_HEURE>
<NOTES>foo</NOTES>
<CODE_TACHE>1234</CODE_TACHE>
</TACHE>
</TACHES>
...
My is:
JAXBContext context = JAXBContext.newInstance(MoReports.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
StringWriter sw = new StringWriter();
m.marshal(moReports, sw);
xmlString = sw.toString();
Step 1
I do not use 'TaskAdapter extends XmlAdapter'
Step 2
I set JAXB_FORMATTED_OUTPUT at FALSE
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.FALSE);
Step 3
and I add this code post marshal
xmlString = removeAllEmptyXmlTags(xmlString);
public String removeAllEmptyXmlTags(String xml) {
final String regex1 = "<([a-zA-Z0-9-\\_]*)[^>]*/>";
final String regex2 = "<([a-zA-Z0-9-\\_]*)[^>]*>\\s*</\\1>";
final Pattern pattern1 = Pattern.compile(regex1);
final Pattern pattern2 = Pattern.compile(regex2);
String xmlString = xml;
Matcher matcher1;
Matcher matcher2;
do {
xmlString = xmlString.replaceAll(regex1, "").replaceAll(regex2, "");
matcher1 = pattern1.matcher(xmlString);
matcher2 = pattern2.matcher(xmlString);
} while (matcher1.find() || matcher2.find());
return xmlString;
}
Step 4
and I add this code post marshal
xmlString = xmlIndent(xmlString, false);
public String xmlIndent(String xml, Boolean ommitXmlDeclaration) {
Writer outxml = new StringWriter();
try {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
OutputFormat format = new OutputFormat(doc);
format.setIndenting(true);
format.setIndent(2);
format.setOmitXMLDeclaration(ommitXmlDeclaration);
format.setLineWidth(Integer.MAX_VALUE);
XMLSerializer serializer = new XMLSerializer(outxml, format);
serializer.serialize(doc);
} catch (ParserConfigurationException | SAXException | IOException e) {
return xml;
}
return outxml.toString();
}
Result is OK:
<TACHES>
<TACHE>
<NB_HEURE>7.75</NB_HEURE>
<NOTES>foo</NOTES>
<CODE_TACHE>1234</CODE_TACHE>
</TACHE>
</TACHES>
I am working on an API that will return an JSON response from an XML source. I have used RestTemplate and JAXB to get the XML string from the source and then used StringReader and Unmarshaller to create the Java object. The object looks like this;
#XmlRootElement(name="ItemSearchResponse", namespace="http://webservices.amazon.com/AWSECommerceService/2011-08-01") //
#XmlAccessorType(XmlAccessType.FIELD)
public class SampleXML {
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType
public static class OperationRequest {
#XmlTransient
private String RequestId;
#XmlElement(name="RequestId")
public void setRequestId(String id) {
this.RequestId = id;
}
public String getRequestId() {
return RequestId;
}
...
This is the code that should return the JSON string to the browser;
#RequestMapping("/samplexml2")
public SampleXML CreateXMLFile2 () throws EncoderException, FileNotFoundException, SAXException {
try {
String requestUrl = null;
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new ResponseErrorHandler());
String decodedUrl = "http://localhost:8080/sample.xml";
String response = restTemplate.getForObject(decodedUrl, String.class);
//Prepare JAXB objects
JAXBContext jaxbContext = JAXBContext.newInstance(SampleXML.class);
Unmarshaller u = jaxbContext.createUnmarshaller();
//Create an XMLReader to use with our filter
XMLReader reader = XMLReaderFactory.createXMLReader();
//Create the filter (to add namespace) and set the xmlReader as its parent.
NamespaceFilter inFilter = new NamespaceFilter("http://webservices.amazon.com/AWSECommerceService/2011-08-01", true);
inFilter.setParent(reader);
//Prepare the input, in this case a java.io.File (output)
InputSource is = new InputSource(new StringReader(response));
//Create a SAXSource specifying the filter
SAXSource source = new SAXSource(inFilter, is);
//Do unmarshalling
SampleXML myJaxbObject = (SampleXML) u.unmarshal(source);
//Convert to myJaxbObject to JSON string here;
return myJaxbObject;
} catch (JAXBException e) {
e.printStackTrace();
return null;
}
}
I want to write the conversation at this line; //Convert to myJaxbObject to JSON string here;
I have read many articles that point to this library; https://github.com/FasterXML/jackson-module-jaxb-annotations But I have not been able to use it successfully.
I would like an example that uses Jackson or MOXy dependencies
Have you tried simply changing your RequestMapping to #RequestMapping(value = "/samplexml2", produces = MediaType.APPLICATION_JSON_VALUE)?
I'm using Jaxb to unmarshal XML into a java object. I need to know when new attributes/elements are in the XML and fail. However by default the unmarshaller is ignoring new elements/attributes.
Is there a configuration I can set to fail when new attributes/elements exist in the XML that are not specified in the POJO?
My POJO:
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "ROOT")
public class Model {
private A a;
public A getA() {
return a;
}
#XmlElement(name = "A")
public void setA(A a) {
this.a = a;
}
static class A {
String country;
public String getCountry() {
return country;
}
#XmlAttribute(name = "Country")
public void setCountry(String country) {
this.country = country;
}
}
}
Code to unmarshal:
JAXBContext jaxbContext = JAXBContext.newInstance(Model.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
String sample = "<ROOT>" +
"<A Country=\"0\" NewAttribute=\"0\"></A>" +
"<NEWELEMENT> </NEWELEMENT>" +
"</ROOT>";
InputStream stream = new ByteArrayInputStream(sample.getBytes(StandardCharsets.UTF_8));
Object unmarshal = jaxbUnmarshaller.unmarshal(stream);
You need to call Unmarshaller.setEventHandler() to make invalid XML content fail.
You can block unexpected content by enabling XML Schema validation on the Unmarshaller. If you don't already have an XML Schema for your POJO, you can generate one at runtime from the JAXBContext, build a Schema object and then set it on the Unmarshaller. By default the Unmarshaller will throw an exception if the XML document isn't valid with respect to the schema.
Here's an example of how to do that:
JAXBContext jaxbContext = JAXBContext.newInstance(Model.class);
// Generate XML Schema from the JAXBContext
final List<ByteArrayOutputStream> schemaDocs = new ArrayList<ByteArrayOutputStream>();
jaxbContext.generateSchema(new SchemaOutputResolver() {
#Override
public Result createOutput(String namespaceUri, String suggestedFileName)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamResult sr = new StreamResult(baos);
schemaDocs.add(baos);
sr.setSystemId(suggestedFileName);
return sr;
}
});
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
int size = schemaDocs.size();
Source[] schemaSources = new Source[size];
for (int i = 0; i < size; ++i) {
schemaSources[i] = new StreamSource(
new ByteArrayInputStream(schemaDocs.get(i).toByteArray()));
}
Schema s = sf.newSchema(schemaSources);
// Set the JAXP Schema object on your Unmarshaller.
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
jaxbUnmarshaller.setSchema(s);
String sample = "<ROOT>" +
"<A Country=\"0\" NewAttribute=\"0\"></A>" +
"<NEWELEMENT> </NEWELEMENT>" +
"</ROOT>";
InputStream stream = new ByteArrayInputStream(sample.getBytes("UTF-8"));
Object unmarshal = jaxbUnmarshaller.unmarshal(stream);
Combine this with the ValidationEventHandler (set through Unmarshaller.setEventHandler()) suggested in the previous answer if you wish to be notified about multiple errors or filter out validation errors that you want to tolerate.
I've tried this:
package-info.java
#XmlSchema(xmlns=
{
#XmlNs(prefix="Person", namespaceURI="sample.url.something"),
#XmlNs(prefix="xsi", namespaceURI="http://www.w3.org/2001/XMLSchema-instance")
})
Java
#XmlRootElement(name = "Person:sampleData")
public class Person {
private static String path = "files/test.xml";
#XmlElement()
public String Name;
#XmlElement()
public int Age;
public Person(){}
public Person(String name, int age){
this.Name = name;
this.Age = age;
}
public static String PersonToXMLString(Person person) throws JAXBException
{
JAXBContext jc = JAXBContext.newInstance(Person.class);
StringWriter sw = new StringWriter();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "somelocation.xsd");
marshaller.marshal(person, sw);
return sw.toString();
}
public static Person XMLStringToPerson() throws JAXBException
{
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Person Person = (Person) unmarshaller.unmarshal(new File(path));
return Person;
}
public static void WriteXMLStringFile(String xml) throws IOException
{
File file = new File(path);
try (FileOutputStream fop = new FileOutputStream(file)) {
if (!file.exists()) {
file.createNewFile();
}
byte[] contentInBytes = xml.getBytes();
fop.write(contentInBytes);
fop.flush();
fop.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static String ReadXmlStringFromFile() throws IOException
{
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line.trim());
}
return sb.toString();
}
public static void main(String[] args) throws JAXBException, IOException
{
Person user = new Person("User",23);
String xml = user.PersonToXMLString(user);
System.out.println(xml);
user.WriteXMLStringFile(xml);
xml = user.ReadXmlStringFromFile();
//used to unmarshall xml to Person object
Person person = user.XMLStringToPerson();
System.out.println(person.Name);
}
}
XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Person:sampleData xmlns:Person="sample.url.something" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>User</Name>
<Age>23</Age>
</Person:sampleData>
If i do something like this i get exception on unmarshalling:
Exception in thread "main" javax.xml.bind.UnmarshalException: unexpected element (uri:"sample.url.something", local:"sampleData").
Expected elements are <{}Person:sampleData>`
FYI: I can not modify the XML.
Any help gratefully received!
You can do the following:
package-info.java
Your #XmlSchema annotation should look something like the following on your package-info class. Since elementFormDefault is specified as UNQUALIFIED the namespace will only be applied to global elements (in JAXB those corresponding to the #XmlRootElement). Note a JAXB impl is not required to use the prefix included in the #XmlSchema when marshalling XML.
#XmlSchema(
elementFormDefault=XmlNsForm.UNQUALIFIED,
namespace="sample.url.something",
xmlns={
#XmlNs(prefix="Person", namespaceURI="sample.url.something")
}
)
package com.example;
import javax.xml.bind.annotation.*;
Person
The #XmlRootElement annotation should not include the prefix.
package com.example;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="sampleData")
public class Person {
For More Information
You can read more about controlling namespace prefixes in JAXB on my blog:
http://blog.bdoughan.com/2011/11/jaxb-and-namespace-prefixes.html
My schema specifies a namespace, but the documents don't. What's the simplest way to ignore namespace during JAXB unmarshalling (XML -> object)?
In other words, I have
<foo><bar></bar></foo>
instead of,
<foo xmlns="http://tempuri.org/"><bar></bar></foo>
Here is an extension/edit of VonCs solution just in case someone doesn´t want to go through the hassle of implementing their own filter to do this. It also shows how to output a JAXB element without the namespace present. This is all accomplished using a SAX Filter.
Filter implementation:
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLFilterImpl;
public class NamespaceFilter extends XMLFilterImpl {
private String usedNamespaceUri;
private boolean addNamespace;
//State variable
private boolean addedNamespace = false;
public NamespaceFilter(String namespaceUri,
boolean addNamespace) {
super();
if (addNamespace)
this.usedNamespaceUri = namespaceUri;
else
this.usedNamespaceUri = "";
this.addNamespace = addNamespace;
}
#Override
public void startDocument() throws SAXException {
super.startDocument();
if (addNamespace) {
startControlledPrefixMapping();
}
}
#Override
public void startElement(String arg0, String arg1, String arg2,
Attributes arg3) throws SAXException {
super.startElement(this.usedNamespaceUri, arg1, arg2, arg3);
}
#Override
public void endElement(String arg0, String arg1, String arg2)
throws SAXException {
super.endElement(this.usedNamespaceUri, arg1, arg2);
}
#Override
public void startPrefixMapping(String prefix, String url)
throws SAXException {
if (addNamespace) {
this.startControlledPrefixMapping();
} else {
//Remove the namespace, i.e. don´t call startPrefixMapping for parent!
}
}
private void startControlledPrefixMapping() throws SAXException {
if (this.addNamespace && !this.addedNamespace) {
//We should add namespace since it is set and has not yet been done.
super.startPrefixMapping("", this.usedNamespaceUri);
//Make sure we dont do it twice
this.addedNamespace = true;
}
}
}
This filter is designed to both be able to add the namespace if it is not present:
new NamespaceFilter("http://www.example.com/namespaceurl", true);
and to remove any present namespace:
new NamespaceFilter(null, false);
The filter can be used during parsing as follows:
//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Unmarshaller u = jc.createUnmarshaller();
//Create an XMLReader to use with our filter
XMLReader reader = XMLReaderFactory.createXMLReader();
//Create the filter (to add namespace) and set the xmlReader as its parent.
NamespaceFilter inFilter = new NamespaceFilter("http://www.example.com/namespaceurl", true);
inFilter.setParent(reader);
//Prepare the input, in this case a java.io.File (output)
InputSource is = new InputSource(new FileInputStream(output));
//Create a SAXSource specifying the filter
SAXSource source = new SAXSource(inFilter, is);
//Do unmarshalling
Object myJaxbObject = u.unmarshal(source);
To use this filter to output XML from a JAXB object, have a look at the code below.
//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Marshaller m = jc.createMarshaller();
//Define an output file
File output = new File("test.xml");
//Create a filter that will remove the xmlns attribute
NamespaceFilter outFilter = new NamespaceFilter(null, false);
//Do some formatting, this is obviously optional and may effect performance
OutputFormat format = new OutputFormat();
format.setIndent(true);
format.setNewlines(true);
//Create a new org.dom4j.io.XMLWriter that will serve as the
//ContentHandler for our filter.
XMLWriter writer = new XMLWriter(new FileOutputStream(output), format);
//Attach the writer to the filter
outFilter.setContentHandler(writer);
//Tell JAXB to marshall to the filter which in turn will call the writer
m.marshal(myJaxbObject, outFilter);
This will hopefully help someone since I spent a day doing this and almost gave up twice ;)
I have encoding problems with XMLFilter solution, so I made XMLStreamReader to ignore namespaces:
class XMLReaderWithoutNamespace extends StreamReaderDelegate {
public XMLReaderWithoutNamespace(XMLStreamReader reader) {
super(reader);
}
#Override
public String getAttributeNamespace(int arg0) {
return "";
}
#Override
public String getNamespaceURI() {
return "";
}
}
InputStream is = new FileInputStream(name);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithoutNamespace xr = new XMLReaderWithoutNamespace(xsr);
Unmarshaller um = jc.createUnmarshaller();
Object res = um.unmarshal(xr);
I believe you must add the namespace to your xml document, with, for example, the use of a SAX filter.
That means:
Define a ContentHandler interface with a new class which will intercept SAX events before JAXB can get them.
Define a XMLReader which will set the content handler
then link the two together:
public static Object unmarshallWithFilter(Unmarshaller unmarshaller,
java.io.File source) throws FileNotFoundException, JAXBException
{
FileReader fr = null;
try {
fr = new FileReader(source);
XMLReader reader = new NamespaceFilterXMLReader();
InputSource is = new InputSource(fr);
SAXSource ss = new SAXSource(reader, is);
return unmarshaller.unmarshal(ss);
} catch (SAXException e) {
//not technically a jaxb exception, but close enough
throw new JAXBException(e);
} catch (ParserConfigurationException e) {
//not technically a jaxb exception, but close enough
throw new JAXBException(e);
} finally {
FileUtil.close(fr); //replace with this some safe close method you have
}
}
In my situation, I have many namespaces and after some debug I find another solution just changing the NamespaceFitler class. For my situation (just unmarshall) this work fine.
import javax.xml.namespace.QName;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLFilterImpl;
import com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector;
public class NamespaceFilter extends XMLFilterImpl {
private SAXConnector saxConnector;
#Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if(saxConnector != null) {
Collection<QName> expected = saxConnector.getContext().getCurrentExpectedElements();
for(QName expectedQname : expected) {
if(localName.equals(expectedQname.getLocalPart())) {
super.startElement(expectedQname.getNamespaceURI(), localName, qName, atts);
return;
}
}
}
super.startElement(uri, localName, qName, atts);
}
#Override
public void setContentHandler(ContentHandler handler) {
super.setContentHandler(handler);
if(handler instanceof SAXConnector) {
saxConnector = (SAXConnector) handler;
}
}
}
Another way to add a default namespace to an XML Document before feeding it to JAXB is to use JDom:
Parse XML to a Document
Iterate through and set namespace on all Elements
Unmarshall using a JDOMSource
Like this:
public class XMLObjectFactory {
private static Namespace DEFAULT_NS = Namespace.getNamespace("http://tempuri.org/");
public static Object createObject(InputStream in) {
try {
SAXBuilder sb = new SAXBuilder(false);
Document doc = sb.build(in);
setNamespace(doc.getRootElement(), DEFAULT_NS, true);
Source src = new JDOMSource(doc);
JAXBContext context = JAXBContext.newInstance("org.tempuri");
Unmarshaller unmarshaller = context.createUnmarshaller();
JAXBElement root = unmarshaller.unmarshal(src);
return root.getValue();
} catch (Exception e) {
throw new RuntimeException("Failed to create Object", e);
}
}
private static void setNamespace(Element elem, Namespace ns, boolean recurse) {
elem.setNamespace(ns);
if (recurse) {
for (Object o : elem.getChildren()) {
setNamespace((Element) o, ns, recurse);
}
}
}
This is just a modification of lunicon's answer (https://stackoverflow.com/a/24387115/3519572) if you want to replace one namespace for another during parsing. And if you want to see what exactly is going on, just uncomment the output lines and set a breakpoint.
public class XMLReaderWithNamespaceCorrection extends StreamReaderDelegate {
private final String wrongNamespace;
private final String correctNamespace;
public XMLReaderWithNamespaceCorrection(XMLStreamReader reader, String wrongNamespace, String correctNamespace) {
super(reader);
this.wrongNamespace = wrongNamespace;
this.correctNamespace = correctNamespace;
}
#Override
public String getAttributeNamespace(int arg0) {
// System.out.println("--------------------------\n");
// System.out.println("arg0: " + arg0);
// System.out.println("getAttributeName: " + getAttributeName(arg0));
// System.out.println("super.getAttributeNamespace: " + super.getAttributeNamespace(arg0));
// System.out.println("getAttributeLocalName: " + getAttributeLocalName(arg0));
// System.out.println("getAttributeType: " + getAttributeType(arg0));
// System.out.println("getAttributeValue: " + getAttributeValue(arg0));
// System.out.println("getAttributeValue(correctNamespace, LN):"
// + getAttributeValue(correctNamespace, getAttributeLocalName(arg0)));
// System.out.println("getAttributeValue(wrongNamespace, LN):"
// + getAttributeValue(wrongNamespace, getAttributeLocalName(arg0)));
String origNamespace = super.getAttributeNamespace(arg0);
boolean replace = (((wrongNamespace == null) && (origNamespace == null))
|| ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
return replace ? correctNamespace : origNamespace;
}
#Override
public String getNamespaceURI() {
// System.out.println("getNamespaceCount(): " + getNamespaceCount());
// for (int i = 0; i < getNamespaceCount(); i++) {
// System.out.println(i + ": " + getNamespacePrefix(i));
// }
//
// System.out.println("super.getNamespaceURI: " + super.getNamespaceURI());
String origNamespace = super.getNamespaceURI();
boolean replace = (((wrongNamespace == null) && (origNamespace == null))
|| ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
return replace ? correctNamespace : origNamespace;
}
}
usage:
InputStream is = new FileInputStream(xmlFile);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithNamespaceCorrection xr =
new XMLReaderWithNamespaceCorrection(xsr, "http://wrong.namespace.uri", "http://correct.namespace.uri");
rootJaxbElem = (JAXBElement<SqgRootType>) um.unmarshal(xr);
handleSchemaError(rootJaxbElem, pmRes);