Here is my dilemma:
I have a dto class for marshaling back and forth from / to XML.
Here's the trick: due to the number of dto-classes our project is associated with, these are collections with a multiple outter tag, I decided to create a delegate collection that allows me to take one of these classes and turn them into effortlessly effortlessly Collect and get convenience, which comes with it (iteration, addition, etc.).
In our project, we conduct marshaling tests to eliminate annotation errors, etc. Below is my error code.
Problem: Depending on the marshaler, if I extend this QuickCollection, I get the following error. When an object is not bound to xml using CXF as a response to a webservice request, it fails. Exact error: com.sun.istack.SAXException2: could not marshal the type "java.lang.String" as an element because the @XmlRootElement annotation is missing
When it is marshalling / unarchiving with JAXB in the test, this is good. When the same QuickCollection is used to marshal results from third parties, using spring RestOperations and works fine
mind screw: When I delete inheritance and manage the collection as a private member, it all just works!
This makes no sense to me, as I literally return the exact data type in both situations.
Below is all the code.
This is the Inherited delegate class.
public class QuickCollection<T> implements Collection<T> { // to be set if needed after instantiation. To behave like a normal collection, we set it to something safe protected Collection<T> delegate = Collections.emptySet(); public QuickCollection() { } public QuickCollection(Collection<T> delegate) { this.delegate = delegate; } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean contains(Object o) { return delegate.contains(o); } @Override public Iterator<T> iterator() { return delegate.iterator(); } @Override public Object[] toArray() { return delegate.toArray(); } @Override public <T> T[] toArray(T[] a) { return delegate.toArray(a); } @Override public boolean add(T t) { return delegate.add(t); } @Override public boolean remove(Object o) { return delegate.remove(o); } @Override public boolean containsAll(Collection<?> c) { return delegate.containsAll(c); } @Override public boolean addAll(Collection<? extends T> c) { return delegate.addAll(c); } @Override public boolean removeAll(Collection<?> c) { return delegate.removeAll(c); } @Override public boolean retainAll(Collection<?> c) { return delegate.retainAll(c); } @Override public void clear() { delegate.clear(); } @Override public String toString() { return "" + delegate.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; QuickCollection that = (QuickCollection) o; if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null) return false; return true; } @Override public int hashCode() { return delegate != null ? delegate.hashCode() : 0; } }
Here is a child class of DTO
@XmlAccessorType(XmlAccessType.PROPERTY) @XmlType(name = "BuddyCodes") @XmlRootElement(name = "BuddyCodes") public class BuddyCodes extends QuickCollection<String> implements Xml { private Long accountId; private Date expirationDate; public BuddyCodes() { super.delegate = new HashSet<String>(); } public BuddyCodes(Long accountId, Set<String> codes, Date expirationDate) { super(codes); this.accountId = accountId; this.expirationDate = expirationDate; super.delegate = new HashSet<String>(); } public BuddyCodes(Long accountId, Date expirationDate) { this.accountId = accountId; this.expirationDate = expirationDate; super.delegate = new HashSet<String>(); } @Override public String toXml() { String retVal; try { retVal = StringUtils.toXml(this); } catch (JAXBException e) { retVal = e.toString(); } return retVal; } public Long getAccountId() { return accountId; } public void setAccountId(Long accountId) { this.accountId = accountId; } public Set<String> getCodes() { return (Set<String>) super.delegate; } @XmlElement(name = "code") public void setCodes(Set<String> codes) { super.delegate = codes; } public Date getExpirationDate() { return expirationDate; } public void setExpirationDate(Date expirationDate) { this.expirationDate = expirationDate; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BuddyCodes that = (BuddyCodes) o; if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) return false; if (delegate != null ? !super.delegate.equals(that.delegate) : that.delegate != null) return false; if (expirationDate != null ? !expirationDate.equals(that.expirationDate) : that.expirationDate != null) return false; return true; } @Override public int hashCode() { int result = accountId != null ? accountId.hashCode() : 0; result = 31 * result + (expirationDate != null ? expirationDate.hashCode() : 0); result = 31 * result + (super.delegate != null ? super.delegate.hashCode() : 0); return result; } @Override public String toString() { return "BuddyCodes{" + "accountId=" + accountId + "codes=" + super.delegate + ", expirationDate=" + expirationDate + '}'; } }
And it does not work. I get an error message.
Now, this is a child class after removing the inheritance, and it works !!!
import javax.xml.bind.JAXBException; import javax.xml.bind.annotation.*; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Set; @XmlAccessorType(XmlAccessType.PROPERTY) @XmlType(name = "BuddyCodes") @XmlRootElement(name = "BuddyCodes") public class BuddyCodes implements Xml { private Long accountId; private Date expirationDate; private Set<String> delegate; public BuddyCodes() { delegate = new HashSet<String>(); } public BuddyCodes(Long accountId, Set<String> codes, Date expirationDate) { this.accountId = accountId; this.expirationDate = expirationDate; delegate = new HashSet<String>(); } public BuddyCodes(Long accountId, Date expirationDate) { this.accountId = accountId; this.expirationDate = expirationDate; delegate = new HashSet<String>(); } @Override public String toXml() { String retVal; try { retVal = StringUtils.toXml(this); } catch (JAXBException e) { retVal = e.toString(); } return retVal; } public Long getAccountId() { return accountId; } public void setAccountId(Long accountId) { this.accountId = accountId; } public Set<String> getCodes() { return delegate; } @XmlElement(name = "code") public void setCodes(Set<String> codes) { delegate = codes; } public Date getExpirationDate() { return expirationDate; } public void setExpirationDate(Date expirationDate) { this.expirationDate = expirationDate; } public boolean add(String s) { return delegate.add(s); } public int size() { return delegate.size(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BuddyCodes that = (BuddyCodes) o; if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) return false; if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null) return false; if (expirationDate != null ? !expirationDate.equals(that.expirationDate) : that.expirationDate != null) return false; return true; } @Override public int hashCode() { int result = accountId != null ? accountId.hashCode() : 0; result = 31 * result + (expirationDate != null ? expirationDate.hashCode() : 0); result = 31 * result + (delegate != null ? delegate.hashCode() : 0); return result; } }
Why does inheritance matter at all?
I did not understand this, but I have another DTO in a similar layout (BuddyTypes BuddyType). BuddyType has 2 members: Long and String. Both are annotated as an XmlElement. It works well.
The problem seems to be that the members of the set making up the delegate are not annotated in my problematic case, and I don't know how to annotate the parent element. As an inherited class, it would be pointless to have some default name / annotation. But I tried this insanity, and the annotation is ignored - I saw how the annotations of the parent elements are ignored, so this is not new.
I don't know if this is possible, but I need to annotate the parent element.