How to marshall a string using JAXB that sometimes contains XML content and sometimes does not?

后端 未结 3 1814
小蘑菇
小蘑菇 2020-12-04 02:47

Consider this example -

I have a class called Report that has a field of type Message. The Message class has a field called \"body\" which is a string. \"body\" can

相关标签:
3条回答
  • 2020-12-04 02:50

    Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

    This use case is mapped using the @XmlAnyElement annotation and specifying a DOMHandler. There appears to be bug when doing this with the JAXB RI, but the following use case works with EclipseLink JAXB (MOXy).

    BodyDomHandler

    By default a JAXB impleemntation will represent unmapped content as a DOM node. You can leverage a DomHandler to an alternate representation of the DOM, In this case we will represent the DOM as a String.

    import java.io.*;
    import javax.xml.bind.ValidationEventHandler;
    import javax.xml.bind.annotation.DomHandler;
    import javax.xml.transform.Source;
    import javax.xml.transform.stream.*;
    
    public class BodyDomHandler implements DomHandler<String, StreamResult> {
    
        private static final String BODY_START_TAG = "<body>";
        private static final String BODY_END_TAG = "</body>";
    
        private StringWriter xmlWriter = new StringWriter();
    
        public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
            return new StreamResult(xmlWriter);
        }
    
        public String getElement(StreamResult rt) {
            String xml = rt.getWriter().toString();
            int beginIndex = xml.indexOf(BODY_START_TAG) + BODY_START_TAG.length();
            int endIndex = xml.indexOf(BODY_END_TAG);
            return xml.substring(beginIndex, endIndex);
        }
    
        public Source marshal(String n, ValidationEventHandler errorHandler) {
            try {
                String xml = BODY_START_TAG + n.trim() + BODY_END_TAG;
                StringReader xmlReader = new StringReader(xml);
                return new StreamSource(xmlReader);
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    

    Message

    Below is how you would specify the @XmlAnyElement annotation on your Message class.

    import javax.xml.bind.annotation.XmlAnyElement;
    import javax.xml.bind.annotation.XmlType;
    
    @XmlType(propOrder = { "body" })
    public class Message
    {
        private String body;
        public String getBody() { return body; }
        @XmlAnyElement(BodyDomHandler.class)
        public void setBody(String body) { this.body = body; }
    }
    

    Output

    Below is the output from running your SerialziationTest:

    <?xml version="1.0" encoding="UTF-8"?>
    <Report>
       <message>
          <body>Sample report message.</body>
       </message>
    </Report>
    <?xml version="1.0" encoding="UTF-8"?>
    <Report>
       <message>
          <body>
             <rootTag>
                <body>All systems online.</body>
             </rootTag>
          </body>
       </message>
    </Report>
    

    For More Information

    • http://blog.bdoughan.com/2011/04/xmlanyelement-and-non-dom-properties.html
    • http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

    NOTE - Bug in JAXB RI

    There appears to be a bug in the JAXB reference implementation, and the example code will result in a stack trace like the following:

    Exception in thread "main" javax.xml.bind.MarshalException
     - with linked exception:
    [com.sun.istack.internal.SAXException2: unable to marshal type "java.lang.String" as an element because it is missing an @XmlRootElement annotation]
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317)
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
        at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
        at forum12428727.SerializationTest.main(SerializationTest.java:20)
    Caused by: com.sun.istack.internal.SAXException2: unable to marshal type "java.lang.String" as an element because it is missing an @XmlRootElement annotation
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:216)
        at com.sun.xml.internal.bind.v2.runtime.LeafBeanInfoImpl.serializeRoot(LeafBeanInfoImpl.java:126)
        at com.sun.xml.internal.bind.v2.runtime.property.SingleReferenceNodeProperty.serializeBody(SingleReferenceNodeProperty.java:100)
        at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:664)
        at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:141)
        at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561)
        at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290)
        at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
        at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
        ... 3 more
    
    0 讨论(0)
  • 2020-12-04 02:50

    3 different solutions 1), 2) 3), here below :

    1) Following post is a the description of your solution Loresh :

    http://anna-safronova.livejournal.com/2524.html?thread=9180

    This is still missing limitations details.

    • With embeeded html, we need a <![CDATA block

    • JAXB's dependancy

    com.sun.xml.bind.marshaller.CharacterEscapeHandler

    Needs import jaxb-impl for compilation / and may be required for excution, e.g.

    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>  
     <version>2.2.4</version>
    
    • Limitation : this solution is Container-specific and may not run because of class-loading policy.

    2) Another similar approach is JDK's rt.jar dependancy

    com.sun.xml.internal.bind.CharacterEscapeHandler

    http://theopentutorials.com/tutorials/java/jaxb/jaxb-marshalling-and-unmarshalling-cdata-block/

    Same limitation / dependends on target JDK, and some tweaks on Eclipse/Maven are necessary (bad alternative / My opinion)

    3) Finally, the best solution was found on another post of Reg Whitton :

    https://stackoverflow.com/a/12637295/560410

    and this is the detailed reciepe :

    http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

    Worked perfect for me !

    0 讨论(0)
  • 2020-12-04 02:58

    If its only for Marshalling, and to ignore the < and >, We can use the following:

    marshaller.setProperty("com.sun.xml.bind.marshaller.CharacterEscapeHandler",
                    new CharacterEscapeHandler() {
                        @Override
                        public void escape(char[] ac, int i, int j, boolean flag,
                                Writer writer) throws IOException {
                            writer.write(ac, i, j);
                        }
                    });
    
    0 讨论(0)
提交回复
热议问题