javajaxbxmladapter

JAXB using @XmlJavaTypeAdapter to marshal a subset of a Collection


I've got an entity which contains a collection of a different type of entities. What I want to do is have JAXB marshal only a select subset of the collection, based on some criteria.

@XmlRootElement
@Entity
public class A{

    // other fields
    @OneToMany(mappedBy = "x", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Collection<B> bees;

    @XmlJavaTypeAdapter(BFormatter.class)
    public Collection<B> getBees() {
        return bees;
    }

    public void setBees(Collection<B> bees) {
        this.bees= bees;
    }
}


@XmlRootElement
@Entity
public class B{
    // fields
}

public class BFormatter extends XmlAdapter<Collection<B>, Collection<B>>{

    @Override
    public Collection<B> unmarshal(Collection<B> v) throws Exception {
        return v;
    }

    @Override
    public Collection<B> marshal(Collection<B> v) throws Exception {
        Collection<B> subset;
        // making subset
        return subset;
    }

}

This results in errors saying "java.util.Collection is an interface, and JAXB can't handle interfaces" and that "java.util.Collection does not have a no-arg default constructor."

What am I doing wrong, and is this even the right way to go about it?


Solution

  • The important thing is that you can't adapt a Collection (an interface) to something JAXB can handle, since it doesn't marshal an ArrayList or some other collection class. It is designed to marshal (bean) classes containing fields that are Lists or similar, which is meant to "disappear", remaining as the mere repetition of its elements. In other words, there's no XML element representing the ArrayList (or whatever) itself.

    Therefore, the adapter has to modify the containing element. (See below for alternatives.) The following classes are working; just assemble a Root element and modify the AFormatter according to your design. (The comments refer to the example at https://jaxb.java.net/tutorial/section_6_2_9-Type-Adapters-XmlJavaTypeAdapter.html#Type%20Adapters:%20XmlJavaTypeAdapter.)

    (Most classes should be modified to avoid making fields public, but as it is, it is brief and working.)

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Root{ // Training
        @XmlElement
        private A a;  // Brochure
        public Root(){}
        public A getA(){ return a; }
        public void setA( A value ){ a = value; }
    }
    
    @XmlJavaTypeAdapter(AFormatter.class)
    public class A{  // Brochure
        private Collection<B> bees;
        public A(){
            bees = new ArrayList<>();
        }
        public Collection<B> getBees() {
            if( bees == null ) bees = new ArrayList<>();
            return bees;
        }
    }
    
    @XmlAccessorType(XmlAccessType.FIELD)
    public class B{  // Course
        @XmlElement
        private String id;
        public B(){}
        public String getId(){ return id; }
        public void setId( String value ){ id = value; }
    }
    
    public class AFormatter extends XmlAdapter<BeeHive, A>{
        @Override
        public A unmarshal(BeeHive v) throws Exception {
            A a = new A();
            for( B b: v.beeList ){
                a.getBees().add( b );
            }
            return a;
        }
        @Override
        public BeeHive marshal(A v) throws Exception {
        BeeHive beeHive = new BeeHive();
            for( B b: v.getBees() ){
                 if( b.getId().startsWith("a") ) beeHive.beeList.add( b ); 
            }                              
            return beeHive;
        }
    }
    
    public class BeeHive { // Courses
        @XmlElement(name="b")
        public List<B> beeList = new ArrayList<B>();
    }
    

    Alternatives: It would be quite simple if the regular getter of the B-list would return the ones that should be marshalled. If the application needs to see all, an alternative getter could be added. Or, the class could have a static flag that instructs the getter to return a List to be used for marshalling, or the regular list at other times.