AvroMapper.java
package tools.jackson.dataformat.avro;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.apache.avro.Schema;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.Version;
import tools.jackson.databind.*;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.cfg.MapperBuilderState;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.dataformat.avro.schema.AvroSchemaGenerator;
/**
* Convenience {@link AvroMapper}, which is mostly similar to simply
* constructing a mapper with {@link AvroFactory}, but also adds little
* bit of convenience around {@link AvroSchema} generation.
*/
public class AvroMapper extends ObjectMapper
{
private static final long serialVersionUID = 3L;
/**
* Base implementation for "Vanilla" {@link ObjectMapper}, used with
* Avro backend.
*
* @since 3.0
*/
public static class Builder extends MapperBuilder<AvroMapper, Builder>
{
public Builder(AvroFactory f) {
super(f);
addModule(new AvroModule());
// 26-Nov-2019, tatu: Since Avro does not allow open-ended classes,
// but does rely on polymorphic (but not default) type handling,
// we need to use more permissive PTV:
_baseSettings = _baseSettings.with(AvroSubTypeValidator.instance);
}
public Builder(StateImpl state) {
super(state);
// no need to add module, should come by default
}
@Override
public AvroMapper build() {
return new AvroMapper(this);
}
@Override
protected MapperBuilderState _saveState() {
// nothing extra, just format features
return new StateImpl(this);
}
/*
/******************************************************************
/* Format features
/******************************************************************
*/
public Builder enable(AvroReadFeature... features) {
for (AvroReadFeature f : features) {
_formatReadFeatures |= f.getMask();
}
return this;
}
public Builder disable(AvroReadFeature... features) {
for (AvroReadFeature f : features) {
_formatReadFeatures &= ~f.getMask();
}
return this;
}
public Builder configure(AvroReadFeature feature, boolean state)
{
if (state) {
_formatReadFeatures |= feature.getMask();
} else {
_formatReadFeatures &= ~feature.getMask();
}
return this;
}
public Builder enable(AvroWriteFeature... features) {
for (AvroWriteFeature f : features) {
_formatWriteFeatures |= f.getMask();
}
return this;
}
public Builder disable(AvroWriteFeature... features) {
for (AvroWriteFeature f : features) {
_formatWriteFeatures &= ~f.getMask();
}
return this;
}
public Builder configure(AvroWriteFeature feature, boolean state)
{
if (state) {
_formatWriteFeatures |= feature.getMask();
} else {
_formatWriteFeatures &= ~feature.getMask();
}
return this;
}
protected static class StateImpl extends MapperBuilderState
implements java.io.Serializable // important!
{
private static final long serialVersionUID = 3L;
public StateImpl(Builder src) {
super(src);
}
// We also need actual instance of state as base class can not implement logic
// for reinstating mapper (via mapper builder) from state.
@Override
protected Object readResolve() {
return new Builder(this).build();
}
}
}
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
/**
* Constructor that will construct mapper with standard {@link AvroFactory}
* as codec, and will also register {@link AvroModule}.
*/
public AvroMapper() {
this(new AvroFactory());
}
/**
* Constructor that will construct mapper with given {@link AvroFactory},
* as well as register standard {@link AvroModule} (with default settings).
*/
public AvroMapper(AvroFactory f) {
this(new Builder(f));
}
public AvroMapper(Builder b) {
super(b);
}
public static Builder builder() {
return new Builder(new AvroFactory());
}
public static Builder builder(AvroFactory streamFactory) {
return new Builder(streamFactory);
}
@SuppressWarnings("unchecked")
@Override
public Builder rebuild() {
return new AvroMapper.Builder((Builder.StateImpl) _savedBuilderState);
}
/*
/**********************************************************************
/* Life-cycle, shared "vanilla" (default configuration) instance
/**********************************************************************
*/
/**
* Accessor method for getting globally shared "default" {@link AvroMapper}
* instance: one that has default configuration, no modules registered, no
* config overrides. Usable mostly when dealing "untyped" or Tree-style
* content reading and writing.
*/
public static AvroMapper shared() {
return SharedWrapper.wrapped();
}
/*
/**********************************************************************
/* Basic accessor overrides
/**********************************************************************
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
@Override
public AvroFactory tokenStreamFactory() {
return (AvroFactory) _streamFactory;
}
/*
/**********************************************************************
/* Format-specific
/**********************************************************************
*/
public boolean isEnabled(AvroReadFeature f) {
return _deserializationConfig.hasFormatFeature(f);
}
public boolean isEnabled(AvroWriteFeature f) {
return _serializationConfig.hasFormatFeature(f);
}
/*
/**********************************************************************
/* Schema introspection
/**********************************************************************
*/
/**
* Factory method for constructing {@link AvroSchema} by introspecting given
* POJO type and building schema that contains specified properties.
*<p>
* Resulting schema object does not use separate reader/writer schemas.
*/
public AvroSchema schemaFor(Class<?> type)
{
AvroSchemaGenerator gen = new AvroSchemaGenerator();
try {
acceptJsonFormatVisitor(type, gen);
return gen.getGeneratedSchema();
} catch (RuntimeException e0) {
throw _invalidSchemaDefinition(constructType(type), e0);
}
}
/**
* Factory method for constructing {@link AvroSchema} by introspecting given
* POJO type and building schema that contains specified properties.
*<p>
* Resulting schema object does not use separate reader/writer schemas.
*/
public AvroSchema schemaFor(JavaType type)
{
AvroSchemaGenerator gen = new AvroSchemaGenerator();
try {
acceptJsonFormatVisitor(type, gen);
return gen.getGeneratedSchema();
} catch (RuntimeException e0) {
throw _invalidSchemaDefinition(type, e0);
}
}
// @since 2.13
protected JacksonException _invalidSchemaDefinition(JavaType type,
Exception e0)
{
String msg = String.format(
"Failed to generate `AvroSchema` for %s, problem: (%s) %s",
ClassUtil.getTypeDescription(type),
e0.getClass().getName(), e0.getMessage()
);
return InvalidDefinitionException.from((JsonGenerator) null, msg, type)
.withCause(e0);
}
/**
* Method for reading an Avro Schema from given {@link InputStream},
* and once done (successfully or not), closing the stream.
*<p>
* Resulting schema object does not use separate reader/writer schemas.
*/
public AvroSchema schemaFrom(InputStream in) throws IOException
{
try {
return new AvroSchema(new Schema.Parser().setValidate(true)
.parse(in));
} finally {
in.close();
}
}
/**
* Convenience method for reading {@link AvroSchema} from given
* encoded JSON representation.
*<p>
* Resulting schema object does not use separate reader/writer schemas.
*/
public AvroSchema schemaFrom(String schemaAsString) throws IOException
{
return new AvroSchema(new Schema.Parser().setValidate(true)
.parse(schemaAsString));
}
/**
* Convenience method for reading {@link AvroSchema} from given
* encoded JSON representation.
*<p>
* Resulting schema object does not use separate reader/writer schemas.
*/
public AvroSchema schemaFrom(File schemaFile) throws IOException
{
return new AvroSchema(new Schema.Parser().setValidate(true)
.parse(schemaFile));
}
/*
/**********************************************************
/* Helper class(es)
/**********************************************************
*/
/**
* Helper class to contain dynamically constructed "shared" instance of
* mapper, should one be needed via {@link #shared}.
*/
private final static class SharedWrapper {
private final static AvroMapper MAPPER = AvroMapper.builder().build();
public static AvroMapper wrapped() { return MAPPER; }
}
}