FULL PRODUCT VERSION :
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) Client VM (build 25.112-b15, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
A DESCRIPTION OF THE PROBLEM :
JAX-B marshal/unmarshal operations are not symmetric for strings containing newline characters (CR, LF or CRLF) (i.e. the resulting string after a marshal/unmarshal cycle isn't what the original string was).
Since XML parsers must normalize attribute values by replacing newline characters with spaces (reference: http://www.w3.org/TR/REC-xml/#AVNormalize), newline characters need to be escaped properly during marshalling. This escaping seems to be missing in JAX-B. In other parts of the JDK, this is handled properly, e.g. Transformer properly escapse newline characters.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See attached test case. The 'testWriter' method shows the problem.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import junit.framework.TestCase;
import org.w3c.dom.Document;
public class CrLfMarshalUnmarshalTest extends TestCase {
private static final String VALUE = "abc\r\nd\re\nf";
private Bean fElem;
private Marshaller fMarshaller;
private Unmarshaller fUnmarshaller;
@Override
protected void setUp() throws Exception {
super.setUp();
JAXBContext ctx = JAXBContext.newInstance(Bean.class);
fMarshaller = ctx.createMarshaller();
fUnmarshaller = ctx.createUnmarshaller();
fElem = new Bean();
fElem.setAttributeString(VALUE);
fElem.setElementString(VALUE);
}
public void testWriter() throws Exception {
StringWriter sw = new StringWriter();
fMarshaller.marshal(fElem, sw);
String resultXml = sw.toString();
System.out.println(resultXml);
Bean resultElem = (Bean) fUnmarshaller.unmarshal(new StringReader(resultXml));
assertEquals(VALUE, resultElem.getAttributeString());
assertEquals(VALUE, resultElem.getElementString());
}
public void testDomAndTransform() throws Exception {
StringWriter sw = new StringWriter();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.newDocument();
DOMResult result = new DOMResult(d);
fMarshaller.marshal(fElem, result);
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.transform(new DOMSource(d), new StreamResult(sw));
String resultXml = sw.toString();
System.out.println(resultXml);
Bean resultElem = (Bean) fUnmarshaller.unmarshal(new StringReader(resultXml));
assertEquals(VALUE, resultElem.getAttributeString());
assertEquals(VALUE, resultElem.getElementString());
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"fElementString", "fAttributeString"})
@XmlRootElement(name = "Bean")
public static class Bean {
@XmlElement(name = "ElementString")
protected String fElementString;
@XmlAttribute(name = "AttributeString")
protected String fAttributeString;
public String getElementString() {
return fElementString;
}
public void setElementString(String elementString) {
fElementString = elementString;
}
public String getAttributeString() {
return fAttributeString;
}
public void setAttributeString(String attributeString) {
fAttributeString = attributeString;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
See attached test case. The 'testDomAndTransform' method shows a workaround: the actual xml output is delegated to a Transformer, which properly escapes newline characters.