Может ли JAXB сначала маршал с помощью сдерживания, а затем маршал с помощью @XmlIDREF для последующих ссылок? - PullRequest
5 голосов
/ 28 сентября 2011

Мне интересно, возможно ли аннотировать мои классы так, чтобы в первый раз маршаллер встречал объект, он генерировал элемент XML соответствующего типа, но любая последующая ссылка на этот объект кем-либо еще будет иметь XML IDREFзапись создана?

1 Ответ

11 голосов
/ 28 сентября 2011

Вы можете использовать концепцию JAXB XmlAdapter, чтобы сделать что-то вроде следующего:

input.xml

Ниже приведен XML-документ, который я буду использоватьдля этого примера.3-я запись phone-number является ссылкой на 1-ю запись phone-number, а 5-я запись phone-number является ссылкой на 4-ю.: * 10101 *

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <phone-number id="A">
        <number>555-AAAA</number>
    </phone-number>
    <phone-number id="B">
        <number>555-BBBB</number>
    </phone-number>
    <phone-number id="A"/>
    <phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W">
        <number>555-WORK</number>
        <extension>1234</extension>
    </phone-number>
    <phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W"/>
</customer>

Клиент

Класс клиента поддерживает коллекцию объектов PhoneNumber.Один и тот же экземпляр PhoneNumber может появляться в коллекции несколько раз.

package forum7587095;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Customer {

    private List<PhoneNumber> phoneNumbers;

    @XmlElement(name="phone-number")
    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

}

PhoneNumber

Это класс, который может отображаться либо в самом документе, либо в видессылка.Это будет обработано с помощью XmlAdapter.XmlAdapter настраивается с использованием аннотации @XmlJavaTypeAdapter.Поскольку мы указали этот адаптер на уровне типа / класса, он будет применяться ко всем свойствам, относящимся к классу PhoneNumber:

package forum7587095;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlJavaTypeAdapter(PhoneNumberAdapter.class)
public class PhoneNumber {

    private String id;
    private String number;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public boolean equals(Object arg0) {
        if(null == arg0 || arg0.getClass() != this.getClass()) {
            return false;
        }
        PhoneNumber test = (PhoneNumber) arg0;
        if(!equals(id, test.getId())) {
            return false;
        }
        return equals(number, test.getNumber());
    }

    protected boolean equals(String control, String test) {
        if(null == control) {
            return null == test;
        } else {
            return control.equals(test);
        }
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

}

WorkPhoneNumber

На основе вашегокомментарий Я добавил подкласс PhoneNumber.

package forum7587095;

public class WorkPhoneNumber extends PhoneNumber {

    private String extension;

    public String getExtension() {
        return extension;
    }

    public void setExtension(String extension) {
        this.extension = extension;
    }

    @Override
    public boolean equals(Object arg0) {
        if(!super.equals(arg0)) {
            return false;
        }
        return equals(extension, ((WorkPhoneNumber) arg0).getExtension());
    }

}

PhoneNumberAdapter

Ниже приведена реализация XmlAdapter.Обратите внимание, что мы должны поддерживать, если объект PhoneNumber был замечен ранее.Если это так, мы заполняем только часть id объекта AdaptedPhoneNumber.

package forum7587095;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class PhoneNumberAdapter extends XmlAdapter<PhoneNumberAdapter.AdaptedPhoneNumber, PhoneNumber>{

    private List<PhoneNumber> phoneNumberList = new ArrayList<PhoneNumber>();
    private Map<String, PhoneNumber> phoneNumberMap = new HashMap<String, PhoneNumber>();

    @XmlSeeAlso(AdaptedWorkPhoneNumber.class)
    @XmlType(name="phone-number")
    public static class AdaptedPhoneNumber {
        @XmlAttribute public String id;
        public String number;

        public AdaptedPhoneNumber() {
        }

        public AdaptedPhoneNumber(PhoneNumber phoneNumber) {
            id = phoneNumber.getId();
            number = phoneNumber.getNumber();
        }

        public PhoneNumber getPhoneNumber() {
            PhoneNumber phoneNumber = new PhoneNumber();
            phoneNumber.setId(id);
            phoneNumber.setNumber(number);
            return phoneNumber;
        }

    }

    @XmlType(name="work-phone-number")
    public static class AdaptedWorkPhoneNumber extends AdaptedPhoneNumber {

        public String extension;

        public AdaptedWorkPhoneNumber() {
        }

        public AdaptedWorkPhoneNumber(WorkPhoneNumber workPhoneNumber) {
            super(workPhoneNumber);
            extension = workPhoneNumber.getExtension();
        }

        @Override
        public WorkPhoneNumber getPhoneNumber() {
            WorkPhoneNumber phoneNumber = new WorkPhoneNumber();
            phoneNumber.setId(id);
            phoneNumber.setNumber(number);
            phoneNumber.setExtension(extension);
            return phoneNumber;
        }
}

    @Override
    public AdaptedPhoneNumber marshal(PhoneNumber phoneNumber) throws Exception {
        AdaptedPhoneNumber adaptedPhoneNumber;
        if(phoneNumberList.contains(phoneNumber)) {
            if(phoneNumber instanceof WorkPhoneNumber) {
                adaptedPhoneNumber = new AdaptedWorkPhoneNumber();
            } else {
                adaptedPhoneNumber = new AdaptedPhoneNumber();
            }
            adaptedPhoneNumber.id = phoneNumber.getId();
        } else {
            if(phoneNumber instanceof WorkPhoneNumber) {
                adaptedPhoneNumber = new AdaptedWorkPhoneNumber((WorkPhoneNumber)phoneNumber);
            } else {
                adaptedPhoneNumber = new AdaptedPhoneNumber(phoneNumber);
            }
            phoneNumberList.add(phoneNumber);
        }
        return adaptedPhoneNumber;
    }

    @Override
    public PhoneNumber unmarshal(AdaptedPhoneNumber adaptedPhoneNumber) throws Exception {
        PhoneNumber phoneNumber = phoneNumberMap.get(adaptedPhoneNumber.id);
        if(null != phoneNumber) {
            return phoneNumber;
        }
        phoneNumber = adaptedPhoneNumber.getPhoneNumber();
        phoneNumberMap.put(phoneNumber.getId(), phoneNumber);
        return phoneNumber;
    }

}

Demo

Для обеспечения того же экземпляра XmlAdapterиспользуется для всех операций marshal и unmarshal, мы должны специально установить экземпляр XmlAdapter для Marshaller и Unmarshaller:

package forum7587095;

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setAdapter(new PhoneNumberAdapter());
        File xml = new File("src/forum7587095/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

        System.out.println(customer.getPhoneNumbers().get(0) == customer.getPhoneNumbers().get(2));
        System.out.println(customer.getPhoneNumbers().get(3) == customer.getPhoneNumbers().get(4));

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setAdapter(new PhoneNumberAdapter());
        marshaller.marshal(customer, System.out);
    }

}

Output

true
true
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <phone-number id="A">
        <number>555-AAAA</number>
    </phone-number>
    <phone-number id="B">
        <number>555-BBBB</number>
    </phone-number>
    <phone-number id="A"/>
    <phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W">
        <number>555-WORK</number>
        <extension>1234</extension>
    </phone-number>
    <phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W"/>
</customer>

Для получения дополнительной информации

...