XmlAdapter и XmlIDREF в moxy jaxb
Я пытаюсь использовать 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 карта "конвертируется" в определяющий класс. Класс А теперь выглядит так:
@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 ответ
Примечание: я ведущий EclipseLink JAXB (MOXy).
Я смог подтвердить проблему, которую вы видите:
Почему проблема происходит
Проблема связана с обработкой МОКСИ XmlAdapter
логика, прежде чем он обработал @XmlIDREF
логика. MOXy делает один проход документа XML, и @XmlIDREF
отношения обрабатываются в конце, чтобы гарантировать, что все ссылочные объекты были построены (поскольку ссылка может предшествовать ссылочному объекту, как в этом случае).
Я постараюсь опубликовать решение этой проблемы, и вы можете отслеживать наш прогресс по этой проблеме, используя вышеуказанную ошибку.