@finos/legend-graph
Version:
Legend graph and graph manager
358 lines • 22 kB
JavaScript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* 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 { isNonNullable, assertNonEmptyString, UnsupportedOperationError, IllegalStateError, assertNonNullable, assertTrue, LogEvent, guaranteeType, guaranteeNonNullable, } from '@finos/legend-shared';
import { Stereotype } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Stereotype.js';
import { Tag } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Tag.js';
import { Enum } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Enum.js';
import { V1_buildFullPath, } from './V1_GraphBuilderContext.js';
import { V1_buildVariable, V1_buildUnit, V1_buildTaggedValue, V1_buildFunctionSuite, } from './helpers/V1_DomainBuilderHelper.js';
import { V1_buildServiceExecution, V1_buildLegacyServiceTest, V1_buildPostValidation, V1_buildServiceOwnership, } from './helpers/V1_ServiceBuilderHelper.js';
import { V1_buildEnumerationMapping, V1_buildMappingInclude, } from './helpers/V1_MappingBuilderHelper.js';
import { V1_buildFlatDataSection } from './helpers/V1_FlatDataStoreBuilderHelper.js';
import { V1_buildSchema } from './helpers/V1_DatabaseBuilderHelper.js';
import { V1_buildConfigurationProperty, V1_buildScopeElement, } from './helpers/V1_FileGenerationBuilderHelper.js';
import { V1_buildEngineRuntime } from './helpers/V1_RuntimeBuilderHelper.js';
import { V1_buildConnection } from './helpers/V1_ConnectionBuilderHelper.js';
import { V1_ConnectionPointer } from '../../../model/packageableElements/connection/V1_ConnectionPointer.js';
import { V1_buildGenerationTreeNode, V1_buildFileGenerationPointer, } from './helpers/V1_GenerationSpecificationBuilderHelper.js';
import { V1_buildSection } from './helpers/V1_SectionBuilderHelper.js';
import { V1_buildEmbeddedData } from './helpers/V1_DataElementBuilderHelper.js';
import { V1_buildTestSuite } from './helpers/V1_TestBuilderHelper.js';
import { ServiceTestSuite } from '../../../../../../../graph/metamodel/pure/packageableElements/service/ServiceTestSuite.js';
import { V1_DataElementReference } from '../../../model/data/V1_EmbeddedData.js';
import { V1_buildFunctionSignature } from '../../../helpers/V1_DomainHelper.js';
import { getFunctionName } from '../../../../../../../graph/helpers/DomainHelper.js';
import { GraphBuilderError } from '../../../../../../GraphManagerUtils.js';
import { PostValidation } from '../../../../../../../graph/metamodel/pure/packageableElements/service/PostValidation.js';
import { V1_buildExecutionParameters } from './V1_ExecutionEnvironmentBuilderHelper.js';
import { PackageableElementExplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/PackageableElementReference.js';
import { generateFunctionPrettyName } from '../../../../../../../graph/helpers/PureLanguageHelper.js';
import { V1_MappingIncludeMapping, } from '../../../model/packageableElements/mapping/V1_MappingInclude.js';
import { V1_INTERNAL__UnknownMappingInclude } from '../../../model/packageableElements/mapping/V1_INTERNAL__UnknownMappingInclude.js';
import { V1_buildHostedServiceDeploymentConfiguration, V1_buildSnowflakeAppDeploymentConfiguration, V1_buildDeploymentOwnership, V1_builHostedServiceOwnership, V1_buildHostedServiceActions, } from './helpers/V1_FunctionActivatorBuilderHelper.js';
import {} from '../../../model/packageableElements/dataProduct/V1_DataProduct.js';
import { AccessPointGroup } from '../../../../../../../graph/metamodel/pure/dataProduct/DataProduct.js';
import { V1_buildAccessPoint } from './helpers/V1_DataProductBuilder.js';
export class V1_ElementSecondPassBuilder {
context;
constructor(context) {
this.context = context;
}
visit_PackageableElement(element) {
this.context.extensions
.getExtraBuilderOrThrow(element)
.runSecondPass(element, this.context);
}
visit_INTERNAL__UnknownElement(element) {
throw new UnsupportedOperationError();
}
visit_INTERNAL__UnknownPackageableElement(element) {
throw new UnsupportedOperationError();
}
visit_IngestDefinition(element) {
throw new UnsupportedOperationError();
}
visit_INTERNAL__UnknownFunctionActivator(element) {
const metamodel = this.context.currentSubGraph.getOwnFunctionActivator(V1_buildFullPath(element.package, element.name));
metamodel.function = PackageableElementExplicitReference.create(guaranteeNonNullable(this.context.graph.functions.find((fn) => generateFunctionPrettyName(fn, {
fullPath: true,
spacing: false,
}) === element.function.path.replaceAll(/\s*/gu, ''))));
}
visit_SnowflakeApp(element) {
const metamodel = this.context.currentSubGraph.getOwnFunctionActivator(V1_buildFullPath(element.package, element.name));
metamodel.function = PackageableElementExplicitReference.create(guaranteeNonNullable(this.context.graph.functions.find((fn) => generateFunctionPrettyName(fn, {
fullPath: true,
spacing: false,
notIncludeParamName: true,
}) === element.function.path.replaceAll(/\s*/gu, ''))));
metamodel.ownership = V1_buildDeploymentOwnership(element.ownership, metamodel);
metamodel.activationConfiguration =
V1_buildSnowflakeAppDeploymentConfiguration(element.activationConfiguration, this.context);
metamodel.stereotypes = element.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
metamodel.taggedValues = element.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
}
visit_HostedService(element) {
assertNonEmptyString(element.pattern, `Hosted Service 'pattern' field is missing or empty`);
const metamodel = this.context.currentSubGraph.getOwnFunctionActivator(V1_buildFullPath(element.package, element.name));
metamodel.function = PackageableElementExplicitReference.create(guaranteeNonNullable(this.context.graph.functions.find((fn) => generateFunctionPrettyName(fn, {
fullPath: true,
spacing: false,
notIncludeParamName: true,
}) === element.function.path.replaceAll(/\s*/gu, ''))));
metamodel.ownership = V1_builHostedServiceOwnership(element.ownership, metamodel);
if (element.activationConfiguration) {
metamodel.activationConfiguration =
V1_buildHostedServiceDeploymentConfiguration(element.activationConfiguration);
}
metamodel.stereotypes = element.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
metamodel.taggedValues = element.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
metamodel.actions = element.actions
.map((action) => V1_buildHostedServiceActions(action, this.context))
.filter(isNonNullable);
}
visit_INTERNAL__UnknownStore(element) {
return;
}
visit_Profile(element) {
const profile = this.context.currentSubGraph.getOwnProfile(V1_buildFullPath(element.package, element.name));
const uniqueStereotypes = new Set();
profile.p_stereotypes = element.stereotypes.map((stereotypeV) => {
const stereotype = stereotypeV.value;
if (uniqueStereotypes.has(stereotype)) {
const message = `Found duplicated stereotype '${stereotype}' in profile '${element.path}'`;
/**
* In strict-mode, graph builder will consider this as an error
* See https://github.com/finos/legend-studio/issues/941
*
* @discrepancy graph-building
*/
if (this.context.options?.strict) {
throw new GraphBuilderError(message);
}
this.context.logService.warn(LogEvent.create(message));
}
uniqueStereotypes.add(stereotype);
return new Stereotype(profile, stereotype);
});
const uniqueTags = new Set();
profile.p_tags = element.tags.map((tagV) => {
const tag = tagV.value;
if (uniqueTags.has(tag)) {
const message = `Found duplicated tag '${tag}' in profile '${element.path}'`;
/**
* In strict-mode, graph builder will consider this as an error
* See https://github.com/finos/legend-studio/issues/941
*
* @discrepancy graph-building
*/
if (this.context.options?.strict) {
throw new GraphBuilderError(message);
}
this.context.logService.warn(LogEvent.create(message));
}
uniqueTags.add(tag);
return new Tag(profile, tag);
});
}
visit_Enumeration(element) {
const enumeration = this.context.currentSubGraph.getOwnEnumeration(V1_buildFullPath(element.package, element.name));
enumeration.stereotypes = element.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
enumeration.taggedValues = element.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
const uniqueEnumValues = new Set();
enumeration.values = element.values.map((enumValue) => {
assertNonEmptyString(enumValue.value, `Enum value 'value' field is missing or empty`);
if (uniqueEnumValues.has(enumValue.value)) {
const message = `Found duplicated value '${enumValue.value}' in enumeration '${enumeration.path}'`;
/**
* In strict-mode, graph builder will consider this as an error
* See https://github.com/finos/legend-studio/issues/941
*
* @discrepancy graph-building
*/
if (this.context.options?.strict) {
throw new GraphBuilderError(message);
}
this.context.logService.warn(LogEvent.create(message));
}
const _enum = new Enum(enumValue.value, enumeration);
_enum.stereotypes = enumValue.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
_enum.taggedValues = enumValue.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
uniqueEnumValues.add(enumValue.value);
return _enum;
});
}
visit_Measure(element) {
assertNonNullable(element.canonicalUnit, `Measure 'canonicalUnit' field is missing`);
const measure = this.context.currentSubGraph.getOwnMeasure(V1_buildFullPath(element.package, element.name));
measure.canonicalUnit = V1_buildUnit(element.canonicalUnit, measure, this.context.graph, this.context);
measure.nonCanonicalUnits = element.nonCanonicalUnits.map((unit) => V1_buildUnit(unit, measure, this.context.currentSubGraph, this.context));
}
visit_Class(element) {
const _class = this.context.currentSubGraph.getOwnClass(V1_buildFullPath(element.package, element.name));
_class.stereotypes = element.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
_class.taggedValues = element.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
}
visit_Association(element) {
throw new UnsupportedOperationError();
}
// NOTE: Alloy Execution server has 2 passes for processing as it needs to build the lambda and compiles
// but we currently do not build the lambda and so this needs only one pass
visit_ConcreteFunctionDefinition(protocol) {
assertNonNullable(protocol.returnGenericType, `Function 'returnGenericType' field is missing or empty`);
assertNonNullable(protocol.returnMultiplicity, `Function 'returnMultiplicity' field is missing`);
const func = this.context.currentSubGraph.getOwnFunction(V1_buildFullPath(protocol.package, V1_buildFunctionSignature(protocol)));
func.returnType = this.context.resolveGenericTypeFromProtocol(protocol.returnGenericType);
func.returnMultiplicity = this.context.graph.getMultiplicity(protocol.returnMultiplicity.lowerBound, protocol.returnMultiplicity.upperBound);
func.stereotypes = protocol.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
func.taggedValues = protocol.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
func.parameters = protocol.parameters.map((param) => V1_buildVariable(param, this.context));
func.expressionSequence = protocol.body;
func.functionName = getFunctionName(func, func.name);
if (protocol.tests?.length) {
func.tests = protocol.tests.map((l) => V1_buildFunctionSuite(l, this.context));
}
}
visit_FlatData(element) {
const flatData = this.context.currentSubGraph.getOwnFlatDataStore(V1_buildFullPath(element.package, element.name));
flatData.sections = element.sections.map((section) => V1_buildFlatDataSection(section, flatData, this.context));
}
visit_Database(element) {
const database = this.context.currentSubGraph.getOwnDatabase(V1_buildFullPath(element.package, element.name));
database.includes = element.includedStores.map((includedStore) => this.context.resolveDatabase(includedStore.path));
database.schemas = element.schemas.map((schema) => V1_buildSchema(schema, database, this.context));
}
getMappingIdentifier(protocol) {
if (protocol instanceof V1_INTERNAL__UnknownMappingInclude) {
return undefined;
}
if (protocol instanceof V1_MappingIncludeMapping) {
return protocol.includedMapping;
}
const extraIncludeMappingIdentifierBuilders = this.context.extensions.plugins.flatMap((plugin) => plugin.V1_getExtraMappingIncludeIdentifierBuilders?.() ?? []);
for (const builder of extraIncludeMappingIdentifierBuilders) {
const builtIdentifier = builder(protocol);
if (builtIdentifier) {
return builtIdentifier;
}
}
throw new UnsupportedOperationError(`Can't build mapping include identifier: no compatible builder available from plugins`, protocol);
}
visit_Mapping(element) {
const mapping = this.context.currentSubGraph.getOwnMapping(V1_buildFullPath(element.package, element.name));
const mappingIncludesSet = new Set();
mapping.includes = element.includedMappings.map((mappingInclude) => {
const includedMappingIdentifier = this.getMappingIdentifier(mappingInclude);
if (includedMappingIdentifier) {
assertTrue(!mappingIncludesSet.has(includedMappingIdentifier), `Duplicated mapping include '${includedMappingIdentifier}' in mapping '${mapping.path}'`);
mappingIncludesSet.add(includedMappingIdentifier);
}
return V1_buildMappingInclude(mappingInclude, this.context, mapping);
});
mapping.enumerationMappings = element.enumerationMappings.map((enumerationMapping) => V1_buildEnumerationMapping(enumerationMapping, this.context, mapping));
}
visit_Service(element) {
assertNonEmptyString(element.pattern, `Service 'pattern' field is missing or empty`);
const service = this.context.currentSubGraph.getOwnService(V1_buildFullPath(element.package, element.name));
service.stereotypes = element.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
service.taggedValues = element.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
service.pattern = element.pattern;
service.owners = element.owners.slice();
service.documentation = element.documentation;
service.autoActivateUpdates = element.autoActivateUpdates;
if (element.ownership) {
service.ownership = V1_buildServiceOwnership(element.ownership, service);
}
// NOTE: process execution before the test, so we can do some check between test and execution (such matching type, keys, etc.)
service.execution = V1_buildServiceExecution(element.execution, this.context, service);
if (element.test) {
service.test = V1_buildLegacyServiceTest(element.test, this.context, service);
}
service.tests = element.testSuites
.map((testSuite) => V1_buildTestSuite(service, testSuite, this.context))
.map((e) => guaranteeType(e, ServiceTestSuite));
service.postValidations = element.postValidations
.map((postValidation) => V1_buildPostValidation(postValidation, this.context))
.map((e) => guaranteeType(e, PostValidation));
}
visit_SectionIndex(element) {
const sectionIndex = this.context.currentSubGraph.getOwnSectionIndex(element.path);
sectionIndex.sections = element.sections.map((section) => V1_buildSection(section, this.context, sectionIndex));
}
visit_FileGeneration(element) {
assertNonEmptyString(element.type, `File generation 'type' field is missing or empty`);
const fileGeneration = this.context.currentSubGraph.getOwnFileGeneration(V1_buildFullPath(element.package, element.name));
fileGeneration.type = element.type;
fileGeneration.configurationProperties =
element.configurationProperties.map(V1_buildConfigurationProperty);
fileGeneration.generationOutputPath = element.generationOutputPath;
fileGeneration.scopeElements = element.scopeElements.map((scopeElement) => V1_buildScopeElement(scopeElement, this.context));
}
visit_GenerationSpecification(element) {
const generationSpec = this.context.currentSubGraph.getOwnGenerationSpecification(V1_buildFullPath(element.package, element.name));
generationSpec.generationNodes = element.generationNodes.map((node) => V1_buildGenerationTreeNode(node, this.context));
generationSpec.fileGenerations = element.fileGenerations.map((node) => V1_buildFileGenerationPointer(node, this.context));
}
visit_PackageableRuntime(element) {
const runtime = this.context.currentSubGraph.getOwnRuntime(V1_buildFullPath(element.package, element.name));
runtime.runtimeValue = V1_buildEngineRuntime(element.runtimeValue, this.context);
}
visit_PackageableConnection(element) {
const connection = this.context.currentSubGraph.getOwnConnection(V1_buildFullPath(element.package, element.name));
if (element.connectionValue instanceof V1_ConnectionPointer) {
throw new IllegalStateError('Packageable connection value cannot be a connection pointer');
}
connection.connectionValue = V1_buildConnection(element.connectionValue, this.context);
}
visit_DataElement(element) {
const dataElement = this.context.currentSubGraph.getOwnDataElement(V1_buildFullPath(element.package, element.name));
dataElement.stereotypes = element.stereotypes
.map((stereotype) => this.context.resolveStereotype(stereotype))
.filter(isNonNullable);
dataElement.taggedValues = element.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context))
.filter(isNonNullable);
if (element.data instanceof V1_DataElementReference) {
throw new IllegalStateError('Cannot use Data element reference in a Data element');
}
dataElement.data = V1_buildEmbeddedData(element.data, this.context);
}
visit_ExecutionEnvironmentInstance(element) {
const execEnv = this.context.currentSubGraph.getOwnExecutionEnvironment(V1_buildFullPath(element.package, element.name));
execEnv.executionParameters = element.executionParameters.map((e) => V1_buildExecutionParameters(e, this.context));
}
visit_DataProduct(element) {
const dataProduct = this.context.currentSubGraph.getOwnDataProduct(V1_buildFullPath(element.package, element.name));
dataProduct.title = element.title;
dataProduct.description = element.description;
dataProduct.accessPointGroups = element.accessPointGroups.map((elementGroup) => {
const group = new AccessPointGroup();
group.id = elementGroup.id;
group.description = elementGroup.description;
group.accessPoints = elementGroup.accessPoints.map((ep) => V1_buildAccessPoint(ep, this.context));
return group;
});
}
}
//# sourceMappingURL=V1_ElementSecondPassBuilder.js.map