Skip to content

Commit c570549

Browse files
baharclerodecowtowncoder
authored andcommitted
[Avro] Remove dependencies upon Jackson 1.X and Avro's JacksonUtils
1 parent c59429f commit c570549

File tree

6 files changed

+90
-62
lines changed

6 files changed

+90
-62
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroFieldDefaulters.java

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

33
import java.util.*;
44

5-
import org.codehaus.jackson.JsonNode;
5+
import com.fasterxml.jackson.databind.JsonNode;
66

77
/**
88
* Factory class for various default providers
@@ -19,7 +19,7 @@ public static AvroFieldReader createDefaulter(String name,
1919
case VALUE_NULL:
2020
return new ScalarDefaults.NullDefaults(name);
2121
case VALUE_NUMBER_FLOAT:
22-
switch (defaultAsNode.getNumberType()) {
22+
switch (defaultAsNode.numberType()) {
2323
case FLOAT:
2424
return new ScalarDefaults.FloatDefaults(name, (float) defaultAsNode.asDouble());
2525
case DOUBLE:
@@ -28,7 +28,7 @@ public static AvroFieldReader createDefaulter(String name,
2828
return new ScalarDefaults.DoubleDefaults(name, defaultAsNode.asDouble());
2929
}
3030
case VALUE_NUMBER_INT:
31-
switch (defaultAsNode.getNumberType()) {
31+
switch (defaultAsNode.numberType()) {
3232
case INT:
3333
return new ScalarDefaults.FloatDefaults(name, defaultAsNode.asInt());
3434
case BIG_INTEGER: // TODO: maybe support separately?
@@ -40,7 +40,7 @@ public static AvroFieldReader createDefaulter(String name,
4040
return new ScalarDefaults.StringDefaults(name, defaultAsNode.asText());
4141
case START_OBJECT:
4242
{
43-
Iterator<Map.Entry<String,JsonNode>> it = defaultAsNode.getFields();
43+
Iterator<Map.Entry<String,JsonNode>> it = defaultAsNode.fields();
4444
List<AvroFieldReader> readers = new ArrayList<AvroFieldReader>();
4545
while (it.hasNext()) {
4646
Map.Entry<String,JsonNode> entry = it.next();
@@ -64,7 +64,7 @@ public static AvroFieldReader createDefaulter(String name,
6464

6565
// 23-Jul-2019, tatu: With Avro 1.9, likely changed to use "raw" JDK containers?
6666
// Code would look more like this:
67-
/*
67+
/*
6868
public static AvroFieldReader createDefaulter(String name,
6969
Object defaultValue)
7070
{

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroReaderFactory.java

+20-18
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import java.util.*;
44

55
import org.apache.avro.Schema;
6-
import org.apache.avro.util.internal.JacksonUtils;
76

7+
import com.fasterxml.jackson.databind.ObjectMapper;
88
import com.fasterxml.jackson.dataformat.avro.deser.ScalarDecoder.*;
99
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper;
1010

@@ -22,6 +22,7 @@ public abstract class AvroReaderFactory
2222
protected final static ScalarDecoder READER_LONG = new LongReader();
2323
protected final static ScalarDecoder READER_NULL = new NullReader();
2424
protected final static ScalarDecoder READER_STRING = new StringReader();
25+
private final static ObjectMapper DEFAULT_MAPPER = new ObjectMapper();
2526

2627
/**
2728
* To resolve cyclic types, need to keep track of resolved named
@@ -56,24 +57,24 @@ public ScalarDecoder createScalarValueDecoder(Schema type)
5657
switch (type.getType()) {
5758
case BOOLEAN:
5859
return READER_BOOLEAN;
59-
case BYTES:
60+
case BYTES:
6061
return READER_BYTES;
61-
case DOUBLE:
62+
case DOUBLE:
6263
return READER_DOUBLE;
63-
case ENUM:
64+
case ENUM:
6465
return new EnumDecoder(AvroSchemaHelper.getFullName(type), type.getEnumSymbols());
65-
case FIXED:
66+
case FIXED:
6667
return new FixedDecoder(type.getFixedSize(), AvroSchemaHelper.getFullName(type));
67-
case FLOAT:
68+
case FLOAT:
6869
return READER_FLOAT;
6970
case INT:
7071
if (AvroSchemaHelper.getTypeId(type) != null) {
7172
return new IntReader(AvroSchemaHelper.getTypeId(type));
7273
}
7374
return READER_INT;
74-
case LONG:
75+
case LONG:
7576
return READER_LONG;
76-
case NULL:
77+
case NULL:
7778
return READER_NULL;
7879
case STRING:
7980
if (AvroSchemaHelper.getTypeId(type) != null) {
@@ -127,7 +128,7 @@ public AvroStructureReader createReader(Schema schema)
127128
switch (schema.getType()) {
128129
case ARRAY:
129130
return createArrayReader(schema);
130-
case MAP:
131+
case MAP:
131132
return createMapReader(schema);
132133
case RECORD:
133134
return createRecordReader(schema);
@@ -252,7 +253,7 @@ public AvroStructureReader createReader(Schema writerSchema, Schema readerSchema
252253
switch (writerSchema.getType()) {
253254
case ARRAY:
254255
return createArrayReader(writerSchema, readerSchema);
255-
case MAP:
256+
case MAP:
256257
return createMapReader(writerSchema, readerSchema);
257258
case RECORD:
258259
return createRecordReader(writerSchema, readerSchema);
@@ -303,7 +304,7 @@ protected AvroStructureReader createRecordReader(Schema writerSchema, Schema rea
303304
final List<Schema.Field> writerFields = writerSchema.getFields();
304305

305306
// but first: find fields that only exist in reader-side and need defaults,
306-
// and remove those from
307+
// and remove those from
307308
Map<String,Schema.Field> readerFields = new HashMap<String,Schema.Field>();
308309
List<Schema.Field> defaultFields = new ArrayList<Schema.Field>();
309310
{
@@ -320,7 +321,7 @@ protected AvroStructureReader createRecordReader(Schema writerSchema, Schema rea
320321
}
321322
}
322323
}
323-
324+
324325
// note: despite changes, we will always have known number of field entities,
325326
// ones from writer schema -- some may skip, but there's entry there
326327
AvroFieldReader[] fieldReaders = new AvroFieldReader[writerFields.size()
@@ -339,12 +340,13 @@ protected AvroStructureReader createRecordReader(Schema writerSchema, Schema rea
339340
: createFieldReader(readerField.name(),
340341
writerField.schema(), readerField.schema());
341342
}
342-
343+
343344
// Any defaults to consider?
344345
if (!defaultFields.isEmpty()) {
345346
for (Schema.Field defaultField : defaultFields) {
346-
AvroFieldReader fr =
347-
AvroFieldDefaulters.createDefaulter(defaultField.name(), JacksonUtils.toJsonNode(defaultField.defaultVal()));
347+
AvroFieldReader fr = AvroFieldDefaulters.createDefaulter(defaultField.name(),
348+
AvroSchemaHelper.parseDefaultValue(defaultField.defaultVal())
349+
);
348350
if (fr == null) {
349351
throw new IllegalArgumentException("Unsupported default type: "+defaultField.schema().getType());
350352
}
@@ -359,9 +361,9 @@ protected AvroStructureReader createUnionReader(Schema writerSchema, Schema read
359361
final List<Schema> types = writerSchema.getTypes();
360362
AvroStructureReader[] typeReaders = new AvroStructureReader[types.size()];
361363
int i = 0;
362-
364+
363365
// !!! TODO: actual resolution !!!
364-
366+
365367
for (Schema type : types) {
366368
typeReaders[i++] = createReader(type);
367369
}
@@ -414,7 +416,7 @@ private Schema _verifyMatchingStructure(Schema readerSchema, Schema writerSchema
414416
// in case there are multiple alternatives of same structured type.
415417
// But since that is quite non-trivial let's wait for a good example of actual
416418
// usage before tackling that.
417-
419+
418420
if (actualType == Schema.Type.UNION) {
419421
for (Schema sch : readerSchema.getTypes()) {
420422
if (sch.getType() == expectedType) {

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

+48-4
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@
1414
import org.apache.avro.specific.SpecificData;
1515

1616
import com.fasterxml.jackson.core.JsonParser;
17-
import com.fasterxml.jackson.databind.BeanDescription;
18-
import com.fasterxml.jackson.databind.JavaType;
17+
import com.fasterxml.jackson.core.JsonProcessingException;
18+
import com.fasterxml.jackson.databind.*;
1919
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
2020
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
2121
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
2222
import com.fasterxml.jackson.databind.util.ClassUtil;
2323

2424
public abstract class AvroSchemaHelper
2525
{
26+
/**
27+
* Dedicated mapper for handling default values (String &lt;-&gt; JsonNode &lt;-&gt; Object)
28+
*/
29+
private static final ObjectMapper DEFAULT_VALUE_MAPPER = new ObjectMapper();
30+
2631
/**
2732
* Constant used by native Avro Schemas for indicating more specific
2833
* physical class of a value; referenced indirectly to reduce direct
@@ -317,6 +322,10 @@ public static String getTypeId(Schema schema) {
317322
/**
318323
* Returns the full name of a schema; This is similar to {@link Schema#getFullName()}, except that it properly handles namespaces for
319324
* nested classes. (<code>package.name.ClassName$NestedClassName</code> instead of <code>package.name.ClassName$.NestedClassName</code>)
325+
* <p>
326+
* <b>WARNING!</b> This method has to probe for nested classes in order to resolve whether or not a schema references a top-level
327+
* class or a nested class and return the corresponding name for each.
328+
* </p>
320329
*/
321330
public static String getFullName(Schema schema) {
322331
switch (schema.getType()) {
@@ -332,9 +341,44 @@ public static String getFullName(Schema schema) {
332341
return namespace + name;
333342
}
334343
StringBuilder sb = new StringBuilder(1 + namespace.length() + name.length());
335-
return sb.append(namespace).append('.').append(name).toString();
336-
default:
344+
// Check if this is a nested class
345+
String nestedClassName = sb.append(namespace).append('$').append(name).toString();
346+
try {
347+
Class.forName(nestedClassName);
348+
return nestedClassName;
349+
} catch (ClassNotFoundException e) {
350+
// Could not find a nested class, must be a regular class
351+
sb.setLength(0);
352+
return sb.append(namespace).append('.').append(name).toString();
353+
}
354+
default:
337355
return schema.getType().getName();
338356
}
339357
}
358+
359+
public static JsonNode parseDefaultValue(Object defaultValue) {
360+
return DEFAULT_VALUE_MAPPER.convertValue(defaultValue, JsonNode.class);
361+
}
362+
363+
public static Object convertDefaultValueToObject(JsonNode defaultJsonValue) {
364+
return DEFAULT_VALUE_MAPPER.convertValue(defaultJsonValue, Object.class);
365+
}
366+
367+
/**
368+
* Converts a default value represented as a string (such as from a schema specification) into a JsonNode for further handling.
369+
*
370+
* @param defaultValue The default value to parse, in the form of a JSON string
371+
* @return a parsed JSON representation of the default value
372+
* @throws JsonMappingException If {@code defaultValue} is not valid JSON
373+
*/
374+
public static JsonNode parseDefaultValue(String defaultValue) throws JsonMappingException {
375+
if (defaultValue == null) {
376+
return null;
377+
}
378+
try {
379+
return DEFAULT_VALUE_MAPPER.readTree(defaultValue);
380+
} catch (JsonProcessingException e) {
381+
throw new JsonMappingException(null, "Failed to parse default value", e);
382+
}
383+
}
340384
}

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

+7-34
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.fasterxml.jackson.dataformat.avro.schema;
22

3-
import java.io.IOException;
43
import java.util.ArrayList;
54
import java.util.List;
65
import java.util.Map;
@@ -9,9 +8,6 @@
98
import org.apache.avro.Schema.Type;
109
import org.apache.avro.reflect.AvroMeta;
1110
import org.apache.avro.reflect.AvroSchema;
12-
import org.apache.avro.util.internal.JacksonUtils;
13-
import org.codehaus.jackson.JsonNode;
14-
import org.codehaus.jackson.map.ObjectMapper;
1511

1612
import com.fasterxml.jackson.databind.*;
1713
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
@@ -36,9 +32,9 @@ public class RecordVisitor
3632
protected final boolean _overridden;
3733

3834
protected Schema _avroSchema;
39-
35+
4036
protected List<Schema.Field> _fields = new ArrayList<Schema.Field>();
41-
37+
4238
public RecordVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas)
4339
{
4440
super(p);
@@ -75,7 +71,7 @@ public RecordVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas
7571
}
7672
schemas.addSchema(type, _avroSchema);
7773
}
78-
74+
7975
@Override
8076
public Schema builtAvroSchema() {
8177
if (!_overridden) {
@@ -90,7 +86,7 @@ public Schema builtAvroSchema() {
9086
/* JsonObjectFormatVisitor implementation
9187
/**********************************************************
9288
*/
93-
89+
9490
@Override
9591
public void property(BeanProperty writer) throws JsonMappingException
9692
{
@@ -142,7 +138,7 @@ public void optionalProperty(String name, JsonFormatVisitable handler,
142138
/* Internal methods
143139
/**********************************************************************
144140
*/
145-
141+
146142
protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional) throws JsonMappingException
147143
{
148144
Schema writerSchema;
@@ -187,10 +183,10 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional)
187183
writerSchema = AvroSchemaHelper.unionWithNull(writerSchema);
188184
}
189185
}
190-
JsonNode defaultValue = parseJson(prop.getMetadata().getDefaultValue());
186+
JsonNode defaultValue = AvroSchemaHelper.parseDefaultValue(prop.getMetadata().getDefaultValue());
191187
writerSchema = reorderUnionToMatchDefaultType(writerSchema, defaultValue);
192188
Schema.Field field = new Schema.Field(prop.getName(), writerSchema, prop.getMetadata().getDescription(),
193-
JacksonUtils.toObject(defaultValue));
189+
AvroSchemaHelper.convertDefaultValueToObject(defaultValue));
194190

195191
AvroMeta meta = prop.getAnnotation(AvroMeta.class);
196192
if (meta != null) {
@@ -206,29 +202,6 @@ protected Schema.Field schemaFieldForWriter(BeanProperty prop, boolean optional)
206202
return field;
207203
}
208204

209-
/**
210-
* Parses a JSON-encoded string for use as the default value of a field
211-
*
212-
* @param defaultValue
213-
* Default value as a JSON-encoded string
214-
*
215-
* @return Jackson V1 {@link JsonNode} for use as the default value in a {@link Schema.Field}
216-
*
217-
* @throws JsonMappingException
218-
* if {@code defaultValue} is not valid JSON
219-
*/
220-
protected JsonNode parseJson(String defaultValue) throws JsonMappingException {
221-
if (defaultValue == null) {
222-
return null;
223-
}
224-
ObjectMapper mapper = new ObjectMapper();
225-
try {
226-
return mapper.readTree(defaultValue);
227-
} catch (IOException e) {
228-
throw JsonMappingException.from(getProvider(), "Unable to parse default value as JSON: " + defaultValue, e);
229-
}
230-
}
231-
232205
/**
233206
* A union schema with a default value must always have the schema branch corresponding to the default value first, or Avro will print a
234207
* warning complaining that the default value is not compatible. If {@code schema} is a {@link Schema.Type#UNION UNION} schema and

avro/src/test/java/com/fasterxml/jackson/dataformat/avro/interop/ApacheAvroInteropUtil.java

+9
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ protected Schema createSchema(Type type, Map<String, Schema> names) {
126126
/* 14-Jun-2018, tatu: This is 99.9% certainly wrong; IDE gives warnings and
127127
* it just... doesn't fly. Either keys are NOT Strings or...
128128
*/
129+
/*
130+
* 26-Jun-2019, baharclerode: The keys are NOT always strings. It's a horrible hack I used to enable support for generics
131+
* in the apache implementation because otherwise it flat out doesn't support generic types correctly except for the handful
132+
* that they hand-coded support for, resulting in oddities like Set<T> not working, or List<T> working but LinkedList<T> not
133+
* working. This regression suite is a lot simpler when we don't have to disable half the tests because the reference
134+
* implementation doesn't support them.
135+
*
136+
* Note the "((Map) names).put(...)" above this else/if case.
137+
*/
129138
if (names.containsKey(type)) {
130139
return names.get(type);
131140
}

avro/src/test/java/com/fasterxml/jackson/dataformat/avro/interop/annotations/AvroDefaultTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ static class RecordWithDefaults {
1515
@AvroDefault("1234")
1616
public Integer intField;
1717
@AvroDefault("true")
18-
public Integer booleanField;
18+
public Boolean booleanField;
1919
}
2020

2121
@Test

0 commit comments

Comments
 (0)