私の質問への答え
XmlSeeAlso、XmlElementReference の使用、および JaxbContext.newInstance に含まれるクラスの仕様に関する問題の詳細をさらに追求するようになりました。
私は質問に答えようとすることから始めました:
以下のJunitテストを作成しました。そうするために、私は出くわしました:
この状態では、コードはコンパイル可能で実行されます。正常にマーシャリングされますが、期待どおりにマーシャリングされません。マーシャルの結果は次のとおりです。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Message>
<BasicInfo ref="id001"/>
</Message>
アンマーシャリングは、アダプターのコードから期待されるように、BasicInfo を作成しません。
public BasicInfo unmarshal(BasicInfoRef info) throws Exception {
BasicInfo binfo=new BasicInfo();
binfo.id=info.ref;
return binfo;
}
これはすべて非常に紛らわしいと思います - 物事はやや矛盾しているように見え、関連する JaxB 設定に関して期待どおりにほとんど機能しません。ほとんどの組み合わせは機能しません。機能する組み合わせでも、まだ完全な結果は得られません。それは、使用されているバージョンと実装にも依存していると思います。
- これを機能させるために何をする必要がありますか?
- 必要なクラスを参照する XmlElementRef、XmlSeeAlso、および JaxbContext newInstance の最小セットは何ですか?
- どの JaxB 実装が使用されているかを確認するにはどうすればよいですか? EclipseLink 2.3.2 を使用していますか? これがMoxYを使用しているかどうかはわかりません。
更新: 考慮した後:
- JAXB xsi:type サブクラスのアンマーシャリングが機能しない
- http://www.java.net/node/654579
- http://jaxb.java.net/faq/#jaxb_version
- http://www.eclipse.org/eclipselink/documentation/2.4/moxy/type_level003.htm
- JAXB は最初に包含によってマーシャリングし、その後の参照のために @XmlIDREF によってマーシャリングできますか?
以下の変更されたコードは、意図したものに近いものです。
試行中に表示されたエラーは次のとおりです。
javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: unable to marshal type "com.bitplan.storage.jaxb.TestRefId$BasicInfo$BasicInfoRef" as an element because it is missing an @XmlRootElement annotation]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
コードにはかなりの数のコメントがあり、コメントを付けたり外したりして、試してみるとどうなるかを知ることができます...余分な注釈を避けるための最適な解決策が何であるかはまだわかりません。
変更されたコード:
package com.bitplan.storage.jaxb;
import static org.junit.Assert.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.junit.Test;
/**
* Test the Reference/Id handling for Jaxb
*
* @author wf
*
*/
public class TestRefId {
@XmlDiscriminatorNode("@reforid")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlJavaTypeAdapter(RefOrId.Adapter.class)
public static class RefOrId {
@XmlAttribute(name = "reforid")
public String reforid;
public RefOrId() {
}
@XmlAttribute
public String id;
@XmlAttribute
public String ref;
public static class Adapter extends XmlAdapter<RefOrId, RefOrId> {
@Override
public RefOrId marshal(RefOrId idref) throws Exception {
if (idref == null)
return null;
System.out.println("marshalling " + idref.getClass().getSimpleName()
+ " reforid:" + idref.reforid);
if (idref instanceof Ref)
return new Id(((Ref) idref).ref);
return idref;
}
@Override
public RefOrId unmarshal(RefOrId idref) throws Exception {
System.out.println("unmarshalling " + idref.getClass().getSimpleName()
+ " reforid:" + idref.reforid);
return idref;
}
}
}
@XmlDiscriminatorValue("id")
@XmlAccessorType(XmlAccessType.FIELD)
public static class Id extends RefOrId {
public Id() {
reforid = "id";
}
public Id(String pId) {
this();
this.id = pId;
}
}
@XmlDiscriminatorValue("ref")
@XmlAccessorType(XmlAccessType.FIELD)
public static class Ref extends RefOrId {
public Ref() {
reforid = "ref";
}
public Ref(String pRef) {
this();
this.ref = pRef;
}
}
/*
* https://stackoverflow.com/questions/8292427/is-it-possible-to-xmlseealso-on-a
* -class-without-no-args-constructor-which-has
*/
@XmlRootElement(name = "BasicInfo")
public static class BasicInfo {
public BasicInfo() {
};
public BasicInfo(String pId, String pInfo) {
this.id = new Id(pId);
this.basic = pInfo;
}
// @XmlTransient
public RefOrId id;
public String basic;
}
@XmlRootElement(name = "SpecificInfoA")
// @XmlJavaTypeAdapter(BasicInfo.Adapter.class)
public static class SpecificInfoA extends BasicInfo {
public SpecificInfoA() {
};
public SpecificInfoA(String pId, String pInfo, String pInfoA) {
super(pId, pInfo);
infoA = pInfoA;
}
public String infoA;
}
@XmlRootElement(name = "Message")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({ SpecificInfoA.class, BasicInfo.class })
public static class Message {
public Message() {
};
public Message(BasicInfo pBasicInfo) {
basicInfos.add(pBasicInfo);
}
// @XmlJavaTypeAdapter(BasicInfo.Adapter.class)
// @XmlElement(name="basicInfo",type=BasicInfoRef.class)
@XmlElementWrapper(name = "infos")
@XmlElement(name = "info")
public List<BasicInfo> basicInfos = new ArrayList<BasicInfo>();
}
/**
* marshal the given message
*
* @param jaxbContext
* @param message
* @return - the xml string
* @throws JAXBException
*/
public String marshalMessage(JAXBContext jaxbContext, Message message)
throws JAXBException {
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(message, sw);
String xml = sw.toString();
return xml;
}
/**
* test marshalling and umarshalling a message
*
* @param message
* @throws JAXBException
*/
public void testMessage(Message message) throws JAXBException {
@SuppressWarnings("rawtypes")
Class[] classes = { Message.class, SpecificInfoA.class, BasicInfo.class }; // BasicInfo.class,};
// https://stackoverflow.com/questions/8318231/xmlseealso-alternative/8318490#8318490
// https://stackoverflow.com/questions/11966714/xmljavatypeadapter-not-being-detected
JAXBContext jaxbContext = JAXBContext.newInstance(classes);
String xml = marshalMessage(jaxbContext, message);
System.out.println(xml);
Unmarshaller u = jaxbContext.createUnmarshaller();
Message result = (Message) u.unmarshal(new StringReader(xml));
assertNotNull(result);
assertNotNull(message.basicInfos);
for (BasicInfo binfo : message.basicInfos) {
RefOrId id = binfo.id;
assertNotNull(id);
System.out.println("basicInfo-id " + id.getClass().getSimpleName()
+ " for reforid " + id.reforid);
// assertEquals(message.basicInfo.id, result.basicInfo.id);
}
xml = marshalMessage(jaxbContext, result);
System.out.println(xml);
}
/**
* switch Moxy
*
* @param on
* @throws IOException
*/
public void switchMoxy(boolean on) throws IOException {
File moxySwitch = new File(
"src/test/java/com/bitplan/storage/jaxb/jaxb.properties");
if (on) {
PrintWriter pw = new PrintWriter(new FileWriter(moxySwitch));
pw.println("javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory");
pw.close();
} else {
moxySwitch.delete();
}
}
@Test
public void testStackOverflow8292427() throws JAXBException, IOException {
boolean[] moxyOnList = { false };
for (boolean moxyOn : moxyOnList) {
switchMoxy(moxyOn);
System.out.println("Moxy used: " + moxyOn);
Message message = new Message(new BasicInfo("basicId001",
"basicValue for basic Info"));
message.basicInfos.add(new SpecificInfoA("specificId002",
"basicValue for specific Info", "specific info"));
message.basicInfos.add(new SpecificInfoA("specificId002",
"basicValue for specific Info", "specific info"));
testMessage(message);
}
}
}
これは、上記の Junit テストです (更新前)。
package com.bitplan.storage.jaxb;
import static org.junit.Assert.*;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import org.junit.Test;
// Test the Reference/Id handling for Jaxb
public class TestRefId {
/*
* https://stackoverflow.com/questions/8292427/is-it-possible-to-xmlseealso-on-a-class-without-no-args-constructor-which-has
*/
@XmlRootElement(name="BasicInfo")
@XmlJavaTypeAdapter(BasicInfo.Adapter.class)
public static class BasicInfo {
@XmlRootElement(name="BasicInfo")
@XmlAccessorType(XmlAccessType.FIELD)
public static class BasicInfoRef {
@XmlAttribute(name = "ref")
public String ref;
}
public static class Adapter extends XmlAdapter<BasicInfoRef,BasicInfo>{
@Override
public BasicInfoRef marshal(BasicInfo info) throws Exception {
BasicInfoRef infoRef = new BasicInfoRef();
infoRef.ref=info.id;
return infoRef;
}
@Override
public BasicInfo unmarshal(BasicInfoRef info) throws Exception {
BasicInfo binfo=new BasicInfo();
binfo.id=info.ref;
return binfo;
}
} // Adapter
public String id;
public String basic;
}
@XmlJavaTypeAdapter(BasicInfo.Adapter.class)
public static class SpecificInfoA extends BasicInfo {
public String infoA;
}
@XmlRootElement(name="Message")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({SpecificInfoA.class,BasicInfo.class})
public static class Message {
// https://stackoverflow.com/questions/3107548/xmljavatypeadapter-w-inheritance
@XmlElementRefs({
@XmlElementRef(name="basic", type=BasicInfo.class),
// @XmlElementRef(name="infoA", type=SpecificInfoA.class)
})
public BasicInfo basicInfo;
}
@Test
public void testStackOverflow8292427() throws JAXBException {
Message message=new Message();
SpecificInfoA info=new SpecificInfoA();
info.id="id001";
info.basic="basicValue";
info.infoA="infoAValue";
message.basicInfo=info;
@SuppressWarnings("rawtypes")
Class[] classes= {Message.class,SpecificInfoA.class,BasicInfo.class,BasicInfo.BasicInfoRef.class}; // BasicInfo.class,};
// https://stackoverflow.com/questions/8318231/xmlseealso-alternative/8318490#8318490
JAXBContext jaxbContext = JAXBContext.newInstance(classes);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(message, sw);
String xml=sw.toString();
System.out.println(xml);
Unmarshaller u = jaxbContext.createUnmarshaller();
Message result = (Message) u.unmarshal(new StringReader(xml));
assertNotNull(result);
assertNotNull("basicInfo should not be null",result.basicInfo);
assertEquals("id001",result.basicInfo.id);
}
}