Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Avro] Add support for @Stringable annotation #57

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -145,14 +145,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 @@ -211,6 +212,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