/*
* 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.facebook.presto.metadata;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Throwables.propagateIfInstanceOf;
import static java.util.Objects.requireNonNull;
public abstract class AbstractTypedJacksonModule<T>
extends SimpleModule
{
private static final String TYPE_PROPERTY = "@type";
protected AbstractTypedJacksonModule(
Class<T> baseClass,
Function<T, String> nameResolver,
Function<String, Class<? extends T>> classResolver)
{
super(baseClass.getSimpleName() + "Module", Version.unknownVersion());
TypeIdResolver typeResolver = new InternalTypeResolver<>(nameResolver, classResolver);
addSerializer(baseClass, new InternalTypeSerializer<>(baseClass, typeResolver));
addDeserializer(baseClass, new InternalTypeDeserializer<>(baseClass, typeResolver));
}
private static class InternalTypeDeserializer<T>
extends StdDeserializer<T>
{
private final TypeDeserializer typeDeserializer;
public InternalTypeDeserializer(Class<T> baseClass, TypeIdResolver typeIdResolver)
{
super(baseClass);
this.typeDeserializer = new AsPropertyTypeDeserializer(SimpleType.construct(baseClass), typeIdResolver, TYPE_PROPERTY, false, null);
}
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException
{
return (T) typeDeserializer.deserializeTypedFromAny(jsonParser, deserializationContext);
}
}
private static class InternalTypeSerializer<T>
extends StdSerializer<T>
{
private final TypeSerializer typeSerializer;
private final Cache<Class<?>, JsonSerializer<T>> serializerCache = CacheBuilder.newBuilder().build();
public InternalTypeSerializer(Class<T> baseClass, TypeIdResolver typeIdResolver)
{
super(baseClass);
this.typeSerializer = new AsPropertyTypeSerializer(typeIdResolver, null, TYPE_PROPERTY);
}
@Override
public void serialize(T value, JsonGenerator generator, SerializerProvider provider)
throws IOException
{
if (value == null) {
provider.defaultSerializeNull(generator);
return;
}
try {
Class<?> type = value.getClass();
JsonSerializer<T> serializer = serializerCache.get(type, () -> createSerializer(provider, type));
serializer.serializeWithType(value, generator, provider, typeSerializer);
}
catch (ExecutionException e) {
propagateIfInstanceOf(e.getCause(), IOException.class);
throw Throwables.propagate(e.getCause());
}
}
@SuppressWarnings("unchecked")
private static <T> JsonSerializer<T> createSerializer(SerializerProvider provider, Class<?> type)
throws JsonMappingException
{
JavaType javaType = provider.constructType(type);
return (JsonSerializer<T>) BeanSerializerFactory.instance.createSerializer(provider, javaType);
}
}
private static class InternalTypeResolver<T>
extends TypeIdResolverBase
{
private final Function<T, String> nameResolver;
private final Function<String, Class<? extends T>> classResolver;
public InternalTypeResolver(Function<T, String> nameResolver, Function<String, Class<? extends T>> classResolver)
{
this.nameResolver = requireNonNull(nameResolver, "nameResolver is null");
this.classResolver = requireNonNull(classResolver, "classResolver is null");
}
@Override
public String idFromValue(Object value)
{
return idFromValueAndType(value, value.getClass());
}
@SuppressWarnings("unchecked")
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType)
{
requireNonNull(value, "value is null");
String type = nameResolver.apply((T) value);
checkArgument(type != null, "Unknown class: %s", suggestedType.getSimpleName());
return type;
}
@Override
public JavaType typeFromId(DatabindContext context, String id)
{
requireNonNull(id, "id is null");
Class<?> typeClass = classResolver.apply(id);
checkArgument(typeClass != null, "Unknown type ID: %s", id);
return context.getTypeFactory().constructType(typeClass);
}
@Override
public Id getMechanism()
{
return Id.NAME;
}
}
}