1

The answer to my question

led me to further pursue the details of the issue regarding the use of XmlSeeAlso, XmlElementReference and the specification of classes involved in JaxbContext.newInstance.

I started with trying to answer the question:

i created the Junit test below. To do so I came accross:

In this state the code is compilable and runs - it marshals o.k. but does not umarshal as expected. The marshal result is:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Message>
    <BasicInfo ref="id001"/>
</Message>

Unmarshalling does not create a BasicInfo as would be expected from the code in the Adapter:

   public BasicInfo unmarshal(BasicInfoRef info) throws Exception {
     BasicInfo binfo=new BasicInfo();
     binfo.id=info.ref;
     return binfo;
   }

I find this all very confusing - things seem to be somewhat contradictory and mostly do not work as I'd expect regarding the JaxB settings involved. Most combinations do not work, those that do work do not give the full result yet. I assume that is also depending on the version and implementation being used.

  1. What nees to be done to get this working?
  2. What is a minimal set of XmlElementRef,XmlSeeAlso and JaxbContext newInstance referencing of the necessary classes?
  3. How can I check which JaxB Implementation is being used? I am using EclipseLink 2.3.2? and I'm not sure whether this uses MoxY or not.

Update: after consideration of:

  1. JAXB xsi:type subclass unmarshalling not working
  2. http://www.java.net/node/654579
  3. http://jaxb.java.net/faq/#jaxb_version
  4. http://www.eclipse.org/eclipselink/documentation/2.4/moxy/type_level003.htm
  5. Can JAXB marshal by containment at first then marshal by @XmlIDREF for subsequent references?

The modified code below is close to what is intended.

Errors that showed up while trying were:

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)

The code has quite a few comments which can be commented in and out to find out what happens if one tries ... I still have no clue what the optimal solution would be to avoid superfluous annotations.

modified code:

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);
        }
    }

}

This is the Junit Test as mentioned above (before update):

    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);
       } 
    }
4

0 回答 0