/** * Copyright © 2016 Jeremy Custenborder ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.jcustenborder.kafka.connect.utils.jackson; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import org.apache.kafka.connect.data.Field; import org.apache.kafka.connect.data.Schema; import org.apache.kafka.connect.data.SchemaBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; public class SchemaSerializationModule extends SimpleModule { private static final Logger log = LoggerFactory.getLogger(SchemaSerializationModule.class); public SchemaSerializationModule() { super(); addSerializer(Schema.class, new Serializer()); addDeserializer(Schema.class, new Deserializer()); } public static class Storage { public String name; public String doc; public Schema.Type type; public Object defaultValue; public Integer version; public Map<String, String> parameters; public boolean isOptional; public Schema keySchema; public Schema valueSchema; public Map<String, Schema> fieldSchemas; public Storage() { } Storage(Schema schema) { this.name = schema.name(); this.doc = schema.doc(); this.type = schema.type(); this.defaultValue = schema.defaultValue(); this.version = schema.version(); this.parameters = schema.parameters(); this.isOptional = schema.isOptional(); if (Schema.Type.MAP == this.type) { this.keySchema = schema.keySchema(); this.valueSchema = schema.valueSchema(); } else if (Schema.Type.ARRAY == this.type) { this.keySchema = null; this.valueSchema = schema.valueSchema(); } else if (Schema.Type.STRUCT == this.type) { this.fieldSchemas = new LinkedHashMap<>(); for (Field field : schema.fields()) { this.fieldSchemas.put(field.name(), field.schema()); } } } public Schema build() { log.trace(this.toString()); SchemaBuilder builder; switch (this.type) { case MAP: Preconditions.checkNotNull(this.keySchema, "keySchema cannot be null."); Preconditions.checkNotNull(this.valueSchema, "valueSchema cannot be null."); builder = SchemaBuilder.map(this.keySchema, this.valueSchema); break; case ARRAY: Preconditions.checkNotNull(this.valueSchema, "valueSchema cannot be null."); builder = SchemaBuilder.array(this.valueSchema); break; default: builder = SchemaBuilder.type(this.type); break; } if (Schema.Type.STRUCT == this.type) { for (Map.Entry<String, Schema> kvp : this.fieldSchemas.entrySet()) { builder.field(kvp.getKey(), kvp.getValue()); } } if (!Strings.isNullOrEmpty(this.name)) { builder.name(this.name); } if (!Strings.isNullOrEmpty(this.doc)) { builder.doc(this.doc); } if (null != this.defaultValue) { Object value; switch (this.type) { case INT8: value = ((Number) this.defaultValue).byteValue(); break; case INT16: value = ((Number) this.defaultValue).shortValue(); break; case INT32: value = ((Number) this.defaultValue).intValue(); break; case INT64: value = ((Number) this.defaultValue).longValue(); break; case FLOAT32: value = ((Number) this.defaultValue).floatValue(); break; case FLOAT64: value = ((Number) this.defaultValue).doubleValue(); break; default: value = this.defaultValue; break; } builder.defaultValue(value); } if (null != this.parameters) { builder.parameters(this.parameters); } if (this.isOptional) { builder.optional(); } if (null != this.version) { builder.version(this.version); } return builder.build(); } @Override public String toString() { return MoreObjects.toStringHelper(this.getClass()) .add("type", this.type) .add("name", this.name) .add("isOptional", this.isOptional) .add("version", this.version) .add("keySchema", this.keySchema) .add("valueSchema", this.valueSchema) .add("parameters", this.parameters) .add("doc", this.doc) .add("defaultValue", this.defaultValue) .omitNullValues() .toString(); } } static class Serializer extends JsonSerializer<Schema> { @Override public void serialize(Schema schema, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { Storage storage = new Storage(schema); jsonGenerator.writeObject(storage); } } static class Deserializer extends JsonDeserializer<Schema> { @Override public Schema deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { Storage storage = jsonParser.readValueAs(Storage.class); return storage.build(); } } }