4

I have a Java application interoperable with several others information systems

A same object could be mapped in differents XML files according to the information system targeted

My question is : Is there a Java solution to perform serveral XML mapping/binding on the same object

Somthing similar to Bean Validation groups that enable to validate an object with differents validation profile

In a JAXB style it could be something like that for instance:

// pseudocode
@XmlRootElement(name="person", , profile="profile1")
@XmlRootElement(name="individual", profile="profile2")
@XmlRootElement(name="human", profile="profile3")
public class Person {

    @XmlElement(name = "lastName", profile="profile1")
    @XmlElement(name = "surname", profile="profile2")
    @XmlElement(name = "famillyName", profile="profile3")
    private String lastName;

    //...
}

and then

    // pseudocode
    File file = new File("C:\\file.xml");
    JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
    Marshaller jaxbMarshaller = jaxbContext.createMarshaller("profile1");
    jaxbMarshaller.marshal(person, file);

    //...

    File file = new File("C:\\file.xml");
    JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
    Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller("profile1");
    Person person = (Person) jaxbUnmarshaller.unmarshal(file);

may be it's possible to do such a thing with JAXB but I don't find out


Edit

The response of @Martin Serrano give me some clues to optimize things with JAXB.

An abstract class to rule them all:

@XmlTransient
public abstract class APerson {

    protected String lastname;

    public APerson() {
    }

    public APerson(APerson p) {
        lastname = p.lastname;
    }

    public abstract String getLastname();

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + "{" + lastname + "}";
    }
}

And concretes classes that do the mapping:

A class Person for the default mapping:

@XmlRootElement
public class Person extends APerson {

    public Person() {
    }

    public Person(APerson p) {
        super(p);
    }

    @Override
    public String getLastname() {
        return lastname;
    }
}

And other classes for alternate mapping:

@XmlRootElement
public class Human extends APerson {

    public Human() {
    }

    public Human(APerson p) {
        super(p);
    }

    @Override
    @XmlElement(name = "famillyName")
    public String getLastname() {
        return lastname;
    }
}


@XmlRootElement
public class Individual extends APerson{   

    public Individual() {
    }

    public Individual(APerson p) {
        super(p);
    }    

    @Override
    @XmlElement(name = "surname")
    public String getLastname() {
        return lastname;
    }
}

And to test :

public class Main {

    public static void main(String[] args) throws Exception {
        Person person = new Person();
        person.setLastname("Doe");

        String fileName = "person.xml";
        marshal(person, fileName);
        person = unmarshal(Person.class, fileName);
        System.out.println(person);

        fileName = "human.xml";
        Human human = new Human(person);
        marshal(human, fileName);
        human = unmarshal(Human.class, fileName);
        System.out.println(human);
        person = new Person(human);
        System.out.println(person);

        fileName = "individual.xml";
        Individual individual = new Individual(person);
        marshal(individual, fileName);
        individual = unmarshal(Individual.class, fileName);
        System.out.println(individual);
        person = new Person(individual);
        System.out.println(person);
    }

    private static <P extends APerson> void marshal(P person, String fileName) throws JAXBException {
        File file = new File(fileName);
        JAXBContext context = JAXBContext.newInstance(person.getClass());
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(person, file);
        marshaller.marshal(person, System.out);
    }

    private static <P extends APerson> P unmarshal(Class<P> cl, String fileName) throws JAXBException {
        File file = new File(fileName);
        JAXBContext context = JAXBContext.newInstance(cl);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        P person = (P) unmarshaller.unmarshal(file);
        return person;
    }

}

as a result :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
    <lastname>Doe</lastname>
</person>
Person{Doe}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<human>
    <famillyName>Doe</famillyName>
</human>
Human{Doe}
Person{Doe}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<individual>
    <surname>Doe</surname>
</individual>
Individual{Doe}
Person{Doe}

That the best solution I found to have distinct mappings and to avoid code duplication, so we just have to implements the getters with the mapping.

Let's see if it's working on a much bigger problem!

But although it's a much better than having 3 disinct classes, it's still not 3 mappings on the same class...

kwisatz
  • 1,266
  • 3
  • 16
  • 36

1 Answers1

0

The simplest way to do something like this would be to use different facades for each system. This would allow you to change the way JAXB de/serializes the objects. It would also allow you to do other transformations and adapting depending on the systems in question. The base Person class would have your default serialization and each system could use the facade appropriate for it if necessary. This is basically the approach outlined in the unofficial jaxb guide on mapping interfaces.

Such facades could be registered for each system allowing you to keep system specific profiles out of the code of each of the core types.

Using your example, the core interface would be:

@XmlRootElement(name="person")
public interface Person {
  @XmlElement(name = "lastName")
  private String lastName;
}

Then for System1 you would have:

@XmlRootElement(name="individual")
public class Individual implements Person {
}

Another system might have:

@XmlRootElement(name="human")
public class Human implements Person {
}
Martin Serrano
  • 3,727
  • 1
  • 35
  • 48
  • I'm not sure to well understand, you mean I'll have an class `Person` whith the profile1 mapping, and a class `Individual` whith the mapping corresponing to profile2 use one class in the application, let say `Person`, and transform the `Person` into `Individual` to get the XML corresponding to profile2 and vice versa? (using bean to bean mapping between `Person` and `Individual` for instance) – kwisatz May 05 '15 at 12:43