user1946448
user1946448

Reputation: 21

Dynamic CDATA in JAXB using MOXy

I am using EclipseLink JAXB (MOXy). I have an issue where a property of a java class can be CDATA or not dynamically. For example,

class Embed{  
//@XmlValue only  
//OR @XmlValue @CDATA  
private String value; //THIS CAN BE CDATA OR NOT  
}

I have tried solving the issue using inheritance where one subclass has the property as just a value and another one as CDATA. The problem i have with this solution is that the generated Xml has xsi:type and xmlns:xsi information which i don't want since i am upgrading legacy code and i need the resulted xml exactly as the legacy one.

The solution i have tried:

@XmlAccessorType(XmlAccessType.FIELD)  
@XmlType(name = "itemType", namespace = "", propOrder = {"embed"}  
public class Item{  
 List`<Embed>` embed;  
 //getter and setters  
}

@XmlAccessorType(XmlAccessType.NONE)  
@XmlSeeAlso(EmbedDefault.class, EmbedAsCdata.class)  
public abstract class Embed{

}

@XmlAccessorType(XmlAccessType.FIELD)    
@XmlType( propOrder = {"value"})  
public class EmbedDefault extends Embed{

@XmlValue
protected String value;
 //getters and setters  
}

@XmlAccessorType(XmlAccessType.FIELD)  
@XmlType(propOrder = {"value"})  
public class EmbedAsCdata extends Embed {  

@XmlValue
@XmlCDATA
protected String value;
//getters and setters  
}

Is there such another easier approach?

Upvotes: 2

Views: 3224

Answers (1)

bdoughan
bdoughan

Reputation: 149037

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

I'm trying to come up with a decent workaround for you, but below is an enhancement request you can follow where we are going to enhance the @XmlCDATA annotation to make this use case much easier to support.


UPDATE #1

Below is an approach I'm working on for your use case. It requires a small change to MOXy which I have figured out but still need to fully test. You can track our progress on this issue using the following link:

DomHandler (CdataHandler)

JAXB has the concept of a DomHandler that allows you some extra control of what the XML looks like. We will leverage a DomHandler to add in a CDATA block when required.

package forum14145131;

import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.DomHandler;
import javax.xml.parsers.*;
import javax.xml.transform.Source;
import javax.xml.transform.dom.*;

import org.w3c.dom.*;

public class CdataHandler implements DomHandler<String, DOMResult> {

    private static DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();;

    @Override
    public DOMResult createUnmarshaller(ValidationEventHandler veh) {
        return new DOMResult();
    }

    @Override
    public String getElement(DOMResult domResult) {
        Document document = (Document) domResult.getNode();
        return document.getDocumentElement().getTextContent();
    }

    @Override
    public Source marshal(String string, ValidationEventHandler veh) {
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.newDocument();

            Node node;
            if(string.contains("<") || string.contains("&") || string.contains("&")) {
                node = document.createCDATASection(string);
            } else {
                node = document.createTextNode(string);
            }
            return new DOMSource(node);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

}

Embed

The @XmlAnyElement annotation is used to specify the DomHandler.

package forum14145131;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
class Embed{  

    @XmlAnyElement(CdataHandler.class)
    private String value; //THIS CAN BE CDATA OR NOT  

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}

For More Information


UPDATE #2

Below is an approach that you can use today using the existing EclipseLink library leveraging an XmlAdapter and a CharacterEscapeHandler:

XmlAdapter (CdataAdapter)

package forum14145131;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class CdataAdapter extends XmlAdapter<String, String> {

    @Override
    public String marshal(String string) throws Exception {
        if(string.contains("&") || string.contains("<") || string.contains("\"")) {
            return "<![CDATA[" + string + "]]>";
        } else {
            return string;
        }
    }
    @Override
    public String unmarshal(String string) throws Exception {
        return string;
    }

}

Embed

The @XmlJavaTypeAdapter annotation is used to specify the XmlAdapter.

package forum14145131;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
class Embed{  

    @XmlValue
    @XmlJavaTypeAdapter(CdataAdapter.class)
    private String value; //THIS CAN BE CDATA OR NOT  

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}

Demo

Below is how you specify the CharacterEscapeHandler. Note this overrides all the character escaping for this Marshaller. We do this so the CDATA portion doesn't get escaped. For production code your implementation of this method would need to be beefed up over what is presented in this example.

package forum14145131;

import java.io.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.oxm.CharacterEscapeHandler;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Embed.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum14145131/input.xml");
        Embed embed = (Embed) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(MarshallerProperties.CHARACTER_ESCAPE_HANDLER,
                new CharacterEscapeHandler() {
                    @Override
                    public void escape(char[] ac, int i, int j, boolean flag,
                            Writer writer) throws IOException {
                        writer.write(ac, i, j);
                    }
                });
        marshaller.marshal(embed, System.out);
    }

}

input.xml/Output

<?xml version="1.0" encoding="UTF-8"?>
<embed><![CDATA[Hello & World]]></embed>

Upvotes: 1

Related Questions