package graphql.java.generator.type; import java.lang.reflect.Type; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import graphql.java.generator.BuildContext; import graphql.java.generator.BuildContextAware; import graphql.introspection.Introspection.TypeKind; import graphql.java.generator.UnsharableBuildContextStorer; import graphql.java.generator.type.strategies.TypeStrategies; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLEnumValueDefinition; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLType; import graphql.schema.GraphQLTypeReference; import graphql.schema.TypeResolver; /** * Given any object, decide how you wish the GraphQL type to be generated. * This class defines the implementation contract, and further TypeGenerators * must be built off of this, using {@link #getType(Object, Type, TypeKind)} * Use {@link WrappingTypeGenerator} to create types from {@code List<?>}s, otherwise * this generator will be happy to create an object based on the interface List.class, which * is not what you want. * @author dwinsor * */ public abstract class TypeGenerator extends UnsharableBuildContextStorer implements ITypeGenerator, BuildContextAware { private static Logger logger = LoggerFactory.getLogger(TypeGenerator.class); private final TypeStrategies strategies; private final TypeRepository typeRepository; public TypeGenerator(TypeStrategies strategies) { this.strategies = strategies; this.typeRepository = strategies.getTypeRepository(); } /** * * @param object A representative "object" from which to construct * a {@link GraphQLOutputType}, the exact type of which is contextual * @return */ @Override public final GraphQLOutputType getOutputType(Object object) { return (GraphQLOutputType) getParameterizedType(object, null, TypeKind.OBJECT); } /** * @param object A representative "object" from which to construct * a {@link GraphQLInterfaceType}, the exact type of which is contextual, * but MUST represent a java interface, NOT an object or class with an interface. * Will be stored internally as a {@link GraphQLOutputType}, so its name * must not clash with any GraphQLOutputType. * * @return */ @Override public final GraphQLInterfaceType getInterfaceType(Object object) { return (GraphQLInterfaceType) getParameterizedType(object, null, TypeKind.INTERFACE); } /** * @param object A representative "object" from which to construct * a {@link GraphQLInputType}, the exact type of which is contextual * @return */ @Override public final GraphQLInputType getInputType(Object object) { return (GraphQLInputType) getParameterizedType(object, null, TypeKind.INPUT_OBJECT); } @Override public GraphQLType getParameterizedType(Object object, Type genericType, TypeKind typeKind) { return getType(object, genericType, typeKind); } /** * An internal, unchanging impl. * @param object * @param genericType * @param typeKind * @return */ protected final GraphQLType getType(Object object, Type genericType, TypeKind typeKind) { logger.debug("{} object is [{}]", typeKind, object); //short circuit if it's a primitive type or some other user defined default GraphQLType defaultType = getDefaultType(object, typeKind); if (defaultType != null) { return defaultType; } String typeName = getGraphQLTypeName(object); if (typeName == null) { logger.debug("TypeName was null for object [{}]. " + "Type will attempt to be built but not placed in the TypeRepository", object); return generateType(object, typeKind); } //this check must come before generated*Types.get //necessary for synchronicity to avoid duplicate object creations Set<String> typesBeingBuilt = getContext().getTypesBeingBuilt(); if (typesBeingBuilt.contains(typeName)) { logger.debug("Using a reference to: [{}]", typeName); if (TypeKind.OBJECT.equals(typeKind)) { return new GraphQLTypeReference(typeName); } logger.error("While constructing type, using a reference to: [{}]", typeName); throw new RuntimeException("Cannot put type-cycles into input or interface types, " + "there is no GraphQLTypeReference"); } GraphQLType prevType = getTypeRepository().getGeneratedType(typeName, typeKind); if (prevType != null) { return prevType; } typesBeingBuilt.add(typeName); try { GraphQLType type = generateType(object, typeKind); if (getTypeRepository() != null && type != null) { getTypeRepository().registerType(typeName, type, typeKind); } return type; } catch (RuntimeException e) { logger.warn("Failed to generate type named {} with kind {}", typeName, typeKind); logger.debug("Failed to generate type, exception is ", e); throw e; } finally { typesBeingBuilt.remove(typeName); } } protected GraphQLType generateType(Object object, TypeKind typeKind) { switch (typeKind) { case OBJECT: return generateOutputType(object); case INTERFACE: return generateInterfaceType(object); case INPUT_OBJECT: return generateInputType(object); case ENUM: return generateEnumType(object); default: return null; } } protected abstract GraphQLOutputType generateOutputType(Object object); protected abstract GraphQLInterfaceType generateInterfaceType(Object object); protected abstract GraphQLInputType generateInputType(Object object); protected abstract GraphQLEnumType generateEnumType(Object object); protected List<GraphQLFieldDefinition> getOutputFieldDefinitions(Object object) { List<GraphQLFieldDefinition> definitions = getContext().getFieldsGeneratorStrategy() .getOutputFields(object); return definitions; } protected List<GraphQLInputObjectField> getInputFieldDefinitions(Object object) { List<GraphQLInputObjectField> definitions = getContext().getFieldsGeneratorStrategy() .getInputFields(object); return definitions; } protected GraphQLType getDefaultType(Object object, TypeKind typeKind) { return getStrategies().getDefaultTypeStrategy().getDefaultType(object, typeKind); } protected String getGraphQLTypeNameOrIdentityCode(Object object) { String typeName = getGraphQLTypeName(object); if (typeName == null) { typeName = "Object_" + String.valueOf(System.identityHashCode(object)); } return typeName; } protected String getGraphQLTypeName(Object object) { return getStrategies().getTypeNameStrategy().getTypeName(object); } protected String getTypeDescription(Object object) { return getStrategies().getTypeDescriptionStrategy().getTypeDescription(object); } protected List<GraphQLEnumValueDefinition> getEnumValues(Object object) { return getStrategies().getEnumValuesStrategy().getEnumValueDefinitions(object); } protected GraphQLInterfaceType[] getInterfaces(Object object) { return getStrategies().getInterfacesStrategy().getInterfaces(object); } protected TypeResolver getTypeResolver(Object object) { return getStrategies().getTypeResolverStrategy().getTypeResolver(object); } @Override public void setContext(BuildContext context) { super.setContext(context); getStrategies().setContext(context); } @Override public TypeStrategies getStrategies() { return strategies; } protected TypeRepository getTypeRepository() { return typeRepository; } }