src/metadata/GraphBuilder.ps1
# Copyright 2020, Adam Edwards # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. . (import-script GraphDataModel) . (import-script EntityEdge) . (import-script EntityVertex) enum BuildFlags { NavigationsProcessed = 1 MethodsProcessed = 2 CopiedToSingleton = 4 } ScriptClass GraphBuilder { $graphEndpoint = $null $version = $null $dataModel = $null static { $AllBuildFlags = ([BuildFlags]::NavigationsProcessed) -bOR ([BuildFlags]::MethodsProcessed) -bOR ([BuildFlags]::CopiedToSingleton) } function __initialize($graphEndpoint, $version, $dataModel) { $this.graphEndpoint = $graphEndpoint $this.version = $version $this.dataModel = $dataModel } function InitializeGraph($graph) { $metadataActivity = "Building graph version '$($this.version)' for endpoint '$($this.graphEndpoint)'" Write-Progress -id 1 -activity $metadataActivity __AddRootVertices $graph } function __AddEntityTypeVertex($graph, $typeName) { AddEntityTypeVertices $graph $typeName } function __AddRootVertices($graph) { $singletons = $this.dataModel |=> GetSingletons __AddVerticesFromSchemas $graph $singletons Singleton $entitySets = $this.dataModel |=> GetEntitySets __AddVerticesFromSchemas $graph $entitySets EntitySet } function __AddVerticesFromSchemas($graph, $schemas, $vertexType) { $schemas | foreach { if ( ! $_.namespace ) { throw "No namespace specified for schema '$($_.QualifiedName)' of vertex type '$vertexType'" } __AddVertex $graph $_.Schema $vertexType $_.namespace } } function __AddVertex($graph, $schema, $vertexType, $namespace, $namespaceAlias) { $entity = new-so Entity $schema $namespace $graph |=> AddVertex $entity } function AddEntityTypeVertices($graph, $qualifiedTypeName) { $entityType = $this.dataModel |=> GetEntityTypeByName $qualifiedTypeName $nameInfo = __GetNamespaceInfoFromQualifiedTypeName $qualifiedTypeName if ( $qualifiedTypeName -and $entityType -eq $null ) { throw "Type '$qualifiedTypeName' does not exist in the schema for the graph at endpoint '$($graph.endpoint)' with API version '$($graph.apiversion)'" } Write-Progress -id 1 -activity "Adding type '$qualifiedTypeName'" __AddVerticesFromSchemas $graph $entityType EntityType } function __AddEdgesToEntityTypeVertex($graph, $sourceVertex) { if ( $sourceVertex.TestFlags([BuildFlags]::NavigationsProcessed) ) { return } $transitions = if ( $sourceVertex.entity.navigations ) { $sourceVertex.entity.navigations } else { @() } foreach ( $transition in $transitions ) { # Look for the existing type in the graph itself $unaliasedName = $this.dataModel |=> UnAliasQualifiedName $transition.typedata.typename $sink = $graph |=> TypeVertexFromTypeName $unaliasedName if ( ! $sink ) { # If we don't find the existing type, try to get it from the model instead $sinkSchema = $this.dataModel |=> GetEntityTypeByName $unAliasedName if ( $sinkSchema ) { # We've found the type, now add it to the graph __AddEntityTypeVertex $graph $unaliasedName $sink = $graph |=> TypeVertexFromTypeName $unaliasedName } else { write-verbose "Unable to find schema for '$($transition.type)', $($transition.typedata.typename)" } } if ( $sink ) { $edge = new-so EntityEdge $sourceVertex $sink $transition $sourceVertex |=> AddEdge $edge } else { write-verbose "Unable to find entity type for '$($transition.type)', $($transition.typedata.typename) = '$unaliasedName', skipping" } } $sourceVertex.SetFlags([BuildFlags]::NavigationsProcessed) } function AddEdgesToVertex($graph, $vertex, $skipIfExist) { if ( $vertex.TestFlags($::.GraphBuilder.AllBuildFlags) ) { if ( !$skipIfExist ) { throw "Vertex '$($vertex.name)' already has edges" } return } $qualifiedTypeName = $vertex.entity.typedata.typename $unqualifiedTypeName = $this.dataModel |=> UnqualifyTypeName $qualifiedTypeName Write-Progress -id 1 -activity "Adding edges for '$($vertex.name)'" __AddEdgesToEntityTypeVertex $graph $vertex if ( $vertex.entity.type -ne 'Singleton' ) { __AddMethodTransitionsToVertex $graph $vertex } else { __CopyEntityTypeEdgesToSingletonVertex $graph $vertex } } function __CopyEntityTypeEdgesToSingletonVertex($graph, $source) { if ( $source.TestFlags([BuildFlags]::CopiedToSingleton) ) { throw "Data from type already copied to singleton '$($source.name)'" } $entityName = ($source.entity.typeData).TypeName $typeVertex = $graph |=> TypeVertexFromTypeName $entityName if ( $typeVertex -eq $null ) { throw "Unable to find an entity type for singleton '$($_.name)' and '$entityName'" } AddEdgesToVertex $graph $typeVertex $true $edges = $typeVertex.outgoingEdges.values | foreach { if ( ( $_ | gm transition ) -ne $null ) { $_ } } $edges | foreach { $sink = $_.sink $transition = $_.transition $edge = new-so EntityEdge $source $sink $transition $source |=> AddEdge $edge } $source.SetFlags([BuildFlags]::CopiedToSingleton) } function __AddMethodTransitionsToVertex($graph, $sourceVertex) { if ( $sourceVertex.TestFlags([BuildFlags]::MethodsProcessed) ) { write-verbose "Methods already processed for $($sourceVertex.name), skipping method addition" return } $sourceTypeName = $sourceVertex.entity.typeData.TypeName $methods = $this.dataModel |=> GetMethodBindingsForType $sourceTypeName if ( ! $methods ) { write-verbose "Vertex ($sourceVertex.name) has no methods, skipping method addition" return } $methods | foreach { $method = $_ $sink = if ( $method | gm ReturnType ) { $typeName = $method.ReturnType | select -expandproperty Type $typeVertex = $graph |=> TypeVertexFromTypeName $typeName if ( $typeVertex -eq $null ) { try { __AddEntityTypeVertex $graph $typeName $typeVertex = $graph |=> TypeVertexFromTypeName $typeName } catch { # Possibly an enumeration type, this will just be considered a scalar } } if ( $typeVertex ) { $typeVertex } else { write-verbose "Type $($typeName) returned by $($method.name) cannot be found, configuring Scalar vertex" $::.EntityVertex.ScalarVertex } } else { $::.Entityvertex.NullVertex } __AddMethod $sourceVertex $method $sink } $sourceVertex.SetFlags([BuildFlags]::MethodsProcessed) } function __AddMethod($targetVertex, $methodSchema, $returnTypeVertex) { if ( ! ($targetVertex |=> EdgeExists($methodSchema.name)) ) { $nameInfo = __GetNamespaceInfoFromQualifiedTypeName $targetVertex.typeName $methodEntity = new-so Entity $methodSchema $nameInfo.Namespace $edge = new-so EntityEdge $targetVertex $returnTypeVertex $methodEntity $targetVertex |=> AddEdge $edge } else { write-verbose "Skipped add of edge $($methodSchema.name) to $($returnTypeVertex.id) from vertex $($targetVertex.id) because it already exists." } } function __GetNamespaceInfoFromQualifiedTypeName($qualifiedTypeName) { $nameInfo = $this.dataModel |=> ParseTypeName $qualifiedTypeName $true [PSCustomObject] @{ Namespace = $nameInfo.Namespace NamespaceAlias = $this.dataModel |=> GetNamespaceAlias $nameInfo.namespace } } } |