简体   繁体   中英

JAXB - Composite pattern and @XmlValue

Following up JAXB and Composite Pattern , I managed to map:

<precondition>
    <or>
        <and>
            <just><query>foo</query></just>
            <just><query>bar</query></just>
        </and>
        <just><query>baz</query></just>
    </or>
</precondition>

But I'd like to map:

<precondition>
    <or>
        <and>
            <query>foo</query>
            <query>bar</query>
        </and>
        <query>baz</query>
    </or>
</precondition>

My JAXB class hierarchy is as follows:

@XmlRootElement
@XmlSeeAlso({SimplePreconditionQuery.class, CompoundAndPreconditionQuery.class, CompoundOrPreconditionQuery.class})
public abstract class PreconditionQuery {
    // JAXB does not deal with interfaces by default >:(
}

With several kinds of queries:

@XmlSeeAlso(PreconditionQuery.class)
@XmlRootElement(name = "just")
public class SimplePreconditionQuery extends PreconditionQuery {

    private String query;

    @XmlElement(name = "query")
    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }
}

The compound ones (AND/OR) are very similar:

@XmlSeeAlso(PreconditionQuery.class)
@XmlRootElement(name = "and")
public class CompoundAndPreconditionQuery extends PreconditionQuery {

    private Collection<PreconditionQuery> preconditionQueries = newArrayList();

    @XmlElementRef(name = "query")
    public Collection<PreconditionQuery> getPreconditionQueries() {
        return preconditionQueries;
    }

    public void setPreconditionQueries(Collection<PreconditionQuery> preconditionQueries) {
        this.preconditionQueries = preconditionQueries;
    }
}

And the enclosing bean:

public class Precondition {

    private PreconditionQuery query;

    @XmlElementRef(required = true)
    public PreconditionQuery getQuery() {
        return query;
    }

    public void setQuery(PreconditionQuery query) {
        this.query = query;
    }
}

JAXB won't allow me to just map a @XmlValue on SimplePreconditionQuery . Why and what's the alternative?

The Problem

If you simply do:

import javax.xml.bind.annotation.*;

@XmlRootElement(name = "query")
public class SimplePreconditionQuery extends PreconditionQuery {

    private String query;

    @XmlValue
    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }

}

You will get the following exception because SimplePreconditionQuery subclasses something other than java.lang.Object .

Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
@XmlValue is not allowed on a class that derives another class.
    this problem is related to the following location:
        at public java.lang.String forum26714143.SimplePreconditionQuery.getQuery()
        at forum26714143.SimplePreconditionQuery
        at @javax.xml.bind.annotation.XmlSeeAlso(value=[class forum26714143.SimplePreconditionQuery, class forum26714143.CompoundAndPreconditionQuery, class forum26714143.CompoundOrPreconditionQuery])
        at public forum26714143.PreconditionQuery forum26714143.Precondition.getQuery()
        at forum26714143.Precondition

Removing PreconditionQuery from the inheritance hierarchy.

We can use the @XmlTransient annotation at the class level to remove the class from JAXB. All the properties from the super class will be treated as properties of the super class.

import javax.xml.bind.annotation.*;

@XmlSeeAlso({SimplePreconditionQuery.class, CompoundAndPreconditionQuery.class, CompoundOrPreconditionQuery.class})
@XmlTransient
public abstract class PreconditionQuery {
    // JAXB does not deal with interfaces by default >:(
}

Now we get the following exception:

Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 3 counts of IllegalAnnotationExceptions
Invalid @XmlElementRef : Type "class forum26714143.PreconditionQuery" or any of its subclasses are not known to this context.
    this problem is related to the following location:
        at public forum26714143.PreconditionQuery forum26714143.Precondition.getQuery()
        at forum26714143.Precondition

Making JAXB Aware of the Subclasses

Now that we have removed PreconditionQuery from the set of classes that JAXB cares about, we now longer benefit from the @XmlSeeAlso annotation that it contains. As such we need to make our @XmlElementRef annotations more explicit:

Precondition

import javax.xml.bind.annotation.*;

@XmlRootElement
public class Precondition {

    private PreconditionQuery query;

    @XmlElementRefs({
        @XmlElementRef(name="and", type = CompoundAndPreconditionQuery.class),
        @XmlElementRef(name="or", type= CompoundOrPreconditionQuery.class),
        @XmlElementRef(name="query", type=SimplePreconditionQuery.class)
    })
    public PreconditionQuery getQuery() {
        return query;
    }

    public void setQuery(PreconditionQuery query) {
        this.query = query;
    }

}

CompoundAndPreconditionQuery

You will also need to do this for CompoundAndPreconditionQuery and CompoundAndPreconditionQuery .

import java.util.*;
import javax.xml.bind.annotation.*;

@XmlRootElement(name = "and")
public class CompoundAndPreconditionQuery extends PreconditionQuery {

    private Collection<PreconditionQuery> preconditionQueries = new ArrayList();

    @XmlElementRefs({
            @XmlElementRef(name="and", type = CompoundAndPreconditionQuery.class),
            @XmlElementRef(name="or", type= CompoundOrPreconditionQuery.class),
            @XmlElementRef(name="query", type=SimplePreconditionQuery.class)
    })
    public Collection<PreconditionQuery> getPreconditionQueries() {
        return preconditionQueries;
    }

    public void setPreconditionQueries(Collection<PreconditionQuery> preconditionQueries) {
        this.preconditionQueries = preconditionQueries;
    }

}

Handling Interfaces

// JAXB does not deal with interfaces by default >:(

Now that JAXB is no longer aware of the Precondition query class, you could make it an interface if you wanted to.

public interface PreconditionQuery {

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM