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

com.auritylab.graphql.kotlin.toolkit.spring.annotation.AnnotationResolver.kt Maven / Gradle / Ivy

package com.auritylab.graphql.kotlin.toolkit.spring.annotation

import graphql.schema.Coercing
import graphql.schema.DataFetcher
import graphql.schema.GraphQLScalarType
import graphql.schema.idl.FieldWiringEnvironment
import graphql.schema.idl.InterfaceWiringEnvironment
import graphql.schema.idl.ScalarWiringEnvironment
import graphql.schema.idl.SchemaDirectiveWiring
import graphql.schema.idl.UnionWiringEnvironment
import org.springframework.context.ApplicationContext
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.stereotype.Component

/**
 * Will fetch all annotated GraphQL components.
 * Including [GQLResolvers], [GQLResolver], [GQLTypeResolver], [GQLDirective] and [GQLScalar].
 */
@Component
class AnnotationResolver(
    private val context: ApplicationContext
) {
    private final val resolvers = fetchDataFetcherComponents()
    final val directives = fetchDirectiveComponents()
    private final val scalars = fetchScalarComponents()
    private val typeResolvers = fetchTypeResolverComponents()
    private final val interfaceTypeResolvers = typeResolvers
        .filter { it.key.scope == GQLTypeResolver.Scope.INTERFACE }
    private final val unionTypeResolvers = typeResolvers
        .filter { it.key.scope == GQLTypeResolver.Scope.UNION }

    /**
     * Will search for a [DataFetcher] which matches the given [FieldWiringEnvironment].
     * If none were found it will return null.
     */
    fun getResolver(env: FieldWiringEnvironment): DataFetcher<*>? =
        resolvers.entries
            .firstOrNull {
                env.parentType.name == it.key.container &&
                    env.fieldDefinition.name == it.key.field
            }?.value

    /**
     * Will search for a [GQLTypeResolver] which matches the given [InterfaceWiringEnvironment].
     * If none were found it will return null.
     */
    fun getTypeResolver(env: InterfaceWiringEnvironment): graphql.schema.TypeResolver? =
        interfaceTypeResolvers.entries
            .firstOrNull { env.interfaceTypeDefinition.name == it.key.type }
            ?.value

    /**
     * Will search for a [GQLTypeResolver] which matches the given [UnionWiringEnvironment].
     * If none were found it will return null.
     */
    fun getTypeResolver(env: UnionWiringEnvironment): graphql.schema.TypeResolver? =
        unionTypeResolvers.entries
            .firstOrNull { env.unionTypeDefinition.name == it.key.type }
            ?.value

    /**
     * Will search for a [GraphQLScalarType] which matches the given [ScalarWiringEnvironment].
     * If none were found it will return null.
     */
    fun getScalar(env: ScalarWiringEnvironment): GraphQLScalarType? =
        scalars.entries
            .firstOrNull { env.scalarTypeDefinition.name == it.key.name }
            ?.value

    /**
     * Will fetch all components which are marked with [GQLResolver] or [GQLResolvers].
     * The components will then be mapped with the [DataFetcher] instance and the according annotation.
     */
    private fun fetchDataFetcherComponents(): Map> {
        val singleComponents = context.getBeansWithAnnotation(GQLResolver::class.java).values
        val multiComponents = context.getBeansWithAnnotation(GQLResolvers::class.java).values

        // Map the single components.
        val flat = mapComponents>(singleComponents)

        // Map the multi components.
        val multi = mapComponents>(multiComponents)
            .flatMap { map -> map.key.resolvers.map { Pair(it, map.value) } }
            .associate { it }

        // Join the maps.
        return flat.plus(multi)
    }

    /**
     * Will fetch all components which are marked with [GQLDirective].
     * The components will be then be mapped with the [SchemaDirectiveWiring] instance and the according annotation.
     */
    private fun fetchDirectiveComponents(): Map =
        mapComponents(context.getBeansWithAnnotation(GQLDirective::class.java).values)

    /**
     * Will fetch all components which are marked with [GQLTypeResolver].
     * The components will be then mapped with the [GQLTypeResolver] instance and the according instance.
     */
    private fun fetchTypeResolverComponents(): Map =
        mapComponents(context.getBeansWithAnnotation(GQLTypeResolver::class.java).values)

    /**
     * Will fetch all components which are marked with [GQLScalar]
     */
    private fun fetchScalarComponents(): Map =
        mapComponents>(context.getBeansWithAnnotation(GQLScalar::class.java).values)
            // Map the value to an actual GraphQLScalarType instance.
            .map { Pair(it.key, buildGraphQLScalarType(it.key.name, it.value)) }
            .associate { it }

    /**
     * Will build a new [GraphQLScalarType] instance with the given [name] and [coercing].
     */
    private fun buildGraphQLScalarType(name: String, coercing: Coercing<*, *>): GraphQLScalarType =
        GraphQLScalarType.newScalar().name(name).coercing(coercing).build()

    /**
     * Will check if the component is [C] and map it to the correct type.
     * In additional it will associate the annotation instance.
     *
     * @param components Collection of all components.
     * @return Map of all valid components mapped with the annotation as key.
     */
    private inline fun  mapComponents(
        components: Collection
    ): Map = components
        // Filter with type check
        .filterIsInstance()
        // Associate with the annotation (It assumes there is a annotation of that type).
        .associateBy {
            AnnotationUtils.findAnnotation(it::class.java, A::class.java)!!
        }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy