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

io.github.graphglue.definition.RelationshipDefinition.kt Maven / Gradle / Ivy

Go to download

A library to develop annotation-based code-first GraphQL servers using GraphQL Kotlin, Spring Boot and Neo4j - excluding Spring GraphQL server dependencies

The newest version!
package io.github.graphglue.definition

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
import graphql.schema.GraphQLFieldDefinition
import io.github.graphglue.data.execution.NodeQueryResult
import io.github.graphglue.data.repositories.RelationshipDiff
import io.github.graphglue.graphql.extensions.getPropertyName
import io.github.graphglue.graphql.schema.SchemaTransformationContext
import io.github.graphglue.model.Direction
import io.github.graphglue.model.Node
import io.github.graphglue.model.NodeRelationship
import io.github.graphglue.model.property.NodePropertyDelegate
import org.neo4j.cypherdsl.core.ExposesPatternLengthAccessors
import org.neo4j.cypherdsl.core.ExposesRelationships
import org.neo4j.cypherdsl.core.RelationshipPattern
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.KVisibility
import kotlin.reflect.full.*


/**
 * Defines a relationship between two [Node]s
 * There may or may not be an inverse relation on the foreign node
 *
 * @param property the property on the class which defines the relationship
 * @param nodeKClass the class associated with the item nodes
 * @param type the type of the relation (label associated with Neo4j relationship)
 * @param direction direction of the relation (direction associated with Neo4j relationship)
 * @param parentKClass the class associated with the [NodeDefinition] this is used as part of,
 *                     must be a subclass of the property defining class
 * @param allowedAuthorizations the names of authorizations which allow via this relation.
 *                              These names result in properties with value `true` on the relation
 */
abstract class RelationshipDefinition(
    val property: KProperty1<*, *>,
    val nodeKClass: KClass,
    val type: String,
    val direction: Direction,
    val parentKClass: KClass,
    val allowedAuthorizations: Set
) {
    /**
     * GraphQL name of the property
     */
    val graphQLName get() = property.getPropertyName(parentKClass)

    /**
     * Description of the property
     */
    val graphQLDescription get() = property.findAnnotation()?.value

    /**
     * If true, this exposes a field in the GraphQL API
     */
    val isGraphQLVisible get() = property.visibility == KVisibility.PUBLIC && !property.hasAnnotation()

    /**
     * optional setter which is used to initialize the opposite property
     * may only be present if the opposite side is a one side
     */
    private val remotePropertySetter: RemotePropertySetter? = generateRemotePropertySetter()

    /**
     * Creates the remote property setter.
     * Checks all properties on the remote node, and returns the first where
     * - the type matches
     * - the direction is opposite
     * - the property is a one property
     *
     * @return the setter if possible, otherwise null
     */
    private fun generateRemotePropertySetter(): RemotePropertySetter? {
        for (remoteProperty in nodeKClass.memberProperties) {
            val annotation = remoteProperty.findAnnotation()
            if (annotation?.type == type && annotation.direction != direction) {
                if (remoteProperty.returnType.isSubtypeOf(Node::class.createType())) {
                    return { remoteNode, value ->
                        val nodeProperty = remoteNode.getProperty(remoteProperty) as NodePropertyDelegate
                        nodeProperty.setFromRemote(value)
                    }
                }
            }
        }
        return null
    }

    /**
     * Generates a Cypher-DSL RelationshipPattern
     *
     * @param rootNode the start node of the relationship
     * @param propertyNode the related node
     * @param T the type of the generated relationship
     * @return the generated relationship pattern
     */
    fun  generateRelationship(
        rootNode: ExposesRelationships, propertyNode: org.neo4j.cypherdsl.core.Node
    ): T where T : RelationshipPattern, T : ExposesPatternLengthAccessors<*> {
        return when (direction) {
            Direction.OUTGOING -> rootNode.relationshipTo(propertyNode, type)
            Direction.INCOMING -> rootNode.relationshipFrom(propertyNode, type)
        }
    }

    /**
     * Called to register the result of a query
     * might initialize the foreign relation
     *
     * @param node the root node of this relationship
     * @param nodeQueryResult the result of the query, provides foreign nodes
     * @param T the type of foreign node
     */
    internal fun  registerQueryResult(node: Node, nodeQueryResult: NodeQueryResult) {
        registerLocalQueryResult(node, nodeQueryResult)
        val setter = remotePropertySetter
        if (setter != null) {
            for (remoteNode in nodeQueryResult.nodes) {
                setter(remoteNode, node)
            }
        }
    }

    /**
     * Registers the query result on the owning side
     * Should update the property of the provided instance
     *
     * @param node the node which contains the property to update
     * @param nodeQueryResult the result of the query
     * @param T the type of nodes of the result
     */
    private fun  registerLocalQueryResult(node: Node, nodeQueryResult: NodeQueryResult) {
        node.getProperty(property).registerQueryResult(nodeQueryResult)
    }

    /**
     * Gets the diff describing updates of the property
     *
     * @param node the node which contains the property to get the diff from
     * @param nodeDefinition definition of the related nodes
     * @return the diff describing added and removed nodes
     */
    internal fun getRelationshipDiff(
        node: Node,
        nodeDefinition: NodeDefinition
    ): RelationshipDiff {
        return node.getProperty(property).getRelationshipDiff(nodeDefinition)
    }

    /**
     * Validates the relationship for [Node] by calling [Node]
     */
    internal fun validate(
        node: Node,
        savingNodes: Set,
        nodeDefinitionCollection: NodeDefinitionCollection
    ) {
        node.getProperty(property).validate(savingNodes, this, nodeDefinitionCollection)
    }

    /**
     * Gets related nodes to save
     *
     * @param node the node which contains the property to get the related nodes to save
     * @return a list of nodes to save
     */
    internal fun getRelatedNodesToSave(node: Node): Collection {
        return node.getProperty(property).getRelatedNodesToSave()
    }

    /**
     * Gets related nodes defined by this property, but only those already loaded (therefore no lazy loading)
     * The relationships do not have to be persisted yet
     *
     * @param node the node which contains the property to get the loaded related nodes
     * @return the already loaded related nodes
     */
    internal fun getLoadedRelatedNodes(node: Node): Collection {
        return node.getProperty(property).getLoadedRelatedNodes()
    }

    /**
     * Generates a field definition
     *
     * @param transformationContext used to generate GraphQL types, register data fetchers, ...
     */
    internal abstract fun generateFieldDefinition(transformationContext: SchemaTransformationContext): GraphQLFieldDefinition
}

/**
 * Alias for the setter function for remote properties
 */
private typealias RemotePropertySetter = (remoteNode: Node, value: Node) -> Unit




© 2015 - 2024 Weber Informatics LLC | Privacy Policy