Skip to content

Commit a966961

Browse files
committed
Fix #179
1 parent 1e8e4b3 commit a966961

File tree

8 files changed

+85
-19
lines changed

8 files changed

+85
-19
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.io.OutputStream;
55
import java.math.BigDecimal;
66
import java.math.BigInteger;
7-
import java.nio.ByteBuffer;
87

98
import org.apache.avro.io.BinaryEncoder;
109

@@ -234,10 +233,10 @@ public boolean canUseSchema(FormatSchema schema) {
234233
return (schema instanceof AvroSchema);
235234
}
236235

237-
// 10-Sep-2019, Tatu: Should implement wrt [dataformats-binary#179], but...
238-
// can't. Not yet, will just break things. Wait until 2.11
239-
// @Override
240-
// public boolean canWriteBinaryNatively() { return true; }
236+
// 26-Nov-2019, tatu: [dataformats-binary#179] needed this; could
237+
// only add in 2.11
238+
@Override // since 2.11
239+
public boolean canWriteBinaryNatively() { return true; }
241240

242241
/*
243242
/**********************************************************
@@ -511,7 +510,7 @@ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int l
511510
writeNull();
512511
return;
513512
}
514-
_avroContext.writeValue(ByteBuffer.wrap(data, offset, len));
513+
_avroContext.writeBinary(data, offset, len);
515514
}
516515

517516
/*

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/AvroSchemaHelper.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ public static boolean isStringable(AnnotatedClass type) {
8888
}
8989

9090
protected static String getNamespace(JavaType type) {
91-
Class<?> cls = type.getRawClass();
91+
return getNamespace(type.getRawClass());
92+
}
93+
94+
protected static String getNamespace(Class<?> cls) {
9295
// 16-Feb-2017, tatu: Fixed as suggested by `baharclerode@github`;
9396
// NOTE: was reverted in 2.8.8, but is enabled for Jackson 2.9.
9497
Class<?> enclosing = cls.getEnclosingClass();
@@ -100,7 +103,11 @@ protected static String getNamespace(JavaType type) {
100103
}
101104

102105
protected static String getName(JavaType type) {
103-
String name = type.getRawClass().getSimpleName();
106+
return getName(type.getRawClass());
107+
}
108+
109+
protected static String getName(Class<?> cls) {
110+
String name = cls.getSimpleName();
104111
// Alas, some characters not accepted...
105112
while (name.indexOf("[]") >= 0) {
106113
name = name.replace("[]", "Array");
@@ -142,6 +149,10 @@ public static Schema simpleSchema(JsonFormatTypes type, JavaType hint)
142149
}
143150
return Schema.create(Schema.Type.DOUBLE);
144151
case STRING:
152+
// 26-Nov-2019, tatu: [dataformats-binary#179] UUIDs are special
153+
if ((hint != null) && hint.hasRawClass(java.util.UUID.class)) {
154+
return createUUIDSchema();
155+
}
145156
return Schema.create(Schema.Type.STRING);
146157
case ARRAY:
147158
case OBJECT:
@@ -249,6 +260,17 @@ public static Schema createEnumSchema(BeanDescription bean, List<String> values)
249260
), bean);
250261
}
251262

263+
/**
264+
* Helper method to enclose details of expressing best Avro Schema for
265+
* {@link java.util.UUID}: 16-byte fixed-length binary (alternative would
266+
* be basic variable length "bytes").
267+
*
268+
* @since 2.11
269+
*/
270+
public static Schema createUUIDSchema() {
271+
return Schema.createFixed("UUID", "", "java.util", 16);
272+
}
273+
252274
/**
253275
* Looks for {@link AvroAlias @AvroAlias} on {@code bean} and adds it to {@code schema} if it exists
254276
* @param schema Schema to which the alias should be added

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/schema/StringVisitor.java

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public Schema builtAvroSchema() {
4444
if (_type.hasRawClass(char.class) || _type.hasRawClass(Character.class)) {
4545
return AvroSchemaHelper.numericAvroSchema(NumberType.INT, TypeFactory.defaultInstance().constructType(Character.class));
4646
}
47+
// [dataformats-binary#179]: need special help with UUIDs, to coerce into Binary
48+
// (could actually be
49+
if (_type.hasRawClass(java.util.UUID.class)) {
50+
return AvroSchemaHelper.createUUIDSchema();
51+
}
4752
BeanDescription bean = _provider.getConfig().introspectClassAnnotations(_type);
4853
if (_enums != null) {
4954
Schema s = AvroSchemaHelper.createEnumSchema(bean, new ArrayList<>(_enums));

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.math.BigDecimal;
5+
import java.util.Arrays;
56
import java.util.List;
67
import java.util.Map;
78

@@ -120,6 +121,14 @@ public boolean writeFieldName(String name) throws IOException {
120121

121122
public abstract void writeValue(Object value) throws IOException;
122123

124+
public void writeBinary(byte[] data, int offset, int len) throws IOException {
125+
// 26-Nov-2019, tatu: Let's defer coercion, just need to remove fluff
126+
if ((offset != 0) || (len != data.length)) {
127+
data = Arrays.copyOfRange(data, offset, offset+len);
128+
}
129+
writeValue(data);
130+
}
131+
123132
/**
124133
* @since 2.5
125134
*/

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/NonBSGenericDatumWriter.java

+22
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44
import java.math.BigDecimal;
55
import java.math.BigInteger;
6+
import java.nio.ByteBuffer;
67
import java.util.ArrayList;
78

89
import org.apache.avro.Schema;
@@ -91,6 +92,27 @@ protected void write(Schema schema, Object datum, Encoder out) throws IOExceptio
9192
}
9293
}
9394
break;
95+
case BYTES:
96+
if (datum instanceof byte[]) {
97+
super.writeWithoutConversion(schema, ByteBuffer.wrap((byte[]) datum), out);
98+
return;
99+
}
100+
break;
101+
case FIXED:
102+
// One more mismatch to fix
103+
/*
104+
if (datum instanceof ByteBuffer) {
105+
byte[] buf = ((ByteBuffer) datum).array();
106+
super.writeWithoutConversion(schema, new GenericData.Fixed(schema, buf), out);
107+
return;
108+
}
109+
*/
110+
if (datum instanceof byte[]) {
111+
super.writeWithoutConversion(schema, new GenericData.Fixed(schema, (byte[]) datum), out);
112+
return;
113+
}
114+
break;
115+
94116
default:
95117
}
96118
// EncodedDatum are already in an avro-encoded format and can be written out directly to the underlying encoder

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/ObjectWriteContext.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,25 @@ public final boolean writeFieldName(String name)
7878
public void writeValue(Object value) throws JsonMappingException {
7979
_verifyValueWrite();
8080
if (_nextField != null) {
81+
// 26-Nov-2019, tatu: Should not be needed any more, handled at a later
82+
// point in `NonBSGenericDatumWriter`
83+
/*
8184
Schema schema = _nextField.schema();
82-
if (schema.getType() == Schema.Type.FIXED && value instanceof ByteBuffer) {
83-
// 13-Nov-2014 josh: AvroGenerator wraps all binary values in ByteBuffers,
84-
// but avro wants FIXED, so rewrap the array, copying if necessary
85-
ByteBuffer bb = (ByteBuffer) value;
86-
byte[] bytes = bb.array();
87-
if (bb.arrayOffset() != 0 || bb.remaining() != bytes.length) {
88-
bytes = Arrays.copyOfRange(bytes, bb.arrayOffset(), bb.remaining());
85+
if (schema.getType() == Schema.Type.FIXED) {
86+
if (value instanceof ByteBuffer) {
87+
// 13-Nov-2014 josh: AvroGenerator wraps all binary values in ByteBuffers,
88+
// but avro wants FIXED, so rewrap the array, copying if necessary
89+
ByteBuffer bb = (ByteBuffer) value;
90+
byte[] bytes = bb.array();
91+
if (bb.arrayOffset() != 0 || bb.remaining() != bytes.length) {
92+
bytes = Arrays.copyOfRange(bytes, bb.arrayOffset(), bb.remaining());
93+
}
94+
value = new GenericData.Fixed(schema, bytes);
95+
} else if (value instanceof byte[]) {
96+
value = new GenericData.Fixed(schema, (byte[]) value);
8997
}
90-
value = new GenericData.Fixed(schema, bytes);
9198
}
99+
*/
92100
_record.put(_nextField.pos(), value);
93101
}
94102
}

avro/src/test/java/com/fasterxml/jackson/dataformat/avro/UUIDTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ protected UUIDWrapper() { }
2020
public void testUUIDRoundtrip() throws Exception
2121
{
2222
final AvroSchema schema = MAPPER.schemaFor(UUIDWrapper.class);
23-
//System.err.println("DEBUG: schema -> "+schema.getAvroSchema());
2423
UUIDWrapper input = new UUIDWrapper(UUID.nameUUIDFromBytes("BOGUS".getBytes("UTF-8")));
2524
byte[] avro = MAPPER.writer(schema).writeValueAsBytes(input);
2625

release-notes/VERSION-2.x

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ Project: jackson-datatypes-binaryModules:
1010

1111
2.11.0 (not yet released)
1212

13+
#179: (avro) Add `AvroGenerator.canWriteBinaryNatively()` to support binary writes,
14+
fix `java.util.UUID` representation
1315
- `AvroGenerator` overrides `getOutputContext()` properly
1416

1517
2.10.1 (09-Nov-2019)
1618

17-
#185: Internal parsing of tagged arrays can lead to stack overflow
19+
#185: (cbor) Internal parsing of tagged arrays can lead to stack overflow
1820
(reported by Paul A)
19-
#188: Unexpected `MismatchedInputException` for `byte[]` value bound to `String`
21+
#188: (cbor) Unexpected `MismatchedInputException` for `byte[]` value bound to `String`
2022
in collection/array (actual fix in `jackson-databind`)
2123
(reported by Yanming Z)
2224

0 commit comments

Comments
 (0)