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.
Related
I'm experimenting with Spring Data REST and so far it's going relatively well. I'm able to query and manipulate the entities, and I have reached a point where I'd like to filter the retrieved data by a variable number of parameters. For this purpose I've been reading and decided on QueryDSL which is integrated nicely with Spring, and it works (almost) flawlessly when using fields from the entities.
However, my filtering form contains some parameters which have no direct mapping to the entity, leading to this question. For the sake of brevity, I'll be using an over-simplified example, hence my using of a persons's age instead of birth-date & etc.
Supposing we have the following Person entity:
#Data
#NoArgsConstructor
#Entity
public class Person {
#Id
#GeneratedValue
private UUID id;
private String name;
private String lastName;
private Integer age;
}
... and the appropriate repo
#RepositoryRestResource
public interface PersonRepository extends CrudRepository<Person, UUID>, QuerydslPredicateExecutor<Person>, QuerydslBinderCustomizer<QPerson> {
#RestResource
Page<Person> findAll(#QuerydslPredicate Predicate predicate, Pageable pageable);
#Override
default void customize(QuerydslBindings bindings, QPerson person) {
bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
}
}
... one can access and filter persons by name or last name (case insensitive) via http://<server>/persons?name=whatever, so far so good.
Next step, I would like to see only the people that are "pensionable", let's say over 65 years old, so the URL would look like http://<server>/persons?pensionable=true. However, pensionable is not an attribute in the Person entity, so adding it as a request param doesn't do anything.
I've been trying to figure out how this can be achieved or if this is currently a limitation of the framework(s), but my searches haven't been successful so far. Eventually via trial and error, I've come up with something that seems to work but feels more like a hack:
Create a different PersonExtendedFilter bean (not entity) which includes the extra/arbitrary params:
#Data
#NoArgsConstructor
public class PersonExtendedFilter{
private Boolean pensionable;
}
... create a BooleanPath using the above, and use it to define a binding inside the repo's customize method:
#Override
default void customize(QuerydslBindings bindings, QPerson person) {
bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
BooleanPath pensionable = new PathBuilder<>(PersonExtendedFilter.class, "personExtendedFilter").getBoolean("pensionable");
bindings.bind(pensionable).first((path, value) -> new BooleanBuilder().and(value ? person.age.gt(65) : person.age.loe(65)));
}
Bottom line, I'm wondering whether there is an elegant way of doing this or if I missing something, be it from a logical POV, a RTFM one, or something else.
I want to unmarshall a XML-File with mixed Content. I found a thread on stackoverflow which seemed appropriate (JAXB- #XmlMixed usage for reading #XmlValue and #XmlElement) where the user bdoughan defined 3 Use-Cases to deal with mixed content.
The third use case would keep the text between the tags in a single String variable and save the elements in a List. Which is what I wanted. Unfortunately I couldn't get it to work and the thread is quite old and maybe outdated.
I've tried the Usecase #3 with a List of Objects and a List of my Reference Class. Also I tried #XmlElement and #XmlValue Annotations.
I'm using the javax.xml.bind jaxb-api in version 2.3.1 and the org.glassfish.jaxb jaxb-runtime in version 2.3.1 in a Maven Projec with Java SE Version 12.0.2.
A Sample XML I tested with
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Date>
2018.06.27
<reference id="AnyId1">
</reference>
</Date>
My Class Representation
#XmlRootElement(name="Date")
public class TestPojo {
#XmlMixed
public String getTextContent() {
return textContent;
}
public void setTextContent(String textContent) {
this.textContent = textContent;
}
#XmlElementRef(name="reference", type = Reference.class)
public List<Object> getRef() {
return ref;
}
public void setRef(List<Object> ref) {
this.ref = ref;
}
String textContent;
List<Object> ref = new ArrayList<Object>();
}
I'd expect that the xml is unmarshalled into a POJO object and the right values are assigned. The Objects variables (textContent & ref) are null after unmarshalling.
You could try this:
Using a Reference class as below,
#XmlAccessorType(XmlAccessType.FIELD)
public class Reference {
#XmlAttribute
private String id;
}
And your Root class,
#XmlRootElement(name="Date")
public class TestPojo {
#XmlMixed
#XmlAnyElement
private List<Object> textContent;
#XmlElement
private Reference reference;
}
This will unmarshall giving you the reference element and everything else in a List.
For the example you have it will be 2 entries. The date value/text along with tab character (\t) and new line characters (\n), and another entry with new line character.
So you can use this list to process the contents and use what you want.
If there is a cleaner solution, I am interested. Cheers
Update to answer to comment:
In order to be more clear with the fix. What I did was using #XmlElement instead of #XmlElementRef for a single Reference instead of list (cos that's what I saw in the xml).
Also I added the #XmlAnyElement annotation for the mixed content made it a list. This is what fixed it. So sticking with your class, it would look like below:
#XmlRootElement(name="Date")
public class TestPojo {
List<Object> textContent;
Reference ref;
#XmlMixed
#XmlAnyElement
public List<Object> getTextContent() {
return textContent;
}
public void setTextContent(List<Object> textContent) {
this.textContent = textContent;
}
#XmlElement(name="reference")
public Reference getRef() {
return ref;
}
public void setRef(Reference ref) {
this.ref = ref;
}
}
The #XmlAccessorType saved me time from writing getters and setters. For an explanation of what this annotation does with an example (and in relation to #XmlElement, check this:
What is the difference between using #XmlElement before field and before getter declaration?
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
I have a class I am not sure how to annotate properly.
My goal for Holder::data:
List should maintain order not by comparator but by the natural ordering of the elements in the array. (Which can be an ndx column if that is helpful.)
Holder will have the only reference to data, so Cascade all is probably applicable as well.
I am also open to a different design that removes the map, if that would make for a cleaner design.
#Entity
public class Holder extends DomainObject {
private Map<Enum,List<Element>> data;
}
#Entity
public class Element extends DomainObject {
private long valueId;
private int otherData;
}
#Mappedsuperclass
public class DomainObject {
// provides id
// optimistic locking
// create and update date
}
I don't think it is possible with hibernate(-core) to map any collection of collections:
Collections may contain almost any
other Hibernate type, including all
basic types, custom types, components,
and of course, references to other
entities.
(from the official doc)
Notice the almost and the omission of the collection type.
A workaround: You need to introduce a new type 'in between' the collection holder and the element. This type you can map as an entity or a component and it refers the original content of the map, in this case a list.
Something like:
#Entity
public class Holder extends DomainObject {
#OneToMany
private Map<Enum,InBetween> inBetweens;
}
#Entity
public class InBetween extends DomainObject {
#OneToMany
private List<Element> elements;
}
#Entity
public class Element extends DomainObject {
private long valueId;
private int otherData;
}
#Mappedsuperclass
public class DomainObject {
// provides id
// optimistic locking
// create and update date
}
The rest of the mapping depends on your particular situation, but is rather straightforward.
Here is a blog about collection of collections in hibernate http://blog.xebia.com/2007/10/05/mapping-multimaps-with-hibernate/
Hope it will help. It helped me.
Regards,
Anton
Please note that the referred link to the Hibernate documentation seems out of date, I found the following working: http://docs.jboss.org/hibernate/core/3.5/reference/en/html/collections.html
I seemed to get the following exception when trying to deploy my application:
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
java.util.List is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at java.util.List
at private java.util.List foobar.alkohol.register.webservice.jaxws.GetRelationsFromPersonResponse._return
at foobar.alkohol.register.webservice.jaxws.GetRelationsFromPersonResponse
java.util.List does not have a no-arg default constructor.
this problem is related to the following location:
at java.util.List
at private java.util.List foobar.alkohol.register.webservice.jaxws.GetRelationsFromPersonResponse._return
at foobar.alkohol.register.webservice.jaxws.GetRelationsFromPersonResponse
My code worked just well until I changed the return type from List to List<List<RelationCanonical>>
Here is the partial webservice:
#Name("relationService")
#Stateless
#WebService(name = "RelationService", serviceName = "RelationService")
#SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL, parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public class RelationService implements RelationServiceLocal {
private boolean login(String username, String password) {
Identity.instance().setUsername(username);
Identity.instance().setPassword(password);
Identity.instance().login();
return Identity.instance().isLoggedIn();
}
private boolean logout() {
Identity.instance().logout();
return !Identity.instance().isLoggedIn();
}
#WebMethod
public List<List<RelationCanonical>> getRelationsFromPerson(#WebParam(name = "username")
String username, #WebParam(name = "password")
String password, #WebParam(name = "foedselsnummer")
String... foedselsnummer) {
......
......
......
}
I have also tried by removing the #SOAPBinding and trying default, but the same result occurs.
Appreciate any help
UPDATE
I want to note out something. I changed all List to ArrayList, and then it compiled. The reason why I say compiled and not worked is because it behaves strange. I get an Object of type:
RelationServiceStub.ArrayList
but the object has no get methods or does not behave as a List either. I also tried to cast it to a List but that didnt work.
Note that this is after I have used Axis 2 and wsdl2java So yes, now it compiles, but I dont know how to get the data out.
In my understanding, you will not be able to process a plain List via JAXB, as JAXB has no idea how to transform that into XML.
Instead, you will need to define a JAXB type which holds a List<RelationCanonical> (I'll call it Type1), and another one to hold a list of those types, in turn (as you're dealing with a List<List<...>>; I'll call this type Type2).
The result could then be an XML ouput like this:
<Type2 ...>
<Type1 ...>
<RelationCanonical ...> ... </RelationCanonical>
<RelationCanonical ...> ... </RelationCanonical>
...
</Type1>
<Type1>
<RelationCanonical ...> ... </RelationCanonical>
<RelationCanonical ...> ... </RelationCanonical>
...
</Type1>
...
</Type2>
Without the two enclosing JAXB-annotated types, the JAXB processor has no idea what markup to generate, and thus fails.
--Edit:
What I mean should look somewhat like this:
#XmlType
public class Type1{
private List<RelationCanonical> relations;
#XmlElement
public List<RelationCanonical> getRelations(){
return this.relations;
}
public void setRelations(List<RelationCanonical> relations){
this.relations = relations;
}
}
and
#XmlRootElement
public class Type2{
private List<Type1> type1s;
#XmlElement
public List<Type1> getType1s(){
return this.type1s;
}
public void setType1s(List<Type1> type1s){
this.type1s= type1s;
}
}
You should also check out the JAXB section in the J5EE tutorial and the Unofficial JAXB Guide.
If that suits your purpose you can always define an array like this:
YourType[]
JAXB can certainly figure out what that is and you should be able to immediatly use it client side. I would also recommend you to do it that way, since you should not be able to modify the array retrieved from a server via a List but via methods provided by the web service
If you want to do this for any class.
return items.size() > 0 ? items.toArray((Object[]) Array.newInstance(
items.get(0).getClass(), 0)) : new Object[0];
You can use "ArrayList" instead of inner "List"