I have a question. Is there any way to detect duplicate nodes (with different values) in xml file?
If java class looks like this (I've just made this up for sake of simplicity):
#XmlRootElement(name = "PERSON")
#XmlAccessorType(XmlAccessType.FIELD)
public class Person {
#XmlElement(name = "NAME")
private String name;
#XmlElement(name = "SURNAME")
private String surname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}
and input .xml file looks like this:
<person>
<name>John</name>
<surname>Smith</surname>
<name>Jack</name>
</person>
After unmarshalling process, value 'John' gets overriden by value 'Jack'.
Is there any way to detect/avoid this?
(So i can throw Exception or log occurence of this duplicate?)
The general solution to this problem is to set your XML schema as an instance of javax.xml.validation.Schema on that Unmarshaller to ensure the XML matches the expected input.
For More Information
http://blog.bdoughan.com/2010/12/jaxb-and-marshalunmarshal-schema.html
Related
I have two objects that are in hierarchical relationship. I have the jaxb mapping for setter methods in the child pojo. While loading the xml into pojo I don't have any issues. However, while generating the xml, I see two element entries in the xml for the same attribute in pojo the object - one with the mapping key and the other with actual variable name.
Example:
//parent class
public class Employee {
private String name;
public String getName(){
return this.name;
}
public void setName(String name){
this.name=name;
}
}
//second class
#XmlRootElement(name = "teacher")
public class Teacher extends Employee {
#Override
public String getName(){
return super.getName();
}
#Override
#XmlElement(name ="NAME")
public void setName(String name){
super.setname(name)
}
}
//xml out put I get is
// like this
<teacher>
<name>John Doe<name/>
<NAME>John Doe</NAME>
</teacher>
How do I fix this? I want just the one with the key - NAME
You could do either:
Mark the name property on the Emoloyee class as #XmlTransient.
Mark the Employee class as #XmlTransient to remove it as a mapped class.
Remove the name property from the Teacher class and annotate the one on Employee with your #XmlElement annotation.
Please, as it could indicate the 'type' attribute on elements 'horaIniJornada' and 'tiempoJornadamedia'?
#XmlElement(name = "tiempoJornadaMedia")
public String getTimeJournalMedia() {
return timeJournalMedia;
}
#XmlAttribute(name = "tipo")
public String getTypeHourInitJournal() {
return typeHourInitJournal;
}
<configuraciones>
<almacen>MD</almacen>
<cliente>MKD</cliente>
<secciones>
<seccion>
<seccId>70</seccId>
<horaIniJornada tipo="T">23:00:00</horaIniJornada>
<tiempoJornadaMedia tipo="T">07:30:00</tiempoJornadaMedia>
</seccion>
<seccion>
<seccId>71</seccId>
<horaIniJornada tipo="T">23:00:00</horaIniJornada>
<tiempoJornadaMedia tipo="T">07:30:00</tiempoJornadaMedia>
</seccion>
</secciones>
</configuraciones>
You'll need a separate class for that. Something like TimeHournalMedia with an #XmlAttribute and #XmlValue property. Something like:
#XmlValue
public String getValue() {
return value;
}
#XmlAttribute(name = "tipo")
public String getTypeHourInitJournal() {
return typeHourInitJournal;
}
Then you'll have in your main class:
#XmlElement(name = "tiempoJornadaMedia")
public TimeHournalMedia getTimeJournalMedia() {
return timeJournalMedia;
}
To get the textual content you'll do getTimeJournalMedia().getValue(), to get the attribute - getTimeJournalMedia().getgetTypeHourInitJournal(), something like that.
There are further options with MOXy #XmlPath.
I use a JAXB marshaller and I would like to add an empty element with a specific attribute. This is a dummy class:
#XmlRootElement(name="observation")
public class Observation {
#XmlAttribute
public static final String classCode = "OBS";
#XmlAttribute
public static final String moodCode = "EVN";
private String data;
#XmlElement
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
This creates the following XML:
<observation classCode="OBS" moodCode="EVN">
<data>fsdfsdfd</data>
</observation>
Is there any way to add a new element with a specific attribute only (no value at all)? E.g.
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/>
<data>fsdfsdfd</data>
</observation>
This should do it:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "TemplateIdType")
public class TemplateIdType {
#XmlAttribute(name = "root")
protected String root;
// getter and setter
}
(And you add an element of this class to Observation.)
In the example code below, Employee class has been specified with JAXB field level access type. For the property dept, however, the access type has been specified at getter method level with #XMLElement annotation.
During marshalling of Organization class, the following exception is thrown -
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Class has two properties of the same name "dept"
this problem is related to the following location:
at public java.lang.String com.playground.jaxb.Employee.getDept()
this problem is related to the following location:
at private java.lang.String com.playground.jaxb.Employee.dept
Can you help me understand why this overriding of JAXB accessor type is not working please? Also any solution would be highly appreciated.
Example
Root Element Class
package com.playground.jaxb;
#XMLRootElement(name="organization")
public class Organization {
#XmlElementWrapper(name = "employees")
#XmlElement(name = "employee")
private Set<Employee> employees;
public Organization{}
// Remainder omitted...
}
Employee Class
package com.playground.jaxb;
#XMLAccessorType(XMLAccessType.FIELD)
public class Employee {
private String name;
private String dept;
#XMLElement(name="department")
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
public Employee {}
// Remainder omitted...
}
You can re-name getter/setter pair, e.g. getDept() -> getDepartment()
private String dept;
#XmlElement(name="department")
public String getDeptartment() {
return dept;
}
public void setDeptartment(String dept) {
this.dept = dept;
}
but in this case you will have duplicate in XML
<dept>my_dept</dept>
<department>my_dept</department>
Or you can annotate field dept with #XmlTransient annotation, if you want to change access type it.
#XmlTransient
private String dept;
#XmlElement(name="department")
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
In this case, dept field will be ignored and getter/setter pair will be used instead
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.