JDK-8171195 : JAX-B Marshal/Unmarshal not symmetric for Strings with newline characters CR LF
  • Type: Bug
  • Component: xml
  • Sub-Component: jaxb
  • Affected Version: 8,9
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 2016-12-06
  • Updated: 2017-07-10
  • Resolved: 2017-07-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbd_minorResolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
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.
 


Comments
For JDK8 fix needs to be partially backported
10-07-2017

Fixed in JAXWS sync update
10-07-2017

To reproduce the issue, run the attached Junit test case. Following are the results on various JDK versions : JDK 8u112 - Fail JDK 8u122 ea - Fail JDK 9-ea + 148 - Fail Following is the output in the failing cases: ------------------------------------------------------ JUnit version 4.4 .Dec 14, 2016 9:00:19 AM com.sun.xml.internal.bind.v2.runtime.reflect.opt.AccessorInjector <clinit> INFO: The optimized code generation is disabled <?xml version="1.0" encoding="UTF-8" standalone="no"?><Bean AttributeString="abc&#13;&#10;d&#13;e&#10;f"><ElementString>abc&#13; d&#13;e f</ElementString></Bean> .<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Bean AttributeString="abc de f"><ElementString>abc de f</ElementString></Bean> E Time: 1.397 There was 1 failure: 1) testWriter(JI9045909) junit.framework.ComparisonFailure: null expected:<abc[ e ]f> but was:<abc[ de ]f> at junit.framework.Assert.assertEquals(Assert.java:81) at junit.framework.Assert.assertEquals(Assert.java:87) at JI9045909.testWriter(JI9045909.java:49) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:538) at junit.framework.TestCase.runTest(TestCase.java:168) at junit.framework.TestCase.runBare(TestCase.java:134) at junit.framework.TestResult$1.protect(TestResult.java:110) at junit.framework.TestResult.runProtected(TestResult.java:128) at junit.framework.TestResult.run(TestResult.java:113) at junit.framework.TestCase.run(TestCase.java:124) at junit.framework.TestSuite.runTest(TestSuite.java:232) at junit.framework.TestSuite.run(TestSuite.java:227) at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:81) at org.junit.internal.runners.CompositeRunner.runChildren(CompositeRunner.java:33) at org.junit.internal.runners.CompositeRunner.run(CompositeRunner.java:28) at org.junit.runner.JUnitCore.run(JUnitCore.java:130) at org.junit.runner.JUnitCore.run(JUnitCore.java:109) at org.junit.runner.JUnitCore.run(JUnitCore.java:100) at org.junit.runner.JUnitCore.runMain(JUnitCore.java:81) at org.junit.runner.JUnitCore.main(JUnitCore.java:44) FAILURES!!! Tests run: 2, Failures: 1
14-12-2016