package io.leangen.graphql.metadata.strategy.value.gson; import com.google.gson.FieldNamingStrategy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.TypeAdapterFactory; import io.leangen.geantyref.GenericTypeReflector; import io.leangen.graphql.execution.GlobalEnvironment; import io.leangen.graphql.metadata.messages.MessageBundle; import io.leangen.graphql.metadata.strategy.type.DefaultTypeInfoGenerator; import io.leangen.graphql.metadata.strategy.type.TypeInfoGenerator; import io.leangen.graphql.metadata.strategy.value.ScalarDeserializationStrategy; import io.leangen.graphql.metadata.strategy.value.ValueMapper; import io.leangen.graphql.metadata.strategy.value.ValueMapperFactory; import io.leangen.graphql.util.ClassUtils; import net.dongliu.gson.GsonJava8TypeAdapterFactory; import java.lang.reflect.AnnotatedType; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; /** * @author Bojan Tomic (kaqqao) */ public class GsonValueMapperFactory implements ValueMapperFactory, ScalarDeserializationStrategy { private final Gson prototype; private final FieldNamingStrategy fieldNamingStrategy; private final TypeInfoGenerator typeInfoGenerator; private final List<Configurer> configurers; public GsonValueMapperFactory() { this(null, new DefaultTypeInfoGenerator(), null, Collections.singletonList(new AbstractClassAdapterConfigurer())); } private GsonValueMapperFactory(Gson prototype, TypeInfoGenerator typeInfoGenerator, FieldNamingStrategy fieldNamingStrategy, List<Configurer> configurers) { this.prototype = prototype; this.fieldNamingStrategy = fieldNamingStrategy; this.typeInfoGenerator = Objects.requireNonNull(typeInfoGenerator); this.configurers = Objects.requireNonNull(configurers); } @Override public GsonValueMapper getValueMapper(Map<Class, List<Class<?>>> concreteSubTypes, GlobalEnvironment environment) { return new GsonValueMapper(initBuilder(concreteSubTypes, environment).create()); } private GsonBuilder initBuilder(Map<Class, List<Class<?>>> concreteSubTypes, GlobalEnvironment environment) { GsonBuilder gsonBuilder = (prototype != null ? prototype.newBuilder() : new GsonBuilder()) .serializeNulls() .setFieldNamingStrategy(fieldNamingStrategy != null ? fieldNamingStrategy : new GsonFieldNamingStrategy(environment.messageBundle)) .registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory()); return configurers.stream().reduce(gsonBuilder, (builder, config) -> config.configure(new ConfigurerParams(builder, concreteSubTypes, this.typeInfoGenerator, environment)), (b1, b2) -> b2); } @Override public boolean isDirectlyDeserializable(AnnotatedType type) { return ClassUtils.isSuperClass(JsonElement.class, type); } public static Builder builder() { return new Builder(); } public static class AbstractClassAdapterConfigurer implements Configurer { @Override public GsonBuilder configure(ConfigurerParams params) { params.concreteSubTypes.entrySet().stream() .map(entry -> adapterFor(entry.getKey(), entry.getValue(), params.infoGenerator, params.environment.messageBundle)) .filter(Objects::nonNull) .forEach(params.gsonBuilder::registerTypeAdapterFactory); if (!params.environment.getInputConverters().isEmpty()) { params.gsonBuilder.registerTypeAdapterFactory(new ConvertingAdapterFactory(params.environment)); } return params.gsonBuilder; } @SuppressWarnings("unchecked") private TypeAdapterFactory adapterFor(Class superClass, List<Class<?>> implementations, TypeInfoGenerator infoGen, MessageBundle messageBundle) { RuntimeTypeAdapterFactory adapterFactory = RuntimeTypeAdapterFactory.of(superClass, ValueMapper.TYPE_METADATA_FIELD_NAME); if (implementations.isEmpty()) { return null; } implementations.stream() .filter(impl -> !ClassUtils.isAbstract(impl)) .forEach(impl -> adapterFactory.registerSubtype(impl, infoGen.generateTypeName(GenericTypeReflector.annotate(impl), messageBundle))); return adapterFactory; } } @FunctionalInterface public interface Configurer { GsonBuilder configure(ConfigurerParams params); } public static class ConfigurerParams { final GsonBuilder gsonBuilder; final Map<Class, List<Class<?>>> concreteSubTypes; final TypeInfoGenerator infoGenerator; final GlobalEnvironment environment; ConfigurerParams(GsonBuilder gsonBuilder, Map<Class, List<Class<?>>> concreteSubTypes, TypeInfoGenerator infoGenerator, GlobalEnvironment environment) { this.gsonBuilder = gsonBuilder; this.concreteSubTypes = concreteSubTypes; this.infoGenerator = infoGenerator; this.environment = environment; } public GsonBuilder getGsonBuilder() { return gsonBuilder; } public Map<Class, List<Class<?>>> getConcreteSubTypes() { return concreteSubTypes; } public TypeInfoGenerator getInfoGenerator() { return infoGenerator; } public GlobalEnvironment getEnvironment() { return environment; } } @Override public String toString() { return this.getClass().getSimpleName(); } public static class Builder { private Gson prototype; private FieldNamingStrategy fieldNamingStrategy; private TypeInfoGenerator typeInfoGenerator = new DefaultTypeInfoGenerator(); private List<Configurer> configurers = new ArrayList<>(Collections.singleton(new AbstractClassAdapterConfigurer())); public Builder withFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) { this.fieldNamingStrategy = fieldNamingStrategy; return this; } public Builder withTypeInfoGenerator(TypeInfoGenerator typeInfoGenerator) { this.typeInfoGenerator = typeInfoGenerator; return this; } public Builder withPrototype(Gson prototype) { this.prototype = prototype; return this; } public Builder withConfigurer(Configurer configurer) { Collections.addAll(this.configurers, configurer); return this; } public GsonValueMapperFactory build() { return new GsonValueMapperFactory(prototype, typeInfoGenerator, fieldNamingStrategy, configurers); } } }