Skip to content

Commit

Permalink
Fix #377
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed May 14, 2020
1 parent 70dd97e commit ab17f00
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 20 deletions.
5 changes: 5 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ Joseph Petersen (jetersen@github)
* Reported #273: Input mismatch with case-insensitive properties
(2.12.0)

Ghenadii Batalski (ghenadiibatalski@github)

* Reported #377: `ToXmlGenerator` ignores `Base64Variant` while serializing `byte[]`
(2.12.0)

Jochen Schalanda (joschi@github)

* Reported #318: XMLMapper fails to deserialize null (POJO reference) from blank tag
Expand Down
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Project: jackson-dataformat-xml
(reported by Joseph P)
#318: XMLMapper fails to deserialize null (POJO reference) from blank tag
(reported by Jochen S)
#377: `ToXmlGenerator` ignores `Base64Variant` while serializing `byte[]`
(reported by Ghenadii B)
#397: `XmlReadContext` does not keep track of array index

2.11.1 (not yet released)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ public XmlMapper(XmlFactory xmlFactory, JacksonXmlModule module)
// 21-Jun-2017, tatu: Seems like there are many cases in XML where ability to coerce empty
// String into `null` (where it otherwise is an error) is very useful.
enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

// 13-May-2020, tatu: [dataformat-xml#377] Need to ensure we will keep XML-specific
// Base64 default as "MIME" (not MIME-NO-LINEFEEDS), to preserve pre-2.12
// behavior
setBase64Variant(Base64Variants.MIME);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,13 @@ public void writeLeafElement(XMLStreamWriter2 sw,

// binary element
public void writeLeafElement(XMLStreamWriter2 sw,
String nsURI, String localName,
byte[] data, int offset, int len)
String nsURI, String localName,
org.codehaus.stax2.typed.Base64Variant base64variant,
byte[] data, int offset, int len)
throws XMLStreamException;

// empty element to represent null
public void writeLeafNullElement(XMLStreamWriter2 sw,
String nsURI, String localName)
String nsURI, String localName)
throws XMLStreamException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -830,22 +830,24 @@ public void writeBinary(Base64Variant b64variant,
if (_nextName == null) {
handleMissingName();
}
final org.codehaus.stax2.typed.Base64Variant stax2base64v = StaxUtil.toStax2Base64Variant(b64variant);
try {
if (_nextIsAttribute) {
// Stax2 API only has 'full buffer' write method:
byte[] fullBuffer = toFullBuffer(data, offset, len);
_xmlWriter.writeBinaryAttribute("", _nextName.getNamespaceURI(), _nextName.getLocalPart(), fullBuffer);
_xmlWriter.writeBinaryAttribute(stax2base64v,
"", _nextName.getNamespaceURI(), _nextName.getLocalPart(), fullBuffer);
} else if (checkNextIsUnwrapped()) {
// should we consider pretty-printing or not?
_xmlWriter.writeBinary(data, offset, len);
_xmlWriter.writeBinary(stax2base64v, data, offset, len);
} else {
if (_xmlPrettyPrinter != null) {
_xmlPrettyPrinter.writeLeafElement(_xmlWriter,
_nextName.getNamespaceURI(), _nextName.getLocalPart(),
data, offset, len);
stax2base64v, data, offset, len);
} else {
_xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
_xmlWriter.writeBinary(data, offset, len);
_xmlWriter.writeBinary(stax2base64v, data, offset, len);
_xmlWriter.writeEndElement();
}
}
Expand All @@ -865,23 +867,25 @@ public int writeBinary(Base64Variant b64variant, InputStream data, int dataLengt
if (_nextName == null) {
handleMissingName();
}
final org.codehaus.stax2.typed.Base64Variant stax2base64v = StaxUtil.toStax2Base64Variant(b64variant);
try {
if (_nextIsAttribute) {
// Stax2 API only has 'full buffer' write method:
byte[] fullBuffer = toFullBuffer(data, dataLength);
_xmlWriter.writeBinaryAttribute("", _nextName.getNamespaceURI(), _nextName.getLocalPart(), fullBuffer);
_xmlWriter.writeBinaryAttribute(stax2base64v,
"", _nextName.getNamespaceURI(), _nextName.getLocalPart(), fullBuffer);
} else if (checkNextIsUnwrapped()) {
// should we consider pretty-printing or not?
writeStreamAsBinary(data, dataLength);
writeStreamAsBinary(stax2base64v, data, dataLength);

} else {
if (_xmlPrettyPrinter != null) {
_xmlPrettyPrinter.writeLeafElement(_xmlWriter,
_nextName.getNamespaceURI(), _nextName.getLocalPart(),
toFullBuffer(data, dataLength), 0, dataLength);
stax2base64v, toFullBuffer(data, dataLength), 0, dataLength);
} else {
_xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
writeStreamAsBinary(data, dataLength);
writeStreamAsBinary(stax2base64v, data, dataLength);
_xmlWriter.writeEndElement();
}
}
Expand All @@ -892,7 +896,8 @@ public int writeBinary(Base64Variant b64variant, InputStream data, int dataLengt
return dataLength;
}

private void writeStreamAsBinary(InputStream data, int len) throws IOException, XMLStreamException
private void writeStreamAsBinary(org.codehaus.stax2.typed.Base64Variant stax2base64v,
InputStream data, int len) throws IOException, XMLStreamException
{
// base64 encodes up to 3 bytes into a 4 bytes string
byte[] tmp = new byte[3];
Expand All @@ -903,7 +908,7 @@ private void writeStreamAsBinary(InputStream data, int len) throws IOException,
len -= read;
if(offset == 3) {
offset = 0;
_xmlWriter.writeBinary(tmp, 0, 3);
_xmlWriter.writeBinary(stax2base64v, tmp, 0, 3);
}
if (len == 0) {
break;
Expand All @@ -912,11 +917,10 @@ private void writeStreamAsBinary(InputStream data, int len) throws IOException,

// we still have < 3 bytes in the buffer
if(offset > 0) {
_xmlWriter.writeBinary(tmp, 0, offset);
_xmlWriter.writeBinary(stax2base64v, tmp, 0, offset);
}
}


private byte[] toFullBuffer(byte[] data, int offset, int len)
{
// might already be ok:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,24 +396,26 @@ public void writeLeafElement(XMLStreamWriter2 sw,
_justHadStartElement = false;
}

// method definition changed in 2.12
@Override
public void writeLeafElement(XMLStreamWriter2 sw,
String nsURI, String localName,
byte[] data, int offset, int len)
String nsURI, String localName,
org.codehaus.stax2.typed.Base64Variant base64variant,
byte[] data, int offset, int len)
throws XMLStreamException
{
if (!_objectIndenter.isInline()) {
_objectIndenter.writeIndentation(sw, _nesting);
}
sw.writeStartElement(nsURI, localName);
sw.writeBinary(data, offset, len);
sw.writeBinary(base64variant, data, offset, len);
sw.writeEndElement();
_justHadStartElement = false;
}

@Override
public void writeLeafNullElement(XMLStreamWriter2 sw,
String nsURI, String localName)
String nsURI, String localName)
throws XMLStreamException
{
if (!_objectIndenter.isInline()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.fasterxml.jackson.dataformat.xml.util;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.stream.*;

import com.fasterxml.jackson.core.Base64Variant;
import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
Expand Down Expand Up @@ -109,4 +113,48 @@ public static String sanitizeXmlTypeName(String name)
}
return sb.toString();
}

/**
* Helper method used to "convert" Jackson's {@link Base64Variant} into corresponding
* Stax2 equivalent, to try to allow Jackson-style configuration for XML output as well.
*
* @param j64b Jackson base64 variant to find match for
*
* @return Stax2 Base64 variant that most closely resembles Jackson canonical Base64 variant
* passed in as argument
*
* @since 2.12
*/
public static org.codehaus.stax2.typed.Base64Variant toStax2Base64Variant(Base64Variant j64b) {
return Base64Mapper.instance.map(j64b);
}

private static class Base64Mapper {
public final static Base64Mapper instance = new Base64Mapper();

private final Map<String, org.codehaus.stax2.typed.Base64Variant> j2stax2
= new HashMap<>();
{
j2stax2.put(Base64Variants.MIME.getName(), org.codehaus.stax2.typed.Base64Variants.MIME);
j2stax2.put(Base64Variants.MIME_NO_LINEFEEDS.getName(),
org.codehaus.stax2.typed.Base64Variants.MIME_NO_LINEFEEDS);
j2stax2.put(Base64Variants.MODIFIED_FOR_URL.getName(),
org.codehaus.stax2.typed.Base64Variants.MODIFIED_FOR_URL);
j2stax2.put(Base64Variants.PEM.getName(), org.codehaus.stax2.typed.Base64Variants.PEM);
}

private Base64Mapper() {
}

public org.codehaus.stax2.typed.Base64Variant map(Base64Variant j64b) {
org.codehaus.stax2.typed.Base64Variant result = j2stax2.get(j64b.getName());
if (result == null) {
// 13-May-2020, tatu: in unexpected case of no match, default to what Stax2
// considers default, not Jackson: this for backwards compatibility with
// Jackson 2.11 and earlier
result = org.codehaus.stax2.typed.Base64Variants.getDefaultVariant();
}
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.fasterxml.jackson.dataformat.xml.ser;

import com.fasterxml.jackson.core.Base64Variant;
import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.XmlTestBase;

import static org.junit.Assert.*;

public class Base64VariantWriteTest extends XmlTestBase
{
public static class BinaryValue {
public byte[] value;

protected BinaryValue() { }
public BinaryValue(byte[] v) {
value = v;
}
}

private final byte[] BINARY_DATA;
{
try {
BINARY_DATA = "abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890X".getBytes("UTF-8");
} catch (Exception e) {
throw new Error(e);
}
}

private final static String XML_MIME_NO_LINEFEEDS =
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA==";

private final static String XML_MIME =
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1\n"
+"dnd4eXoxMjM0NTY3ODkwWA==";
private final static String XML_MOD_FOR_URL =
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA";
private final static String XML_PEM =
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamts\n"
+"bW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA==";

private final XmlMapper MAPPER = newMapper();

public void testBinaryVariantsCompact() throws Exception
{
_testBinaryVariants(Base64Variants.MIME, XML_MIME, false);

_testBinaryVariants(Base64Variants.MIME_NO_LINEFEEDS, XML_MIME_NO_LINEFEEDS, false);
_testBinaryVariants(Base64Variants.MODIFIED_FOR_URL, XML_MOD_FOR_URL, false);
_testBinaryVariants(Base64Variants.PEM, XML_PEM, false);

// default pre-2.12 was "MIME", despite Jackson/json default of "MIME_NO_LINEFEEDS,
// so kept the same for 2.12 by changing XMLMapper defaults
_testBinaryVariants(null, XML_MIME, false);
}

public void testBinaryVariantsPretty() throws Exception
{
_testBinaryVariants(Base64Variants.MIME, XML_MIME, true);

_testBinaryVariants(Base64Variants.MIME_NO_LINEFEEDS, XML_MIME_NO_LINEFEEDS, true);
_testBinaryVariants(Base64Variants.MODIFIED_FOR_URL, XML_MOD_FOR_URL, true);
_testBinaryVariants(Base64Variants.PEM, XML_PEM, true);

// default pre-2.12 was "MIME", despite Jackson/json default of "MIME_NO_LINEFEEDS,
// so kept the same for 2.12 by changing XMLMapper defaults
_testBinaryVariants(null, XML_MIME, true);
}

private void _testBinaryVariants(Base64Variant b64v, String expEncoded,
boolean indent) throws Exception
{
ObjectWriter w = MAPPER.writer();
if (indent) {
w = w.withDefaultPrettyPrinter();
}
ObjectReader r = MAPPER.readerFor(BinaryValue.class);
if (b64v != null) {
w = w.with(b64v);
r = r.with(b64v);
}
final String EXP = indent ?
"<BinaryValue>\n <value>"+expEncoded+"</value>\n</BinaryValue>" :
"<BinaryValue><value>"+expEncoded+"</value></BinaryValue>";
final String xml = w.writeValueAsString(new BinaryValue(BINARY_DATA)).trim();

//System.err.println("EXP:\n"+EXP+"\nACT:\n"+xml+"\n");

assertEquals(EXP, xml);

// and read back just for shirts & goggles
BinaryValue result = r.readValue(EXP);
assertArrayEquals(BINARY_DATA, result.value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/
public class TestBinaryStreamToXMLSerialization extends XmlTestBase
{
private final XmlMapper MAPPER = new XmlMapper();
private final XmlMapper MAPPER = newMapper();

public void testWith0Bytes() throws Exception
{
Expand Down

0 comments on commit ab17f00

Please sign in to comment.