All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cc.voox.graphql.GraphQLProvider Maven / Gradle / Ivy

The newest version!
package cc.voox.graphql;

import cc.voox.graphql.annotation.ObjectField;
import cc.voox.graphql.annotation.ObjectType;
import cc.voox.graphql.metadata.TypeEntity;
import cc.voox.graphql.metadata.TypeField;
import cc.voox.graphql.utils.GraphQLTypeUtils;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import graphql.GraphQL;
import graphql.Scalars;
import graphql.analysis.MaxQueryDepthInstrumentation;
import graphql.execution.instrumentation.ChainedInstrumentation;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions;
import graphql.scalars.ExtendedScalars;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLEnumValueDefinition;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLTypeReference;
import graphql.schema.GraphqlTypeBuilder;
import graphql.schema.TypeResolver;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeRuntimeWiring;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cc.voox.graphql.utils.GraphQLTypeUtils.isGraphQLPrimitive;

@Component
@ComponentScan(value = "graphql.spring.web.servlet", excludeFilters = @ComponentScan.Filter(classes = {RestController.class}))
public class GraphQLProvider {

    @Autowired
    private GraphqlResolverFactory graphqlResolverFactory;
    @Autowired
    private GraphqlProperties graphqlProperties;

    private GraphQL graphQL;
    private List typeEntityList = new ArrayList<>();
//    private Set additionalTypes = new HashSet<>();
    private Map additionalTypesBuilders = new HashMap<>();

    void initSchema() {
        if (!graphqlProperties.isEnableCodeMode()) {
            return;
        }
        List> typeList = graphqlResolverFactory.getTypeList();
        for (Class clz : typeList) {
            ObjectType objectType = clz.getAnnotation(ObjectType.class);
            TypeEntity typeEntity = convert(objectType, clz);
            typeEntityList.add(typeEntity);
        }
        typeEntityList.stream().forEach(typeEntity -> {

            if (typeEntity.isInputType()) {
                GraphQLInputObjectType.Builder builder = GraphQLInputObjectType.newInputObject()
                        .name(typeEntity.getName())
                        .description(typeEntity.getDescription());

                typeEntity.getTypeField().stream().forEach(typeField -> {
                    GraphQLInputObjectField.Builder graphQLFieldDefinition = GraphQLInputObjectField.newInputObjectField()
                            .name(typeField.getValue())
                            .description(typeField.getDescription());
                    Object typeFieldType = typeField.getType();
                    if (typeFieldType instanceof GraphQLInputObjectType) {
                        graphQLFieldDefinition.type((GraphQLInputObjectType) typeFieldType);
                    } else if (typeFieldType instanceof GraphQLScalarType) {
                        graphQLFieldDefinition.type((GraphQLScalarType) typeFieldType);
                    } else if (typeFieldType instanceof GraphQLTypeReference) {
                        graphQLFieldDefinition.type((GraphQLTypeReference) typeFieldType);
                    }
                    builder.field(graphQLFieldDefinition.build());
                });
                additionalTypesBuilders.put(typeEntity.getName(), builder);
            } else if(typeEntity.isEnumType()) {
                GraphQLEnumType.Builder builder = GraphQLEnumType.newEnum()
                        .name(typeEntity.getName())
                        .description(typeEntity.getDescription());
                typeEntity.getValues().stream().forEach(o -> {
                    if( o != null) {
                        builder.value(o.toString(), o);
                    }
                });
                additionalTypesBuilders.put(typeEntity.getName(), builder);
            }else {
                GraphQLObjectType.Builder builder = GraphQLObjectType.newObject()
                        .name(typeEntity.getName())
                        .description(typeEntity.getDescription());

                typeEntity.getTypeField().stream().forEach(typeField -> {
                    GraphQLFieldDefinition.Builder graphQLFieldDefinition = GraphQLFieldDefinition.newFieldDefinition()
                            .name(typeField.getValue())
                            .description(typeField.getDescription());
                    Object typeFieldType = typeField.getType();
                    if (typeFieldType instanceof GraphQLObjectType) {
                        graphQLFieldDefinition.type((GraphQLObjectType) typeFieldType);
                    } else if (typeFieldType instanceof GraphQLScalarType) {
                        graphQLFieldDefinition.type((GraphQLScalarType) typeFieldType);
                    } else if (typeFieldType instanceof GraphQLTypeReference) {
                        graphQLFieldDefinition.type((GraphQLTypeReference) typeFieldType);
                    }
                    builder.field(graphQLFieldDefinition.build());

                });
                additionalTypesBuilders.put(typeEntity.getName(), builder);
            }
        });
    }

    private TypeEntity convert(ObjectType objectType, Class clz) {
        TypeEntity typeEntity = new TypeEntity();
        String value = objectType.value();
        if (StringUtils.isEmpty(value)) {
            value = clz.getSimpleName();
        }
        typeEntity.setName(value);
        typeEntity.setDescription(objectType.description());
        typeEntity.setInputType(objectType.inputType());
        typeEntity.setEnumType(clz.isEnum());
        List typeFields = initFields(clz);
        typeEntity.setTypeField(typeFields);
        if (clz.isEnum()) {
            Object[] enumConstants = clz.getEnumConstants();
            typeEntity.setValues(Stream.of(enumConstants).collect(Collectors.toList()));
        }
        return typeEntity;
    }

    private List initFields(Class clz) {
        List typeFields = new ArrayList<>();
        ReflectionUtils.doWithFields(clz, field -> {
                    field.setAccessible(true);
                    ObjectField objectField = field.getAnnotation(ObjectField.class);
                    TypeField typeField = convertField(objectField, field);
                    typeFields.add(typeField);
                }, field -> field.isAnnotationPresent(ObjectField.class)
        );
        return typeFields;
    }

    private TypeField convertField(ObjectField objectField, Field field) {
        TypeField typeField = new TypeField();
        typeField.setDescription(objectField.description());
        String value = objectField.value();
        if (StringUtils.isEmpty(value)) {
            value = field.getName();
        }
        typeField.setValue(value);
        Class fieldType = field.getType();
        boolean isPrimitive = fieldType.isPrimitive();
        if (isPrimitive || isGraphQLPrimitive(fieldType)) {
            if (objectField.id()) {
                typeField.setType(Scalars.GraphQLID);
            } else {
                GraphQLScalarType objectFieldType = GraphQLTypeUtils.getType(fieldType);
                typeField.setType(objectFieldType);
            }
        } else {
            if (Collection.class.isAssignableFrom(fieldType)) {
                ParameterizedType typeListType = (ParameterizedType) field.getGenericType();
                Class typeListClass = (Class) typeListType.getActualTypeArguments()[0];
                GraphQLScalarType objectFieldType = GraphQLTypeUtils.getType(typeListClass);
                if (objectFieldType != null) {
                    typeField.setType(GraphQLList.list(objectFieldType));
                } else {
                    typeField.setType(GraphQLList.list(GraphQLTypeReference.typeRef(typeListClass.getSimpleName())));
                }
            } else {
                typeField.setType(GraphQLTypeReference.typeRef(fieldType.getSimpleName()));
            }
        }

        return typeField;
    }




    @PostConstruct
    public void init() throws IOException {
        GraphQLSchema graphQLSchema = null;
        if(graphqlProperties.isEnableCodeMode()) {
            initSchema();
            graphQLSchema = buildSchema();
        } else {
            URL url = Resources.getResource(graphqlProperties.getSchema());
            String sdl = Resources.toString(url, Charsets.UTF_8);
            graphQLSchema = buildSchema(sdl);
        }
        DataLoaderDispatcherInstrumentationOptions options = DataLoaderDispatcherInstrumentationOptions
                .newOptions().includeStatistics(graphqlProperties.isOpenStatistics());

        DataLoaderDispatcherInstrumentation dispatcherInstrumentation
                = new DataLoaderDispatcherInstrumentation(options);
        Set interceptors = graphqlResolverFactory.getInterceptors();
        List graphQLInterceptors = new ArrayList<>(interceptors);
        List collect = graphQLInterceptors.stream().sorted(Comparator.comparingInt(GraphQLInterceptor::getOrder)).collect(Collectors.toList());
        MaxQueryDepthInstrumentation maxQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(graphqlProperties.getMaxQueryDepth());
//        MaxQueryComplexityInstrumentation maxQueryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(100);
        List chainedList = new ArrayList<>();
        chainedList.add(dispatcherInstrumentation);
        chainedList.add(maxQueryDepthInstrumentation);
        chainedList.addAll(collect);
        ChainedInstrumentation chainedInstrumentation = new ChainedInstrumentation(chainedList);
        this.graphQL = GraphQL.newGraphQL(graphQLSchema).instrumentation(chainedInstrumentation).build();
    }

    private GraphQLSchema buildSchema() {
        if(additionalTypesBuilders.size() > 0) {
            Map> resolvers = graphqlResolverFactory.getResolvers();
            Map> typeFieldMap = graphqlResolverFactory.getTypeFieldMap();
            GraphQLSchema.Builder schemaBuilder = GraphQLSchema.newSchema();
            Set queries = typeFieldMap.get("Query");
            Set mutations = typeFieldMap.get("Mutation");
            Map queryResolvers = resolvers.get("Query");
            Map mutationResolvers = resolvers.get("Mutation");

            GraphQLObjectType.Builder queryBuilder = GraphQLObjectType.newObject().name("query");
            GraphQLObjectType.Builder mutationBuilder = GraphQLObjectType.newObject().name("mutation");

            setBuilder(queries, queryResolvers, queryBuilder);
            setBuilder(mutations, mutationResolvers, mutationBuilder);
            Set keys = new HashSet<>();
            keys.add("Query");
            keys.add("Mutation");
            typeFieldMap.forEach((s, typeFields) -> {
                if(!Arrays.asList("Query", "Mutation").contains(s)) {
                    GraphqlTypeBuilder builder = additionalTypesBuilders.get(s);
                    setTypes(schemaBuilder, builder);
                    keys.add(s);

                }
            });
            additionalTypesBuilders.forEach((s, graphqlTypeBuilder) -> {
                if (!keys.contains(s)) {
                    setTypes(schemaBuilder, graphqlTypeBuilder);
                }
            });
            schemaBuilder.query(queryBuilder.build());
            schemaBuilder.mutation(mutationBuilder.build());

            return schemaBuilder.build();

        } else {
            throw new Error("No found graphql schema.");
        }
    }

    private void setTypes(GraphQLSchema.Builder schemaBuilder, GraphqlTypeBuilder graphqlTypeBuilder) {
        if (graphqlTypeBuilder instanceof GraphQLObjectType.Builder) {
            GraphQLObjectType.Builder builder = (GraphQLObjectType.Builder) graphqlTypeBuilder;
            schemaBuilder.additionalType(builder.build());
        } else if(graphqlTypeBuilder instanceof  GraphQLEnumType.Builder) {
            GraphQLEnumType.Builder extraBuilder = (GraphQLEnumType.Builder) graphqlTypeBuilder;
            schemaBuilder.additionalType(extraBuilder.build());
        } else if (graphqlTypeBuilder instanceof GraphQLInputObjectType.Builder) {
            GraphQLInputObjectType.Builder builder = (GraphQLInputObjectType.Builder) graphqlTypeBuilder;
            schemaBuilder.additionalType(builder.build());
        }
    }

    private void setBuilder(Set queries, Map dataFetcherMap, GraphQLObjectType.Builder queryBuilder) {
        queries.forEach(tf -> queryBuilder.field(builder -> {
            builder.name(tf.getValue());
            builder.type((GraphQLOutputType) tf.getType());
            builder.arguments(tf.getArguments());
//            GraphQLDirective.Builder b = GraphQLDirective.newDirective().
//            builder.withDirectives();
            builder.dataFetcher(dataFetcherMap.get(tf.getValue()));
            return builder;
        }).build());
    }

    private GraphQLSchema buildSchema(String sdl) {
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
        RuntimeWiring runtimeWiring = buildWiring();
        GraphQLSchema graphQLSchema = null;
        if(!graphqlProperties.isEnableCodeMode()) {
            SchemaGenerator schemaGenerator = new SchemaGenerator();
            graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
        } else {
            throw new Error("No graphQL schema found.");
        }
        return graphQLSchema;
    }

    private RuntimeWiring buildWiring() {
        RuntimeWiring.Builder rb = RuntimeWiring.newRuntimeWiring();

        //build scalars
        graphqlResolverFactory.getScalarSet().forEach(s -> {
            rb.scalar(GraphQLScalarType.newScalar().name(s.getName()).description(s.getDescription()).coercing(s).build());
        });
        rb.scalar(ExtendedScalars.Date);
        rb.scalar(ExtendedScalars.Object);
        rb.scalar(ExtendedScalars.DateTime);
        rb.scalar(ExtendedScalars.Json);
        rb.scalar(ExtendedScalars.Url);
        //build directives
        graphqlResolverFactory.getDirectiveSet().forEach(d-> {
            rb.directive(d.getName(), d).build();
        });

        //build resolvers
        graphqlResolverFactory.getBuilders().forEach(b -> rb.type(b));

        return rb.build();
    }

    @Bean
    public GraphQL graphQL() {
        return graphQL;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy