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;
}
}
重要なのは、「foos」フィールドのFooオブジェクトは、fooBarマップのFooオブジェクトのスーパーセットであるということです。したがって、「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で、適応されたマップエントリのfooオブジェクトにXmlIDREFアノテーションを使用しようとしました。
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>
アンマーシャルをテストするために、私は次のテストコードを使用しています。
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オブジェクトはnullです。マップアダプタを変更してFooオブジェクトを2回マーシャリングすると、ID参照を使用する代わりに、このnullポインタを取得できません。
JAXB-RIを使用してGoogleでこれに関するいくつかの投稿を見つけることができました。ここでは、 http: //weblogs.java.net/blog/2005/08/15/pluggable-で説明されているようにIDResolverを記述して問題を解決できます。 ididref-handling-jaxb-20。残念ながら、MOXyJAXBJavaDocでそのようなクラスに関する情報を見つけることができませんでした。
回避策の提案 BlaiseDoughanの回答から、これはJAXBのMOXy実装のバグであることがわかりました。私はこのバグの(醜い)回避策を作ることができました。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();
より良い回避策/バグ修正のための提案はありがたいです:)。