io.github.graphglue.model.Node.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of graphglue-core Show documentation
Show all versions of graphglue-core Show documentation
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.model
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
import com.expediagroup.graphql.generator.annotations.GraphQLName
import com.expediagroup.graphql.generator.scalars.ID
import graphql.schema.DataFetchingEnvironment
import io.github.graphglue.data.LazyLoadingContext
import io.github.graphglue.data.execution.NodeQuery
import io.github.graphglue.data.execution.NodeQueryOptions
import io.github.graphglue.data.execution.NodeQueryResult
import io.github.graphglue.graphql.extensions.requiredPermission
import io.github.graphglue.model.property.BasePropertyDelegate
import io.github.graphglue.model.property.NodePropertyDelegate
import io.github.graphglue.model.property.NodeSetPropertyDelegate
import org.springframework.data.annotation.Transient
import org.springframework.data.neo4j.core.schema.GeneratedValue
import org.springframework.data.neo4j.core.schema.Id
import kotlin.properties.PropertyDelegateProvider
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
/**
* Name of the bean used as id generator for [Node]s
*/
const val NODE_ID_GENERATOR_BEAN = "nodeIdGenerator"
/**
* Base class for all Nodes
* This is always added to the schema
* All domain entities which can be retrieved via the api
* and should be persisted in the database should inherit from this class
* Two nodes are equal iff they have the same id, or, if no id is present yet on both, if they are the same object.
*/
@DomainNode
@AdditionalFilter("idIdFilter")
@GraphQLDescription("Base class of all nodes")
abstract class Node {
/**
* Id of this node, `null` if not persisted in the database yet
*/
@Id
@GeneratedValue(generatorRef = NODE_ID_GENERATOR_BEAN)
internal var id: String? = null
/**
* Readonly wrapper for the id
* If `null`, the node has not been persisted in the database yet
*/
@GraphQLIgnore
val rawId
get() = id
/**
* The id of the node as seen in the GraphQL API
* @throws Exception if this node has not been persisted yet and therefore has no id
*/
@GraphQLName("id")
@GraphQLDescription("The unique id of this node")
val graphQLId: ID
get() = ID(id!!)
/**
* Context necessary for lazy-loading
*/
@Transient
internal var lazyLoadingContext: LazyLoadingContext? = null
/**
* Lookup for all node properties
* Trades memory (additional HashMap) for a cleaner and more extensible way to lookup the delegates
* (compared to reflection)
* Name of property as key
*/
@Transient
internal val propertyLookup: MutableMap> = mutableMapOf()
/**
* Cached fetched values for extension fields by resultKey
* Caution: null values should be omitted
*/
@Transient
internal var extensionFields: MutableMap? = null
/**
* Order fields for sorting and cursor generation fetched from the database
*/
@Transient
internal var orderFields: MutableMap? = null
/**
* Creates a new node property used for many sides
*
* @param T value type
* @return a provider for the property delegate
*/
protected fun NodeSetProperty(): PropertyDelegateProvider> {
return NodeSetPropertyProvider()
}
/**
* Creates a new node property used for one sides
*
* @param T value type
* @return a provider for the property delegate
*/
protected fun NodeProperty(): PropertyDelegateProvider> {
return NodePropertyProvider()
}
/**
* Loads all nodes of a relationship
* If the `dataFetchingEnvironment` is provided, required nested nodes are loaded too
*
* @param property defines the relation to load the nodes of
* @param dataFetchingEnvironment if provided used to define nested nodes to load
* @return the result of the query and the query itself
*/
internal suspend fun loadNodesOfRelationship(
property: KProperty1<*, *>,
dataFetchingEnvironment: DataFetchingEnvironment? = null
): Pair, NodeQuery?> {
val lazyLoadingContext = lazyLoadingContext
if (lazyLoadingContext == null) {
return NodeQueryResult(NodeQueryOptions(), emptyList(), null) to null
} else {
val queryParser = lazyLoadingContext.nodeQueryParser
val parentNodeDefinition = queryParser.nodeDefinitionCollection.getNodeDefinition(this::class)
val relationshipDefinition = parentNodeDefinition.getRelationshipDefinitionOfProperty(property)
val nodeDefinition = queryParser.nodeDefinitionCollection.getNodeDefinition(
relationshipDefinition.nodeKClass
)
val query = queryParser.generateRelationshipNodeQuery(
nodeDefinition,
parentNodeDefinition,
dataFetchingEnvironment,
relationshipDefinition,
this,
dataFetchingEnvironment?.requiredPermission
)
@Suppress("UNCHECKED_CAST")
return lazyLoadingContext.nodeQueryEngine.execute(query) as NodeQueryResult to query
}
}
/**
* Gets a node property from the lookup
* May be changed in future to support extensibility
*
* @param property the property to lookup
* @return the found property
*/
@Suppress("UNCHECKED_CAST")
internal fun getProperty(property: KProperty<*>): BasePropertyDelegate {
return propertyLookup[property.name]!! as BasePropertyDelegate
}
final override fun equals(other: Any?): Boolean {
return if (other !is Node) {
false
} else if (this === other) {
true
} else {
this.rawId != null && this.rawId == other.rawId
}
}
final override fun hashCode(): Int {
return if (rawId != null) {
rawId.hashCode()
} else {
super.hashCode()
}
}
override fun toString(): String {
return "${this::class.simpleName}(id=$id)"
}
}
/**
* Provider for [NodePropertyDelegate]s
*/
private class NodePropertyProvider : PropertyDelegateProvider> {
/**
* Creates a new [NodePropertyDelegate] and registers it to the [Node.propertyLookup]
*
* @param thisRef the parent node
* @param property the property to delegate
* @return the generated property delegate
*/
override operator fun provideDelegate(thisRef: Node, property: KProperty<*>): NodePropertyDelegate {
val nodeProperty = NodePropertyDelegate(
thisRef,
property as KProperty1<*, *>
)
thisRef.propertyLookup[property.name] = nodeProperty
return nodeProperty
}
}
/**
* Provider for [NodeSetPropertyDelegate]s
*/
private class NodeSetPropertyProvider : PropertyDelegateProvider> {
/**
* Creates a new [NodeSetPropertyDelegate] and registers it to the [Node.propertyLookup]
*
* @param thisRef the parent node
* @param property the property to delegate
* @return the generated property delegate
*/
override operator fun provideDelegate(thisRef: Node, property: KProperty<*>): NodeSetPropertyDelegate {
val nodeSetPropertyDelegate = NodeSetPropertyDelegate(
thisRef,
property as KProperty1<*, *>
)
thisRef.propertyLookup[property.name] = nodeSetPropertyDelegate
return nodeSetPropertyDelegate
}
}