If a class has #XmlElement property, it cannot have #XmlValue property error - jaxb

I am getting below exception when I am trying to marshel below java classes to below expected xml.
Exception:
If a class has #XmlElement property, it cannot have #XmlValue property.
XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<parent>
I am the parent element of mixedtype.
<child>I am the child element</child>
</parent>
Parent.java
#XmlRootElement(name = "parent")
public class Parent{
protected List<Child> child= new ArrayList<Child>();
protected List<String> text= new ArrayList<String>();
#XmlElementRef(name="child",type=Child.class)
public List<Child> getChild() {
return child;
}
#XmlMixed
public List<String> getText() {
return text;
}
public void setChild(Child value) {
this.child.add(value);
}
public void setText(String value) {
this.text.add(value);
}
}
Child.java
public class Child {
#XmlValue
protected String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

For the above the XML I tried this and seems to work fine for me:
XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<parent>
I am the parent element of mixedtype.
<child>I am the child element</child>
</parent>
Parent.class:
#XmlRootElement(name = "parent")
#Data
#NoArgsConstructor
#XmlAccessorType(XmlAccessType.FIELD)
public class Parent {
#XmlMixed
#XmlAnyElement
private List<Object> textContent;
#XmlElement(name = "child")
private String child;
}
My Main class:
public class JaxbExampleMain {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("parent.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(Parent.class).createUnmarshaller();
final Parent parent = unmarshaller.unmarshal(xmlStreamReader, Parent.class).getValue();
System.out.println(parent.toString());
Marshaller marshaller = JAXBContext.newInstance(Parent.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(parent, System.out);
}
}
Following is the output that I am getting:
Parent(textContent=[
I am the parent element of mixedtype.
,
], child=I am the child element)
<parent>
I am the parent element of mixedtype.
<child>I am the child element</child>
</parent>

Related

How to persist UserData of Element in JAXB Unmarshal?

I unmarshal the Document to object as below.
Before that, when parsing XML, use setUserData to store location information for each element.
class MyJaxbAdapter extends XmlAdapter<Object, SubObject> {}
#Override
public UnattendComponent unmarshal(Object v) throws Exception {
Node node = (Node) v; // ElementNSImpl; It's probably a newly created object. Because It is different from the document object given by ownerDocument as SAXSource.
node.getUserData(...) // return NULL
}
}
Document document = ...;
unmarshaller.setAdapter(new MyJaxbAdapter());
MyXMLObject object = unmarshaller.unmarshal(new DOMSource(document), MyXMLObject.class).getValue();
But I can't get UserData inside XmlAdapter's unmarshal method. Is there any way to persist UserData?
Locator information is stored in the properties of Element as shown below.
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
Element el = document.createElementNS(usedNamespaceUri, qName);
// ...
el.setUserData(
ElementUserData.class.getName(),
ElementUserData.builder()
.lineNumber(locator.getLineNumber())
.columnNumber(locator.getColumnNumber())
.build(),
null);
}
I need the Locator information (UserData) stored by the above code in the unmarshal of the XmlAdapter.
However, there is no userdata in the node passed as an argument to unmarshal .
Sample Code:
https://github.com/joseph-jclab/jaxb-question-01
Not entirely sure if this is something you are looking for but providing it as a reference so you might get some idea to proceed further:
Sample XML:
<root>
<name>Batman</name>
<year>2008</year>
</root>
Root.class:
#XmlRootElement(name = "root")
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
private String name;
private String year;
#XmlJavaTypeAdapter(CustomAdapter.class)
private String after;
private void afterUnmarshal(Unmarshaller m, Object parent) {
after = name;
}
}
CustomAdapter.class:
public class CustomAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(String v) throws Exception {
System.out.println("Within Unmarshal : " + v);
return null;
}
#Override
public String marshal(String v) throws Exception {
System.out.println("Within Marshal : " + v);
return null;
}
}
SampleMain.class:
public class SampleMain {
public static void main(String[] args) throws XMLStreamException, JAXBException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("sample.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(Root.class).createUnmarshaller();
unmarshaller.setAdapter(new CustomAdapter());
final Root root = unmarshaller.unmarshal(xmlStreamReader, Root.class).getValue();
System.out.println(root.toString());
Marshaller marshaller = JAXBContext.newInstance(Root.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(root, System.out);
}
}
Output:
Root(name=Batman, year=2008, after=Batman)
Within Marshal : Batman
<root>
<name>Batman</name>
<year>2008</year>
</root>

How to skip the null fields during jaxb marshalling

Is there a way for marshaller to generate a new xml file skipping any null attributes? So
something like someAttribute="" does not show up in the file.
Thanks
A JAXB (JSR-222) implementation will not marshal a field/property annotated with #XmlAttribute that contains a null value.
Java Model (Root)
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
private String foo;
private String bar;
#XmlAttribute
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
#XmlAttribute(required=true)
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo Code
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.setFoo(null);
root.setBar(null);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<root/>

javassist not injecting annotation at existing field

I'm trying to inject JAXB annotation at runtime using Javassist. I have written following code:
public class AssistAnnotationInjector {
public static void addAnnotationRunTime(String className, String fieldName) throws NotFoundException, CannotCompileException, IOException, ClassNotFoundException{
CtClass ctClass = ClassPool.getDefault().get(className);
ClassFile ccFile = ctClass.getClassFile();
ConstPool constPool = ccFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation annot = new Annotation("javax.xml.bind.annotation.XmlTransient",constPool);
attr.addAnnotation(annot);
CtField field = ctClass.getDeclaredField(fieldName);
field.getFieldInfo().addAttribute(attr);
System.out.println(field.getAnnotation(XmlTransient.class));
ccFile.setVersionToJava5();
ctClass.writeFile();
}
public static void main (String args[]) throws CannotCompileException, NotFoundException, IOException, SecurityException, NoSuchMethodException, ClassNotFoundException, JAXBException, NoSuchFieldException{
Person<Student> p = new Person<Student>();
p.setName("XYZ");
Student s = new Student();
s.setName("ABC");
s.setId("239423");
p.setPayload(s);
addAnnotationRunTime("RuntimeAnnotation.Person", "name");
Field f = p.getClass().getDeclaredField("name");
System.out.println(f.getAnnotation(XmlTransient.class));
JAXBContext context = JAXBContext.newInstance(p.getClass());
Marshaller mr = context.createMarshaller();
mr.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
mr.marshal(p, System.out);
}
}
And Person.java class is:
#XmlRootElement(name="Person")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Student.class})
public class Person <T>{
private T payload;
private String name;
public void setPayload(T payload){
this.payload = payload;
}
public T getPayload(){
return payload;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
In AssistAnnotationInjector.java, I am trying to add XmlTransient annotation to 'name' field. But the name field is still coming in marshalling output. Why is it so?
PS: marshal output is :
#javax.xml.bind.annotation.XmlTransient
null
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Person>
<payload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="student">
<name>ABC</name>
<id>239423</id>
</payload>
**<name>XYZ</name>**
</Person>
name tag was not expected to present in output..
You basicaly have 2 options:
do the modification before you load the class. You can not use reflection in the normal way! One can try to use org.reflections with Maven plugin to pre-fetch classes. See here for more info.
use custom classloader to load the modified class. See here for more info.
After adding the attribute to the field you need to call ctClass.toClass() method,which freezes the class. After this you can check for the annotation.

jaxb marshall Boolean as Integer (0,1) problems

My problem seems pretty simple, yet I was unable to find anything exactly like it on stackoverflow.
I'm using jaxb to marshall/unmarshall this object:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "")
#XmlRootElement(name = "root")
public class MyJaxbObject implements Serializable
{
#XmlElement(name = "DELETE")
#XmlJavaTypeAdapter(BooleanIntAdapter.class)
private Boolean delete;
#XmlElement(name = "message")
private String message;
constructors.. getters... setters...
}
My BooleanAdapter is a simple XmlAdapter<Integer, Boolean> that turns true/false to 1/0 and back.
Unmarshalling works, but marshalling does not. it always yeilds this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<DELETE xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.or/2001/XMLSchema" xsi:type="xs:boolean">true</DELETE>
<message>***DONE***</message>
</root>
When I change the xml element config to #XmlElement(name = "DELETE",type = Boolean.class) unmarshalling of the boolean fails and marshalling yields this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<DELETE xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:int">1</DELETE>
<message>***DONE***</message>
</root>
I put simple print messages in the marshal/unmarshal methods of the xml adapter and tried to marshal/unmarshal this kind of object.
I saw that without type declaration unmarshal method is called but marshal is not.
With the type declaration only marshal is called.
Help??
How can I marshal/unmarshal my Boolean to/from {1,0} and possibly get rid of the xsi:type in the marshalled xml?
Edit - this is the code i used to test marshal/unmarshal:
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
JAXBContext context = javax.xml.bind.JAXBContext.newInstance("my.package.classes");
Unmarshaller unmarshal = context.createUnmarshaller();
Marshaller marshal = context.createMarshaller();
marshal.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
String str = "<root><DELETE>1</DELETE><message>***DONE***</message></root>";
DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
Document d = null;
d = builder.parse(new InputSource(new StringReader(str)));
MyJaxbObject myJaxUnmarsh = unmarshal.unmarshal(d.getFirstChild(), MyJaxbObject.class).getValue();
System.out.println(myJaxUnmarsh.getMessage() + " , " + myJaxUnmarsh.getDelete());
MyJaxbObject myJax = new MyJaxbObject();
myJax.setDelete(true);
myJax.setMessage("***DONE***");
marshal.marshal(myJax, System.out);
With the following implementation of BooleanIntAdapter:
BooleanIntAdapter
package forum9380680;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class BooleanIntAdapter extends XmlAdapter<Integer, Boolean> {
#Override
public Boolean unmarshal(Integer v) throws Exception {
return v.equals(1);
}
#Override
public Integer marshal(Boolean v) throws Exception {
if(v) {
return 1;
} else {
return 0;
}
}
}
Output
I get the following output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<DELETE>1</DELETE>
<message>***DONE***</message>
</root>
Below is the rest of the code I used:
Demo
package forum9380680;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(MyJaxbObject.class);
MyJaxbObject object = new MyJaxbObject();
object.setDelete(true);
object.setMessage("***DONE***");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(object, System.out);
}
}
MyJaxbObject
package forum9380680;
import java.io.Serializable;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "")
#XmlRootElement(name = "root")
public class MyJaxbObject implements Serializable
{
#XmlElement(name = "DELETE")
#XmlJavaTypeAdapter(BooleanIntAdapter.class)
private Boolean delete;
#XmlElement(name = "message")
private String message;
public Boolean getDelete() {
return delete;
}
public void setDelete(Boolean delete) {
this.delete = delete;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
found the problem:
CXF pom makes the project use jaxb-impl version 2.1.0.
Without CXF the project uses the JDK default which was 2.1.10 in my case.
see:
http://jaxb.java.net/guide/Which_JAXB_RI_is_included_in_which_JDK_.html
I added the following under dependency management to fix the problem
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.5</version>
<type>jar</type>
<scope>compile</scope>
</dependency>

Cannot unmarshall using #XmlAttribute

I can marshall my sitemap with JAXB, though I cannot unmarshall it! Any help would be much appreciated.
I get the following exception:
javax.xml.bind.UnmarshalException: unexpected element
(uri:"http://www.sitemaps.org/schemas/sitemap/0.9", local:"urlset").
Expected elements are <{}urls>,<{}urlset>
Sitemap urlset:
#XmlRootElement(name = "urlset")
public class XMLURLSet
{
List<XMLURL> urls;
final String xmlns = "http://www.sitemaps.org/schemas/sitemap/0.9";
#XmlAttribute(name = "xmlns")
public String getXmlns() {
return xmlns;
}
public void setXmlns(String xmlns) {
// nop
}
#XmlElement(name = "url")
public List<XMLURL> getUrls(){
return urls;
}
public void setUrls(List<XMLURL> urls) {
this.urls = urls;
}
Sitemap urls:
#XmlRootElement(name = "urls")
public class XMLURL {
String loc;
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
}
Output XML (it's correct):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://www.example.com</loc>
</url>
</urlset>
JUnit marshalling code that works
JAXBContext context = JAXBContext.newInstance(XMLURLSet.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
StringWriter sw = new StringWriter();
marshaller.marshal(urlSet, sw);
String xml = sw.toString();
Unmarshalling that DOESNT WORK (cont' from above):
JAXBContext ctx = JAXBContext.newInstance(XMLURLSet.class);
Unmarshaller umsler = ctx.createUnmarshaller();
XMLURLSet xmlUrlSet = (XMLURLSet) umsler.unmarshal(new StreamSource(new StringReader(xml)));
The last line generates the exception:
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.sitemaps.org/schemas/sitemap/0.9", local:"urlset"). Expected elements are <{}urls>,<{}urlset>
The problem is with the way you are trying to namespace qualify your document. You will need to add a class called package-info in the same package as your domain classes with the following entry:
package-info
#XmlSchema(
namespace = "http://www.sitemaps.org/schemas/sitemap/0.9",
elementFormDefault = XmlNsForm.QUALIFIED)
package your.package.name;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
For More Information
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html

Resources