XmlAdapter и XmlIDREF в moxy jaxb - PullRequest
5 голосов
/ 30 июля 2011

Я пытаюсь использовать MOXy JAXB для сериализации класса A, который выглядит следующим образом:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement
public class A {
    private Map<Foo, Bar> fooBar = new HashMap<Foo, Bar>();
    private Set<Foo> foos = new HashSet<Foo>();

    @XmlJavaTypeAdapter(FooBarMapAdapter.class)
    public Map<Foo, Bar> getFooBar() {
        return fooBar;
    }

    public void setFooBar(Map<Foo, Bar> fooBar) {
        this.fooBar = fooBar;
    }

    @XmlElement
    public Set<Foo> getFoos() {
        return foos;
    }

    public void setFoos(Set<Foo> foos) {
        this.foos = foos;
    }
}

Дело в том, что объекты Foo в полях "foos" являются надмножеством объектов на карте fooBar,Поэтому я хотел бы «связать» ключевые элементы карты «fooBar» с соответствующими элементами в списке «foos».Я пробовал это с использованием аннотаций XmlID и XmlIDREF:

@XmlAccessorType(XmlAccessType.NONE)
public class Foo {
    private String xmlId;

    @XmlID
    @XmlAttribute
    public String getXmlId() {
        return xmlId;
    }

    public void setXmlId(String xmlId) {
        this.xmlId = xmlId;
    }
}


@XmlAccessorType(XmlAccessType.NONE)
public class Bar {
    // Some code...
}

Затем в моем XmlAdapter я попытался использовать аннотацию XmlIDREF для объекта foo адаптированных записей карты:

public class FooBarMapAdapter extends
        XmlAdapter<FooBarMapAdapter.FooBarMapType, Map<Foo, Bar>> {
    public static class FooBarMapType {
        public List<FooBarMapEntry> entries = new ArrayList<FooBarMapEntry>();
    }

    @XmlAccessorType(XmlAccessType.NONE)
    public static class FooBarMapEntry {
        private Foo foo;
        private Bar bar;

        @XmlIDREF
        @XmlAttribute
        public Foo getFoo() {
            return foo;
        }

        public void setFoo(Foo foo) {
            this.foo = foo;
        }

        @XmlElement
        public Bar getBar() {
            return bar;
        }

        public void setBar(Bar bar) {
            this.bar = bar;
        }
    }

    @Override
    public FooBarMapType marshal(Map<Foo, Bar> map) throws Exception {
        FooBarMapType fbmt = new FooBarMapType();
        for (Map.Entry<Foo, Bar> e : map.entrySet()) {
            FooBarMapEntry entry = new FooBarMapEntry();
            entry.setFoo(e.getKey());
            entry.setBar(e.getValue());
            fbmt.entries.add(entry);
        }
        return fbmt;
    }

    @Override
    public Map<Foo, Bar> unmarshal(FooBarMapType fbmt) throws Exception {
        Map<Foo, Bar> map = new HashMap<Foo, Bar>();
        for (FooBarMapEntry entry : fbmt.entries) {
            map.put(entry.getFoo(), entry.getBar());
        }
        return map;
    }
}

При маршалинге приведенный выше код работает должным образом и создает следующий XML:

<?xml version="1.0" encoding="UTF-8"?>
<a>
   <fooBar>
      <entries foo="nr1">
         <bar/>
      </entries>
   </fooBar>
   <foos xmlId="nr1"/>
</a>

Для тестирования unmarshal я использую следующий тест-код:

public class Test {
    public static void main(String[] args) throws Exception {
        A a = new A();

        Map<Foo, Bar> map = new HashMap<Foo, Bar>();
        Foo foo = new Foo();
        foo.setXmlId("nr1");
        Bar bar = new Bar();
        map.put(foo, bar);
        a.setFooBar(map);
        a.setFoos(map.keySet());

        final File file = new File("test.xml");
        if (!file.exists())
            file.createNewFile();
        FileOutputStream fos = new FileOutputStream(file);

        JAXBContext jc = JAXBContext.newInstance(A.class);
        Marshaller m = jc.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        m.marshal(a, fos);

        FileInputStream fis = new FileInputStream(file);
        Unmarshaller um = jc.createUnmarshaller();
        A newA = (A) um.unmarshal(fis);

        System.out.println(newA.getFooBar());
    }
}

Этот код создает(для меня) неожиданный результат:

{null=test.moxy.Bar@373c0b53}

То есть объект Foo, используемый в качестве ключа на карте, является нулевым.Если я дважды меняю адаптер карты и выполняю маршалинг объекта Foo, вместо использования идентификатора ссылки, я не получаю этот нулевой указатель.

Мне удалось найти несколько сообщений об этом в Google с помощью JAXB-RI, где проблему можно решить, написав IDResolver, как описано в http://weblogs.java.net/blog/2005/08/15/pluggable-ididref-handling-jaxb-20. К сожалению, мне не удалось найти какую-либо информацию о таком классе в MOXy JAXB JavaDoc.

Предложениедля обхода Из ответа Блеза Дафана я понял, что это ошибка в реализации MOXy JAXB.Я смог сделать (некрасивый) обходной путь для этой ошибки.Идея состоит в том, что вместо использования XMLAdapter карта «конвертируется» в определяющий класс.Класс A теперь выглядит следующим образом:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement
public class A {
    private Map<Foo, Bar> fooBar = new HashMap<Foo, Bar>();
    private Set<Foo> foos = new HashSet<Foo>();

    // Due to a bug a XMLAdapter approch is not possible when using XmlIDREF.
    // The map is mapped by the wrapper method getXmlableFooBarMap.
    // @XmlJavaTypeAdapter(FooBarMapAdapter.class)
    public Map<Foo, Bar> getFooBar() {
        return fooBar;
    }

    public void setFooBar(Map<Foo, Bar> fooBar) {
        this.fooBar = fooBar;
    }

    @XmlElement
    public Set<Foo> getFoos() {
        return foos;
    }

    public void setFoos(Set<Foo> foos) {
        this.foos = foos;
    }

    // // WORKAROUND FOR JAXB BUG /////
    private List<FooBarMapEntry> mapEntries;

    @XmlElement(name = "entry")
    public List<FooBarMapEntry> getXmlableFooBarMap() {
        this.mapEntries = new LinkedList<FooBarMapEntry>();
        if (getFooBar() == null)
            return mapEntries;

        for (Map.Entry<Foo, Bar> e : getFooBar().entrySet()) {
            FooBarMapEntry entry = new FooBarMapEntry();
            entry.setFoo(e.getKey());
            entry.setBar(e.getValue());
            mapEntries.add(entry);
        }

        return mapEntries;
    }

    public void setXmlableFooBarMap(List<FooBarMapEntry> entries) {
        this.mapEntries = entries;
    }

    public void transferFromListToMap() {
        fooBar = new HashMap<Foo, Bar>();
        for (FooBarMapEntry entry : mapEntries) {
            fooBar.put(entry.getFoo(), entry.getBar());
        }
    }
}

После демаршала теперь необходимо вызвать метод TransferFromListToMap.Таким образом, следующая строка должна быть добавлена ​​сразу после получения ссылки на newA:

newA.transferFromListToMap();

Будут признательны за любые предложения по улучшению обходного пути / исправлению ошибки:).

1 Ответ

1 голос
/ 02 августа 2011

Примечание: Я EclipseLink JAXB (MOXy) lead.

Я смог подтвердить проблему, которую вывидите:

Почему происходит проблема

Проблема связана с обработкой MOXy *Логика 1020 * до обработки логики @XmlIDREF.MOXy выполняет один проход документа XML, и в конце обрабатываются отношения @XmlIDREF, чтобы гарантировать, что все объекты, на которые имеются ссылки, были построены (так как ссылка может предшествовать объекту, на который есть ссылка, как в этом случае).

Я постараюсь опубликовать обходной путь к этой проблеме, и вы можете отслеживать наш прогресс по этой проблеме, используя вышеуказанную ошибку.

...