I need to map some legacy XML I can't change. There are several elements that have hundreds attributes exactly the same as some other elements. The attributes all have the same name postfixed with a number. So XML might look like this:
<someElement custom1="..." custom2="..." custom78=".."/>
<anotherElmenent custom1="..." custom45="..."/>
A solution that "works" is to create a base class like so:
#XmlAccessorType(FIELD)
public class LotsaCustomIds
{
#XmlAttribute
private String custom1;
#XmlAttribute
private String custom2;
...
}
#XmlType
public class SomeElement extends LotsaCustomIds
{
....
}
But it's a shame to use inheritence here, especially since Java only has single inheritence. What I'd like to do is something like the way JPA/Hibernate do embedded objects, like:
#XmlType
public class SomeElement
{
#EmbeddedAttributes
private LotsaCustomIds customIds;
....
}
Anyway to do this?
Note: I'm the EclipseLink JAXB (MOXy) lead.
You could use MOXy's #XmlPath extension to map this use case. When you use it as #XmlPath(".") then it will pull the contents of the child object (LotsaCustomIds) into the parent object (SomeElement).
#XmlType
public class SomeElement
{
#XmlPath(".")
private LotsaCustomIds customIds;
....
}
Related Information from my Blog
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
Related
I have to create POJOs so that I can generate XML using JAXB for the below XML (Just a sample because child elements may go beyond 40).
Here, important thing to note is that I cannot declare these elements as properties in POJO because I won't be knowing the elements name.
<User>
<FirstName>Mahendra</FirstName>
<MiddleName>Singh</MiddleName>
<LastName>Dhoni</LastName>
<Organization>
<Name>Electronics</Name>
<id>elc001</id>
</Organization>
<Manager>
<Name>Sourabh</Name>
<id>emp_001</id>
</Manager>
</User>
I have created POJO for above XML as:
Fields1.java : For elements having value only.
public class Fields1
{
#XmlTransient
public String fieldName1;
#XmlValue
public String value;
// getter,setter
}
Fields2.java : For elements having child elements.
public class Fields2
{
#XmlTransient
public String fieldName2;
#XmlElement(name="NAME")
public String name;
#XmlElement(name="ID")
public String id;
// getter,setter
}
User.java : Root element class
public class User
{
#XmlVariableNode("fieldName1")
public List<Fields1> fields1;
#XmlVariableNode("fieldName2")
public List<Fields2> fields2;
// getter, setter
}
Here, #XmlVariableNode is helping me to generate elements name dynamically.
1. But, it only works fine if there is only single property
2. and if, there are two properties then it just works for the first one and ignores the next.
AFAIK, multiple #XmlVariableNodes in the same class are not possible. EclipseLink's documentation states:
Since this [#XmlVariableNode] makes use of the any logic during unmarshal and MOXy only
handles one Any mapping on a class if a class makes use of the
XmlVariableNode annotation then that class can not have XmlAnyElement
annotations or any other variables that would cause AnyObject or
AnyCollection mappings to be created.
(Source: EclipseLink/DesignDocs/406697)
You might be able to solve your problem by using nested #XmlVariableNodes:
public class TopLevelField {
#XmlTransient
public String fieldName;
#XmlVariableNode("fieldName")
public List<NestedField> fields;
// ...
}
public class NestedField {
#XmlTransient
public String fieldName;
#XmlValue
public String value;
// ...
}
#XmlRootElement
public class User {
#XmlVariableNode("fieldName")
public List<TopLevelField> fields;
}
I'm trying to use JAXB in my project and I found an issue that with Jibx was easy to handle (we stopped using it with jboss 7) but with JAXB we are having difficulties to solve.
So I have a class A as follows:
public class A
{
#XmlElement(name ="xpto")
public B fieldXPTO;
#XmlElement(name ="foo")
public B fieldFOO;
}
And my class B :
public class B
{
public String name;
public String address;
}
Now building our schema for class A we wanted to specify different names for the class B fields for each class A field. Something that when marshalling an A object would turn out like
<A>
<xpto>
<name_xpto>Peter</name_xpto>
<address_xpto>Moon Street</address_xpto>
</xpto>
<foo>
<name_foo>Ryan</name_foo>
<address_foo>Mars Street</address_foo>
</foo>
</A>
Is it possible to do this just by working with the schema ?
Thank you all,
Pedro
I'm using MOXy 2.5.1 along with Jersey 2.4 and Hibernate. I'm seeing a strange marshalling behavior with MOXy with a specific set of classes.
I have classes that look similar to this (with additional stuff removed for brevity):
public abstract class ListWrapper<M> {
protected List<M> records = new ArrayList<M>();
public abstract List<M> getRecords();
// plus functions to make it look like a List / Iterator
}
public abstract class RecordList<T extends Record> extends ListWrapper<T> {
protected Person person;
#XmlElement
public Person getPerson() { return person; }
}
public class FooList extends RecordList<Foo> {
#Override
#XmlElement(type=Foo.class)
public List<Foo> getRecords() { return super.records; }
}
public class Holder {
#XmlElement
public FooList getFooList() { /* ... */ }
}
When Holder gets marshalled to XML using MOXy I get the following:
<holder>
<fooList>
<records>...</records>
<records>...</records>
<person>...</person>
</fooList>
</holder>
I was expecting those <records> elements to be named <foos> instead.
If I put a name="foos" entry on the #XmlElement declaration in FooList I will get my expected name, but then MOXy does something else that's a bit strange. It will still output elements named <records> within the <fooList> element, but these <records> will only contain the <id> values for the (Hibernate) database entries that these correspond to. And so it looks like this:
<holder>
<fooList>
<records><id>5</id></records>
<records><id>10</id></records>
<person>...</person>
<foos>...</foos>
<foos>...</foos>
</fooList>
</holder>
It doesn't matter if I use an #XmlElementWrapper, a #XmlJavaTypeAdapter, or any other thing I can think of. I will either get the wrong names, or a second set of elements that contain a limited set of data. It's almost as though MOXy is not understanding the inheritance structure here or something.
I'll play around with a few other things in the meantime (e.g. ListWrapper API; it's not my design), but to me this looks like a bug in MOXy. Note that the same behaviors happen with JSON, so this isn't limited to XML.
I found a solution. I simply put #XmlAccessorType(XmlAccessType.NONE) on the ListWrapper and RecordList classes (it was already on the FooList class in my code). I think MOXy is (incorrectly?) looking up the tree to ListWrapper and creating an output entry based on that.
I have 2 classes:
#XmlRootElement
public class A {
private Long id;
private B b;
// setters and getters
}
and
#XmlRootElement
public class B {
private Long id;
private String field1;
private String field2;
// setters and getters
}
By default, if I transform an instance of class A to the XML, I will have all its fields (id) and the referenced B class fields (id, field1, field2) like this:
<a>
<id>2</id>
<b>
<id>5</id>
<field1>test1</field1>
<field2>test3</field2>
</b>
</a>
Is is possible to modify what fields from referenced class B are included in the XML of the A class? E.g. I want to say that when I transform an instance of A class, I just want to get id from the B class (no field1 and field2 fields), so I want to get:
<a>
<id>2</id>
<b>
<id>5</id>
</b>
</a>
I don't want to permanently annotate the B class (using #XMLTransient or #XMLElement) to achieve it, as there are cases in which I want to export whole B class as is (with id, field1 and field2.)
I just don't want to export all these fields when the B class is referenced from A.
Is this even possible with JAX-B?
You can use annotation #XmlTransient to ignore fields. Put this annotation on field itself or its getter.
You can use #XmlTransient on the field. Also the default JAXB bindings can be overridden at a global scope or on a case-by-case basis as needed by using custom binding declarations.
Check out the Guide to JAXB from Baeldung website for more examples.
Ok, I've come up with some hacky solution:
#XmlRootElement
public class A {
private Long id;
private B b;
// setters and getters
}
I've provided an additional getter just for the REST use case, so it's like:
#XMLTransient
public B getB() {
return b;
}
#XMLElement(name="b")
public Long getBForREST() {
return b.getId();
}
It results in the following structure:
<a>
<id>2</id>
<b>5</b>
</a>
It's not exactly same structure I aimed for it works for me.
I don't like this solution - an #XMLExclude({"field1", "field2"}) or something like that would be much cleaner in my opinion.
Nevertheless - for now on, it works; it's ugly, but it works.
I have successfully marshaled the following class
#XmlRootElement(name = "Field")
private static class MyField {
#XmlAttribute(name = "Name")
String name;
#XmlElement(name = "Size")
int size;
....}
Now I want to have my container class to hold multiple instances of Field, so I declared a class in the following way:
private static class MyFieldsCollection {
private Collection<MyField> fields = new LinkedList<MyField>();
public MyFieldsCollection() {}
....}
When I simply try to marshal the container field I get the following error:
class java.util.LinkedList nor any of its super class is known to this context
How do I annotate the fields member so the container class will be marshaled as a collection of fields?
You shouldn't annotate your MyField class as a #XmlRootElement as it won't be a xml root element. I think you can either annotate it with #XmlType or simply do not annotate it. You could try and put the #XmlElement tag in the getter for the "fields" attribute in your class. If it works, your XML will look like this:
<MyFieldsCollection>
<fields name="name1" size="size1"/>
<fields name="name2" size="size2"/>
</MyFieldsCollection>
If it doesn't work, and it's a shot in the dark, try changing the type of the "Collection" to "List" to check if it makes some difference. What version of Java are you using? How does the code that you use to serialize your object looks like?