Question

I am trying to parse a class with multiple bidirectional connections to other classes. To prevent the infinite loop problem and cause of other reasons I want to parse some properties in a custom way.

i.e I have the class MovieImpl (implements the movie interface) which contains a list of actors (PersonImpl) and one field for the director (PersonImpl) (and of course other fields):

@XmlRootElement
@XmlSeeAlso({ PersonImpl.class })
@Entity
public class MovieImpl implements Movie {

    @Id
    @Column(name = "movieId", unique = true)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    int _id;

    ...

    @ManyToOne(fetch = FetchType.LAZY, targetEntity = PersonImpl.class)
    @JoinColumn(name = "directorId")
    Person _director;

    ...

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = PersonImpl.class)
    @JoinTable(name = "movie_has_actors", joinColumns = { @JoinColumn(name = "movieId", nullable = false) }, inverseJoinColumns = { @JoinColumn(name = "personId", nullable = false) })
    Collection<Person> _actors;

    ...
}

my PersonImpl class has a list of movies:

@XmlRootElement
@XmlSeeAlso({ MovieImpl.class })
@Entity
public class PersonImpl implements Person {

    ...
    private int _id;
    private String _name;

    @ManyToMany(mappedBy = "_actors", targetEntity = MovieImpl.class)
    private Collection<Movie> _movies;

    ...

}

So is there a way with jaxb to display only the name and id of an actor when it is rendered from a movie object. And on the other hand: display only some other fields from a movie, when it is rendered from a person object?

i.e.:

<movie>
    <id>82303482</id>
    ...
    <director>
        <id>340980</id>
        <name>xyz</name>
    </director>
    <actors>
        <actor>
            <id>479834</id>
            <name>xyz</name>
        </actor>
        <actor>
            <id>3434234</id>
            <name>abc</name>
        </actor>
    ...
</movie>


<actor>
    <id>34843</id>
    <name>abc</name>
    ...
    <movies>
        <movie>
            <id>3489</id>
            <title>asdf</title>
            ...
        </movie>
        <movie>
            <id>4534534</id>
            <title>qwer</title>
            ...
        </movie>
    </movies>
</actor>

Thanks for help in advance.

Update: when I add the @XmlInverseReference annotation to the fields it gives me only something like:

...
<_actors>
    com.company.package...PersonImpl@2450c3
</_actors>
<_actors>
    com.company.package...PersonImpl@4e98166
</_actors>
<actors>
    com.company.package...PersonImpl@2450c3
</actors>
<actors>
    com.company.package...PersonImpl@4e98166
</actors>
...
  1. why are there 2 fields from the actor (_actor and actor)?
  2. why are the fields of the actor are not displayed?
Was it helpful?

Solution

Since based on your question tag you are using MOXy:

MOXy allows bidirectional mappings via the @XmlInverseReference extension (you will need to use EclipseLink 2.5.0 or later to have the relationship writeable in both directions):

MOXy also allows you to marshal a subset of the data using the Object Graphs extension:


Note

Below is what the answer should be, but there appears to be a marshalling bug wrt @XmlElementWrapper and @XmlInverseReference.

Java Model

MovieImpl

import java.util.Collection;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement(name="movie")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlNamedObjectGraph(
        name="partial",
        attributeNodes={
            @XmlNamedAttributeNode("_id"),
            @XmlNamedAttributeNode(value="_director", subgraph="partial"),
            @XmlNamedAttributeNode(value="_actors", subgraph="partial")
        },
        subgraphs={
            @XmlNamedSubgraph(
                name="location",
                attributeNodes = {
                    @XmlNamedAttributeNode("_id"),
                    @XmlNamedAttributeNode("_name")
                }
            )
        }
    )
@Entity
public class MovieImpl implements Movie {

    @XmlElement(name="id")
    @Id
    @Column(name = "movieId", unique = true)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    int _id;

    @XmlElement(name="director", type=PersonImpl.class)
    @ManyToOne(fetch = FetchType.LAZY, targetEntity = PersonImpl.class)
    @JoinColumn(name = "directorId")
    Person _director;

    @XmlElementWrapper(name="actors")
    @XmlElement(name="actor", type=PersonImpl.class)
    @XmlInverseReference(mappedBy="_actors")
    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = PersonImpl.class)
    @JoinTable(name = "movie_has_actors", joinColumns = { @JoinColumn(name = "movieId", nullable = false) }, inverseJoinColumns = { @JoinColumn(name = "personId", nullable = false) })
    Collection<Person> _actors;

    public Collection<Person> getActors() {
        return _actors;
    }

}

PersonImpl

import java.util.Collection;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement(name="actor")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlNamedObjectGraph(
        name="partial",
        attributeNodes={
            @XmlNamedAttributeNode("_id"),
            @XmlNamedAttributeNode("_name"),
            @XmlNamedAttributeNode(value="_movies", subgraph="partial")
        },
        subgraphs={
            @XmlNamedSubgraph(
                name="location",
                attributeNodes = {
                    @XmlNamedAttributeNode("_id"),
                    @XmlNamedAttributeNode("_name")
                }
            )
        }
    )
@Entity
public class PersonImpl implements Person {

    @XmlElement(name="id")
    private int _id;

    @XmlElement(name="name")
    private String _name;

    @XmlElementWrapper(name="movies")
    @XmlElement(name="movie", type=MovieImpl.class)
    @XmlInverseReference(mappedBy="_actors")
    @ManyToMany(mappedBy = "_actors", targetEntity = MovieImpl.class)
    private Collection<Movie> _movies;

}

Demo Code

Demo

import java.io.File;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(MovieImpl.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum21764131/input.xml");
        Movie movie = (Movie) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "partial");
        marshaller.marshal(movie.getActors().toArray()[0], System.out);
    }

}

OTHER TIPS

Expanding objects into XML in more than one way is not available with JAXB. If you have several collections of entities, interlinked mutually, the best strategy is to expand each entity object once, and only once. References to other entities should be annotated with IDREF. This, of course, requires an ID to be available with each object, but a synthetic ordinal will do.

If the XML structure you have posted is something that's required by other programs, you might consider processing the primary output with its IDREFs using XSLT.

See the last section in my JAXB tutorial

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top