Skip to content

Commit

Permalink
Merge pull request #57 from baharclerode/bah.StringableAnnotationMaster
Browse files Browse the repository at this point in the history
[Avro] Add support for @Stringable annotation
  • Loading branch information
cowtowncoder authored Mar 10, 2017
2 parents d9d6e60 + ee71b6a commit ca58cfc
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 67 deletions.
16 changes: 7 additions & 9 deletions avro/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ abstractions.
</properties>

<dependencies>
<!-- Hmmh. Need databind for Avro Schema generation... -->
<!-- Hmmh. Need annotations for introspection... -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- and databind for Avro Schema generation... -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand All @@ -32,14 +37,7 @@ abstractions.
<version>1.8.1</version>
</dependency>

<!-- and for testing we need annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>test</scope>
</dependency>

<!-- plus logback -->
<!-- and for testing we need logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@

import org.apache.avro.reflect.*;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
/**
* Adds support for the following annotations from the Apache Avro implementation:
* <ul>
Expand All @@ -20,6 +24,8 @@
* define default value for generated Schemas
* </li>
* <li>{@link Nullable @Nullable} - Alias for <code>JsonProperty(required = false)</code></li>
* <li>{@link Stringable @Stringable} - Alias for <code>JsonCreator</code> on the constructor and <code>JsonValue</code> on
* the {@link #toString()} method. </li>
* </ul>
*
* @since 2.9
Expand Down Expand Up @@ -76,4 +82,26 @@ public Boolean hasRequiredMarker(AnnotatedMember m) {
}
return null;
}

@Override
public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
AnnotatedConstructor constructor = a instanceof AnnotatedConstructor ? (AnnotatedConstructor) a : null;
AnnotatedClass parentClass =
a instanceof AnnotatedConstructor && ((AnnotatedConstructor) a).getTypeContext() instanceof AnnotatedClass
? (AnnotatedClass) ((AnnotatedConstructor) a).getTypeContext()
: null;
if (constructor != null && parentClass != null && parentClass.hasAnnotation(Stringable.class)
&& constructor.getParameterCount() == 1 && String.class.equals(constructor.getRawParameterType(0))) {
return JsonCreator.Mode.DELEGATING;
}
return null;
}

@Override
public Object findSerializer(Annotated a) {
if (a instanceof AnnotatedClass && a.hasAnnotation(Stringable.class)) {
return ToStringSerializer.class;
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.dataformat.avro;

import java.io.File;
import java.io.IOException;

import org.apache.avro.Schema;
Expand All @@ -8,6 +9,7 @@
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

/**
* Module that adds support for handling datatypes specific to the standard
Expand All @@ -31,6 +33,7 @@ public AvroModule()
{
super(PackageVersion.VERSION);
addSerializer(new SchemaSerializer());
addSerializer(File.class, new ToStringSerializer(File.class));
// 08-Mar-2016, tatu: to fix [dataformat-avro#35], need to prune 'schema' property:
setSerializerModifier(new AvroSerializerModifier());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.fasterxml.jackson.dataformat.avro;

import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.math.BigDecimal;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserBase;
Expand Down Expand Up @@ -255,6 +258,17 @@ public JsonLocation getCurrentLocation()
@Override
public abstract JsonToken nextToken() throws IOException;

@Override
protected void convertNumberToBigDecimal() throws IOException {
// ParserBase uses _textValue instead of _numberDouble for some reason when NR_DOUBLE is set, but _textValue is not set by setNumber()
// Catch and use _numberDouble instead
if ((_numTypesValid & NR_DOUBLE) != 0 && _textValue == null) {
_numberBigDecimal = BigDecimal.valueOf(_numberDouble);
return;
}
super.convertNumberToBigDecimal();
}

/*
/**********************************************************
/* String value handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.*;

import org.apache.avro.Schema;
import org.apache.avro.util.internal.JacksonUtils;

import com.fasterxml.jackson.dataformat.avro.deser.ScalarDecoder.*;
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper;
Expand Down Expand Up @@ -329,8 +330,8 @@ protected AvroStructureReader createRecordReader(Schema writerSchema, Schema rea
// Any defaults to consider?
if (!defaultFields.isEmpty()) {
for (Schema.Field defaultField : defaultFields) {
AvroFieldReader fr = AvroFieldDefaulters.createDefaulter(defaultField.name(),
defaultField.defaultValue());
AvroFieldReader fr =
AvroFieldDefaulters.createDefaulter(defaultField.name(), JacksonUtils.toJsonNode(defaultField.defaultVal()));
if (fr == null) {
throw new IllegalArgumentException("Unsupported default type: "+defaultField.schema().getType());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,15 @@ public static Schema numericAvroSchema(JsonParser.NumberType type) {
switch (type) {
case INT:
return Schema.create(Schema.Type.INT);
case BIG_INTEGER:
case LONG:
return Schema.create(Schema.Type.LONG);
case FLOAT:
return Schema.create(Schema.Type.FLOAT);
case BIG_DECIMAL:
case DOUBLE:
return Schema.create(Schema.Type.DOUBLE);
case BIG_INTEGER:
case BIG_DECIMAL:
return Schema.create(Schema.Type.STRING);
default:
}
throw new IllegalStateException("Unrecognized number type: "+type);
Expand Down Expand Up @@ -212,6 +213,17 @@ public static Schema parseJsonSchema(String json) {
return parser.parse(json);
}

/**
* Constructs a new enum schema
*
* @param bean Enum type to use for name / description / namespace
* @param values List of enum names
* @return An {@link org.apache.avro.Schema.Type#ENUM ENUM} schema.
*/
public static Schema createEnumSchema(BeanDescription bean, List<String> values) {
return Schema.createEnum(getName(bean.getType()), bean.findClassDescription(), getNamespace(bean.getType()), values);
}

/**
* Returns the Avro type ID for a given type
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import org.apache.avro.Schema;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonNumberFormatVisitor;

public class DoubleVisitor
extends JsonNumberFormatVisitor.Base
implements SchemaBuilder
{
protected final JavaType _hint;
protected JsonParser.NumberType _type;

public DoubleVisitor() { }
public DoubleVisitor(JavaType typeHint) {
_hint = typeHint;
}

@Override
public void numberType(JsonParser.NumberType type) {
Expand All @@ -25,6 +29,6 @@ public Schema builtAvroSchema() {
// would require union most likely
return AvroSchemaHelper.anyNumberSchema();
}
return AvroSchemaHelper.numericAvroSchema(_type);
return AvroSchemaHelper.numericAvroSchema(_type, _hint);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ public class MapVisitor extends JsonMapFormatVisitor.Base
implements SchemaBuilder
{
protected final JavaType _type;

protected final DefinedSchemas _schemas;

protected Schema _valueSchema;


protected JavaType _keyType;

public MapVisitor(SerializerProvider p, JavaType type, DefinedSchemas schemas)
{
super(p);
Expand All @@ -30,7 +32,23 @@ public Schema builtAvroSchema() {
if (_valueSchema == null) {
throw new IllegalStateException("Missing value type for "+_type);
}
return Schema.createMap(_valueSchema);

Schema schema = Schema.createMap(_valueSchema);

// add the key type if there is one
if (_keyType != null && AvroSchemaHelper.isStringable(getProvider()
.getConfig()
.introspectClassAnnotations(_keyType)
.getClassInfo())) {
schema.addProp(AvroSchemaHelper.AVRO_SCHEMA_PROP_KEY_CLASS, AvroSchemaHelper.getTypeId(_keyType));
} else if (_keyType != null && !_keyType.isEnumType()) {
// Avro handles non-stringable keys by converting the map to an array of key/value records
// TODO add support for these in the schema, and custom serializers / deserializers to handle map restructuring
throw new UnsupportedOperationException(
"Key " + _keyType + " is not stringable and non-stringable map keys are not supported yet.");
}

return schema;
}

/*
Expand All @@ -43,12 +61,7 @@ public Schema builtAvroSchema() {
public void keyFormat(JsonFormatVisitable handler, JavaType keyType)
throws JsonMappingException
{
/* We actually don't care here, since Avro only has String-keyed
* Maps like JSON: meaning that anything Jackson can regularly
* serialize must convert to Strings anyway.
* If we do find problem cases, we can start verifying them here,
* but for now assume it all "just works".
*/
_keyType = keyType;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ public void property(BeanProperty writer) throws JsonMappingException
return;
}
_fields.add(schemaFieldForWriter(writer, false));
/*
Schema schema = schemaForWriter(writer);
_fields.add(_field(writer, schema));
*/
}

@Override
Expand Down Expand Up @@ -274,7 +270,7 @@ protected Schema reorderUnionToMatchDefaultType(Schema schema, JsonNode defaultV
}
if (matchingIndex != null) {
types.add(0, types.remove((int)matchingIndex));
Map<String, JsonNode> jsonProps = schema.getJsonProps();
Map<String, Object> jsonProps = schema.getObjectProps();
schema = Schema.createUnion(types);
// copy any properties over
for (String property : jsonProps.keySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@
import org.apache.avro.Schema;

import com.fasterxml.jackson.core.JsonParser.NumberType;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
import com.fasterxml.jackson.databind.type.TypeFactory;

public class StringVisitor extends JsonStringFormatVisitor.Base
implements SchemaBuilder
{
protected final SerializerProvider _provider;
protected final JavaType _type;
protected final DefinedSchemas _schemas;

protected Set<String> _enums;

public StringVisitor(DefinedSchemas schemas, JavaType t) {
public StringVisitor(SerializerProvider provider, DefinedSchemas schemas, JavaType t) {
_schemas = schemas;
_type = t;
_provider = provider;
}

@Override
Expand All @@ -40,13 +44,17 @@ public Schema builtAvroSchema() {
if (_type.hasRawClass(char.class) || _type.hasRawClass(Character.class)) {
return AvroSchemaHelper.numericAvroSchema(NumberType.INT, TypeFactory.defaultInstance().constructType(Character.class));
}
if (_enums == null) {
return Schema.create(Schema.Type.STRING);
BeanDescription bean = _provider.getConfig().introspectClassAnnotations(_type);
if (_enums != null) {
Schema s = AvroSchemaHelper.createEnumSchema(bean, new ArrayList<>(_enums));
_schemas.addSchema(_type, s);
return s;
}
Schema s = Schema.createEnum(AvroSchemaHelper.getName(_type), "",
AvroSchemaHelper.getNamespace(_type),
new ArrayList<String>(_enums));
_schemas.addSchema(_type, s);
return s;
Schema schema = Schema.create(Schema.Type.STRING);
// Stringable classes need to include the type
if (AvroSchemaHelper.isStringable(bean.getClassInfo())) {
schema.addProp(AvroSchemaHelper.AVRO_SCHEMA_PROP_CLASS, AvroSchemaHelper.getTypeId(_type));
}
return schema;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@ public JsonStringFormatVisitor expectStringFormat(JavaType type)
_valueSchema = s;
return null;
}
StringVisitor v = new StringVisitor(_schemas, type);
StringVisitor v = new StringVisitor(_provider, _schemas, type);
_builder = v;
return v;
}

@Override
public JsonNumberFormatVisitor expectNumberFormat(JavaType convertedType) {
DoubleVisitor v = new DoubleVisitor();
DoubleVisitor v = new DoubleVisitor(convertedType);
_builder = v;
return v;
}
Expand Down
Loading

0 comments on commit ca58cfc

Please sign in to comment.