Skip to content

Commit 263f6c0

Browse files
committed
Further fixes to support root-level scalar value writes, reads
1 parent c8cb146 commit 263f6c0

File tree

11 files changed

+222
-57
lines changed

11 files changed

+222
-57
lines changed

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@ public void setSchema(AvroSchema schema)
155155
}
156156
_rootSchema = schema;
157157
// start with temporary root...
158-
_avroContext = _rootContext = AvroWriteContext.createRootContext(this, schema.getAvroSchema());
158+
if (_encoder == null) {
159+
_encoder = AvroSchema.encoder(_output, isEnabled(Feature.AVRO_BUFFERING));
160+
}
161+
_avroContext = _rootContext = AvroWriteContext.createRootContext(this,
162+
schema.getAvroSchema(), _encoder);
159163
}
160164

161165
/*
@@ -506,7 +510,7 @@ public void writeBoolean(boolean state) throws IOException {
506510

507511
@Override
508512
public void writeNull() throws IOException {
509-
_avroContext.writeValue(null);
513+
_avroContext.writeNull();
510514
}
511515

512516
@Override
@@ -592,13 +596,9 @@ protected void _complete() throws IOException
592596
// to forced closure resulting from another exception; so, we typically
593597
// do not want to hide the original problem...
594598
// First one sanity check, for a (relatively?) common case
595-
if (_rootContext == null) {
596-
return;
597-
}
598-
if (_encoder == null) {
599-
_encoder = AvroSchema.encoder(_output, isEnabled(Feature.AVRO_BUFFERING));
599+
if (_rootContext != null) {
600+
_rootContext.complete();
601+
_encoder.flush();
600602
}
601-
_rootContext.complete(_encoder);
602-
_encoder.flush();
603603
}
604604
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ public void writeValue(Object value) {
4444
public void writeString(String value) {
4545
_array.add(value);
4646
}
47-
47+
48+
@Override
49+
public void writeNull() throws JsonMappingException {
50+
_array.add(null);
51+
}
52+
4853
@Override
4954
public void appendDesc(StringBuilder sb)
5055
{

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

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,15 @@ protected AvroWriteContext(int type, AvroWriteContext parent,
3838

3939
// // // Factory methods
4040

41+
public static AvroWriteContext createRootContext(AvroGenerator generator, Schema schema,
42+
BinaryEncoder encoder) {
43+
return new RootContext(generator, schema, encoder);
44+
}
45+
46+
// To be removed ASAP (in 2.9)
47+
@Deprecated
4148
public static AvroWriteContext createRootContext(AvroGenerator generator, Schema schema) {
42-
return new RootContext(generator, schema);
49+
throw new IllegalStateException();
4350
}
4451

4552
/**
@@ -65,32 +72,40 @@ public static AvroWriteContext createNullContext() {
6572
* @return True if writing succeeded (for {@link ObjectWriteContext},
6673
* iff column was recognized)
6774
*/
68-
public boolean writeFieldName(String name) throws JsonMappingException {
75+
public boolean writeFieldName(String name) throws IOException {
6976
return false;
7077
}
7178

72-
public abstract void writeValue(Object value) throws JsonMappingException;
79+
public abstract void writeValue(Object value) throws IOException;
7380

7481
/**
7582
* @since 2.5
7683
*/
77-
public abstract void writeString(String value) throws JsonMappingException;
78-
84+
public abstract void writeString(String value) throws IOException;
85+
86+
/**
87+
* @since 2.8
88+
*/
89+
public abstract void writeNull() throws IOException;
90+
7991
/**
8092
* Accessor called to link data being built with resulting object.
8193
*/
8294
public abstract Object rawValue();
83-
84-
public void complete(BinaryEncoder encoder) throws IOException {
95+
96+
public void complete() throws IOException {
8597
throw new IllegalStateException("Can not be called on "+getClass().getName());
8698
}
87-
99+
100+
@Deprecated // remove from 2.9
101+
public void complete(BinaryEncoder encoder) throws IOException { complete(); }
102+
88103
public boolean canClose() { return true; }
89104

90105
protected abstract void appendDesc(StringBuilder sb);
91-
106+
92107
// // // Overridden standard methods
93-
108+
94109
/**
95110
* Overridden to provide developer writeable "JsonPath" representation
96111
* of the context.
@@ -216,10 +231,15 @@ public void writeValue(Object value) {
216231
}
217232

218233
@Override
219-
public void writeString(String value) throws JsonMappingException {
234+
public void writeString(String value) {
220235
_reportError();
221236
}
222-
237+
238+
@Override
239+
public void writeNull() {
240+
_reportError();
241+
}
242+
223243
@Override
224244
public void appendDesc(StringBuilder sb) {
225245
sb.append("?");

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ public void writeString(String value) {
6262
_verifyValueWrite();
6363
_data.put(_currentName, value);
6464
}
65-
65+
66+
@Override
67+
public void writeNull() {
68+
_verifyValueWrite();
69+
_data.put(_currentName, null);
70+
}
71+
6672
protected final void _verifyValueWrite() {
6773
if (!_expectValue) {
6874
throw new IllegalStateException("Expecting FIELD_NAME, not value");

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ public void writeValue(Object value) { }
3030

3131
@Override
3232
public void writeString(String value) { }
33-
33+
34+
@Override
35+
public void writeNull() { }
36+
3437
@Override
3538
public void appendDesc(StringBuilder sb) {
3639
sb.append("(...)");

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,15 @@ public void writeString(String value) {
9898
_record.put(_nextField.pos(), value);
9999
}
100100
}
101-
101+
102+
@Override
103+
public void writeNull() {
104+
_verifyValueWrite();
105+
if (_nextField != null) {
106+
_record.put(_nextField.pos(), null);
107+
}
108+
}
109+
102110
protected final void _verifyValueWrite() {
103111
if (!_expectValue) {
104112
throw new IllegalStateException("Expecting FIELD_NAME, not value");

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

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
class RootContext
1313
extends AvroWriteContext
1414
{
15+
protected final BinaryEncoder _encoder;
16+
1517
/**
1618
* We need to keep reference to the root value here; either
1719
* <code>GenericContainer</code> or <code>Map</code> (yes,
@@ -24,8 +26,9 @@ class RootContext
2426
*/
2527
private NonBSGenericDatumWriter<Object> _writer;
2628

27-
public RootContext(AvroGenerator generator, Schema schema) {
29+
public RootContext(AvroGenerator generator, Schema schema, BinaryEncoder encoder) {
2830
super(TYPE_ROOT, null, generator, schema);
31+
_encoder = encoder;
2932
}
3033

3134
@Override
@@ -73,21 +76,33 @@ public final AvroWriteContext createChildObjectContext() throws JsonMappingExcep
7376
}
7477

7578
@Override
76-
public void writeValue(Object value) {
77-
_reportError();
79+
public void writeValue(Object value) throws IOException {
80+
// 19-Jan-2017, tatu: Implemented to allow/support root-level scalars, esp.
81+
// for Avro streams
82+
_writer().write(value, _encoder);
83+
}
84+
85+
@Override
86+
public void writeString(String value) throws IOException {
87+
// 19-Jan-2017, tatu: Implemented to allow/support root-level scalars, esp.
88+
// for Avro streams
89+
_writer().write(value, _encoder);
7890
}
7991

8092
@Override
81-
public void writeString(String value) {
82-
_reportError();
93+
public void writeNull() throws IOException {
94+
// 19-Jan-2017, tatu: ... is this even legal?
95+
_writer().write(null, _encoder);
8396
}
8497

8598
@Override
86-
public void complete(BinaryEncoder encoder) throws IOException {
87-
if (_writer == null) {
88-
_writer = new NonBSGenericDatumWriter<Object>(_schema);
99+
public void complete() throws IOException {
100+
// 19-Jan-2017, tatu: Gets also called for root-level scalar, in which
101+
// case nothing (more) to output.
102+
if (_rootValue != null) {
103+
_writer().write(_rootValue, _encoder);
89104
}
90-
_writer.write(_rootValue, encoder);
105+
_rootValue = null;
91106
}
92107

93108
@Override
@@ -98,4 +113,13 @@ public void appendDesc(StringBuilder sb) {
98113
protected void _reportError() {
99114
throw new IllegalStateException("Can not write values directly in root context, outside of Records/Arrays");
100115
}
101-
}
116+
117+
private final NonBSGenericDatumWriter<Object> _writer() {
118+
NonBSGenericDatumWriter<Object> w = _writer;
119+
if (w == null){
120+
w = new NonBSGenericDatumWriter<Object>(_schema);
121+
_writer = w;
122+
}
123+
return w;
124+
}
125+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ protected void verifyException(Throwable e, String... matches)
284284
fail("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\"");
285285
}
286286

287+
protected static String quote(String str) {
288+
return "\""+str+"\"";
289+
}
290+
287291
protected static String aposToQuotes(String json) {
288292
return json.replace("'", "\"");
289293
}

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

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.fasterxml.jackson.dataformat.avro;
22

3-
import static org.junit.Assert.assertArrayEquals;
4-
53
import java.io.ByteArrayOutputStream;
64
import java.util.*;
75

@@ -10,7 +8,6 @@
108
import com.fasterxml.jackson.core.JsonToken;
119
import com.fasterxml.jackson.databind.MappingIterator;
1210
import com.fasterxml.jackson.databind.SequenceWriter;
13-
import com.fasterxml.jackson.dataformat.avro.AvroTestBase.Employee;
1411

1512
public class MapTest extends AvroTestBase
1613
{
@@ -45,8 +42,7 @@ public void setStuff(Map<String,String> arg) {
4542

4643
public void testRecordWithMap() throws Exception
4744
{
48-
AvroMapper mapper = getMapper();
49-
AvroSchema schema = mapper.schemaFrom(MAP_SCHEMA_JSON);
45+
AvroSchema schema = MAPPER.schemaFrom(MAP_SCHEMA_JSON);
5046
Container input = new Container();
5147
input.stuff.put("foo", "bar");
5248
input.stuff.put("a", "b");
@@ -55,16 +51,16 @@ public void testRecordWithMap() throws Exception
5551
* get masked due to auto-close. Hence this trickery.
5652
*/
5753
ByteArrayOutputStream out = new ByteArrayOutputStream();
58-
JsonGenerator gen = mapper.getFactory().createGenerator(out);
59-
mapper.writer(schema).writeValue(gen, input);
54+
JsonGenerator gen = MAPPER.getFactory().createGenerator(out);
55+
MAPPER.writer(schema).writeValue(gen, input);
6056
gen.close();
6157
byte[] bytes = out.toByteArray();
6258
assertNotNull(bytes);
6359

6460
assertEquals(16, bytes.length); // measured to be current exp size
6561

6662
// and then back. Start with streaming
67-
JsonParser p = mapper.getFactory().createParser(bytes);
63+
JsonParser p = MAPPER.getFactory().createParser(bytes);
6864
p.setSchema(schema);
6965
assertToken(JsonToken.START_OBJECT, p.nextToken());
7066
assertToken(JsonToken.FIELD_NAME, p.nextToken());
@@ -93,7 +89,7 @@ public void testRecordWithMap() throws Exception
9389
p.close();
9490

9591
// and then databind
96-
Container output = mapper.readerFor(Container.class).with(schema)
92+
Container output = MAPPER.readerFor(Container.class).with(schema)
9793
.readValue(bytes);
9894
assertNotNull(output);
9995
assertNotNull(output.stuff);
@@ -105,8 +101,8 @@ public void testRecordWithMap() throws Exception
105101
input = new Container();
106102

107103
out = new ByteArrayOutputStream();
108-
gen = mapper.getFactory().createGenerator(out);
109-
mapper.writer(schema).writeValue(gen, input);
104+
gen = MAPPER.getFactory().createGenerator(out);
105+
MAPPER.writer(schema).writeValue(gen, input);
110106
gen.close();
111107
bytes = out.toByteArray();
112108
assertNotNull(bytes);
@@ -116,17 +112,16 @@ public void testRecordWithMap() throws Exception
116112

117113
public void testMapOrNull() throws Exception
118114
{
119-
AvroMapper mapper = getMapper();
120-
AvroSchema schema = mapper.schemaFrom(MAP_OR_NULL_SCHEMA_JSON);
115+
AvroSchema schema = MAPPER.schemaFrom(MAP_OR_NULL_SCHEMA_JSON);
121116
Container input = new Container();
122117
input.stuff = null;
123118

124-
byte[] bytes = mapper.writer(schema).writeValueAsBytes(input);
119+
byte[] bytes = MAPPER.writer(schema).writeValueAsBytes(input);
125120
assertNotNull(bytes);
126121
assertEquals(1, bytes.length); // measured to be current exp size
127122

128123
// and then back
129-
Container output = mapper.readerFor(Container.class).with(schema)
124+
Container output = MAPPER.readerFor(Container.class).with(schema)
130125
.readValue(bytes);
131126
assertNotNull(output);
132127
assertNull(output.stuff);
@@ -135,12 +130,12 @@ public void testMapOrNull() throws Exception
135130
input = new Container();
136131
input.stuff.put("x", "y");
137132

138-
bytes = mapper.writer(schema).writeValueAsBytes(input);
133+
bytes = MAPPER.writer(schema).writeValueAsBytes(input);
139134
assertNotNull(bytes);
140135
assertEquals(7, bytes.length); // measured to be current exp size
141136

142137
// and then back
143-
output = mapper.readerFor(Container.class).with(schema)
138+
output = MAPPER.readerFor(Container.class).with(schema)
144139
.readValue(bytes);
145140
assertNotNull(output);
146141
assertNotNull(output.stuff);
@@ -165,7 +160,6 @@ public void testRootStringMap() throws Exception
165160
assertEquals("1", result.get("a"));
166161
assertEquals("2", result.get("b"));
167162
}
168-
169163
public void testRootMapSequence() throws Exception
170164
{
171165
ByteArrayOutputStream b = new ByteArrayOutputStream(1000);

0 commit comments

Comments
 (0)