@finos/legend-graph
Version:
Legend graph and graph manager
987 lines (947 loc) • 37.7 kB
text/typescript
/**
* 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 {
type Clazz,
UnsupportedOperationError,
getClass,
IllegalStateError,
returnUndefOnError,
promisify,
filterByType,
guaranteeNonNullable,
guaranteeType,
isNonNullable,
isType,
} from '@finos/legend-shared';
import { Package } from '../graph/metamodel/pure/packageableElements/domain/Package.js';
import { Type } from '../graph/metamodel/pure/packageableElements/domain/Type.js';
import { Association } from '../graph/metamodel/pure/packageableElements/domain/Association.js';
import { Mapping } from '../graph/metamodel/pure/packageableElements/mapping/Mapping.js';
import { Class } from '../graph/metamodel/pure/packageableElements/domain/Class.js';
import { Enumeration } from '../graph/metamodel/pure/packageableElements/domain/Enumeration.js';
import { PackageableElement } from '../graph/metamodel/pure/packageableElements/PackageableElement.js';
import { Profile } from '../graph/metamodel/pure/packageableElements/domain/Profile.js';
import { Service } from '../graph/metamodel/pure/packageableElements/service/Service.js';
import { ConcreteFunctionDefinition } from './metamodel/pure/packageableElements/function/ConcreteFunctionDefinition.js';
import { Store } from '../graph/metamodel/pure/packageableElements/store/Store.js';
import { FlatData } from '../graph/metamodel/pure/packageableElements/store/flatData/model/FlatData.js';
import { PackageableRuntime } from '../graph/metamodel/pure/packageableElements/runtime/PackageableRuntime.js';
import { PackageableConnection } from '../graph/metamodel/pure/packageableElements/connection/PackageableConnection.js';
import { FileGenerationSpecification } from '../graph/metamodel/pure/packageableElements/fileGeneration/FileGenerationSpecification.js';
import { GenerationSpecification } from '../graph/metamodel/pure/packageableElements/generationSpecification/GenerationSpecification.js';
import {
Unit,
Measure,
} from '../graph/metamodel/pure/packageableElements/domain/Measure.js';
import { Database } from '../graph/metamodel/pure/packageableElements/store/relational/model/Database.js';
import { SectionIndex } from '../graph/metamodel/pure/packageableElements/section/SectionIndex.js';
import type { Section } from '../graph/metamodel/pure/packageableElements/section/Section.js';
import { PureGraphExtension } from './PureGraphExtension.js';
import { PrimitiveType } from '../graph/metamodel/pure/packageableElements/domain/PrimitiveType.js';
import { DataType } from '../graph/metamodel/pure/packageableElements/domain/DataType.js';
import {
isValidFullPath,
isValidPath,
resolvePackagePathAndElementName,
} from '../graph/MetaModelUtils.js';
import {
addElementToPackage,
deleteElementFromPackage,
getFunctionName,
getOrCreateGraphPackage,
getOrCreatePackage,
} from '../graph/helpers/DomainHelper.js';
import { DataElement } from '../graph/metamodel/pure/packageableElements/data/DataElement.js';
import type { Testable } from '../graph/metamodel/pure/test/Testable.js';
import { ExecutionEnvironmentInstance } from './metamodel/pure/packageableElements/service/ExecutionEnvironmentInstance.js';
import type { GraphDataOrigin } from './GraphDataOrigin.js';
import { INTERNAL__UnknownPackageableElement } from './metamodel/pure/packageableElements/INTERNAL__UnknownPackageableElement.js';
import { FunctionActivator } from './metamodel/pure/packageableElements/function/FunctionActivator.js';
import { INTERNAL__UnknownFunctionActivator } from './metamodel/pure/packageableElements/function/INTERNAL__UnknownFunctionActivator.js';
import { INTERNAL__UnknownStore } from './metamodel/pure/packageableElements/store/INTERNAL__UnknownStore.js';
import type { PureGraphPlugin } from './PureGraphPlugin.js';
import { INTERNAL__UnknownElement } from './metamodel/pure/packageableElements/INTERNAL__UnknownElement.js';
import { DataProduct } from './metamodel/pure/dataProduct/DataProduct.js';
import { IngestDefinition } from './metamodel/pure/packageableElements/ingest/IngestDefinition.js';
const FORBIDDEN_EXTENSION_ELEMENT_CLASS = new Set([
PackageableElement,
Type,
DataType,
PrimitiveType,
Class,
Association,
Enumeration,
ConcreteFunctionDefinition,
Profile,
Measure,
Unit,
DataElement,
Store,
Mapping,
PackageableConnection,
PackageableRuntime,
GenerationSpecification,
FileGenerationSpecification,
Service, // to be modularized
ExecutionEnvironmentInstance,
SectionIndex,
INTERNAL__UnknownPackageableElement,
INTERNAL__UnknownStore,
]);
/**
* Since this is the basis of the Pure graph itself, it shares many methods with the graph.
* But the graph holds references to many basic graphs and expose those to outside consumers
* as if it is one graph.
*
* As such, to avoid confusion, we add `Own` to methods in basic graph for methods that only
* deal with elements belonging to the basic graph.
*/
export abstract class BasicModel {
root: Package;
private _origin: GraphDataOrigin | undefined;
readonly extensions: PureGraphExtension<PackageableElement>[] = [];
readonly graphPlugins: PureGraphPlugin[] = [];
private elementSectionIndex = new Map<string, Section>();
private sectionIndicesIndex = new Map<string, SectionIndex>();
private readonly profilesIndex = new Map<string, Profile>();
private readonly typesIndex = new Map<string, Type>();
private readonly associationsIndex = new Map<string, Association>();
private readonly functionsIndex = new Map<
string,
ConcreteFunctionDefinition
>();
private readonly functionActivatorsIndex = new Map<
string,
FunctionActivator
>();
private readonly storesIndex = new Map<string, Store>();
private readonly mappingsIndex = new Map<string, Mapping>();
private readonly connectionsIndex = new Map<string, PackageableConnection>();
private readonly runtimesIndex = new Map<string, PackageableRuntime>();
private readonly servicesIndex = new Map<string, Service>();
private readonly generationSpecificationsIndex = new Map<
string,
GenerationSpecification
>();
private readonly fileGenerationsIndex = new Map<
string,
FileGenerationSpecification
>();
private readonly productsIndex = new Map<string, DataProduct>();
private readonly dataElementsIndex = new Map<string, DataElement>();
private readonly executionEnvironmentsIndex = new Map<
string,
ExecutionEnvironmentInstance
>();
private readonly INTERNAL__unknownElementsIndex = new Map<
string,
INTERNAL__UnknownPackageableElement
>();
private readonly INTERNAL__unknownIndex = new Map<
string,
INTERNAL__UnknownElement
>();
constructor(
rootPackageName: string,
graphPlugins: PureGraphPlugin[],
origin?: GraphDataOrigin | undefined,
) {
this.root = new Package(rootPackageName);
const extensionElementClasses = graphPlugins.flatMap(
(plugin) => plugin.getExtraPureGraphExtensionClasses?.() ?? [],
);
this.extensions = this.createGraphExtensions(extensionElementClasses);
this._origin = origin;
}
private createGraphExtensions(
extensionElementClasses: Clazz<PackageableElement>[],
): PureGraphExtension<PackageableElement>[] {
return extensionElementClasses.map((extensionElementClass) => {
if (FORBIDDEN_EXTENSION_ELEMENT_CLASS.has(extensionElementClass)) {
throw new IllegalStateError(
`Pure graph extension not allowed for the specified class. Consider removing this extension from plugins.`,
);
}
return new PureGraphExtension(extensionElementClass);
});
}
get ownSectionIndices(): SectionIndex[] {
return Array.from(this.sectionIndicesIndex.values());
}
get ownProfiles(): Profile[] {
return Array.from(this.profilesIndex.values());
}
get ownEnumerations(): Enumeration[] {
return Array.from(this.typesIndex.values()).filter(
filterByType(Enumeration),
);
}
get ownMeasures(): Measure[] {
return Array.from(this.typesIndex.values()).filter(filterByType(Measure));
}
get ownClasses(): Class[] {
return Array.from(this.typesIndex.values()).filter(filterByType(Class));
}
get ownTypes(): Type[] {
return Array.from(this.typesIndex.values());
}
get ownAssociations(): Association[] {
return Array.from(this.associationsIndex.values());
}
get ownFunctions(): ConcreteFunctionDefinition[] {
return Array.from(this.functionsIndex.values());
}
get ownFunctionActivators(): FunctionActivator[] {
return Array.from(this.functionActivatorsIndex.values());
}
get ownStores(): Store[] {
return Array.from(this.storesIndex.values());
}
get ownFlatDatas(): FlatData[] {
return Array.from(this.storesIndex.values()).filter(filterByType(FlatData));
}
get ownDatabases(): Database[] {
return Array.from(this.storesIndex.values()).filter(filterByType(Database));
}
get ownMappings(): Mapping[] {
return Array.from(this.mappingsIndex.values());
}
get ownServices(): Service[] {
return Array.from(this.servicesIndex.values());
}
get ownRuntimes(): PackageableRuntime[] {
return Array.from(this.runtimesIndex.values());
}
get ownConnections(): PackageableConnection[] {
return Array.from(this.connectionsIndex.values());
}
get ownFileGenerations(): FileGenerationSpecification[] {
return Array.from(this.fileGenerationsIndex.values());
}
get ownProducts(): DataProduct[] {
return Array.from(this.productsIndex.values());
}
get ownDataElements(): DataElement[] {
return Array.from(this.dataElementsIndex.values());
}
get ownExecutionEnvironments(): ExecutionEnvironmentInstance[] {
return Array.from(this.executionEnvironmentsIndex.values());
}
get ownIngests(): IngestDefinition[] {
return Array.from(this.INTERNAL__unknownElementsIndex.values()).filter(
filterByType(IngestDefinition),
);
}
get ownGenerationSpecifications(): GenerationSpecification[] {
return Array.from(this.generationSpecificationsIndex.values());
}
get ownTestables(): Testable[] {
const coreTestables = [
...this.ownServices,
...this.ownMappings,
// TODO: re-add functions once function test runner has been completed in backend
// ...this.ownFunctions,
];
const filters = this.graphPlugins.flatMap(
(plugin) => plugin.getExtraTestablesCollectors?.() ?? [],
);
const extraTestables = this.allOwnElements
.map((element) =>
filters.find((elementFilter) => Boolean(elementFilter(element)))?.(
element,
),
)
.filter(isNonNullable);
return [...coreTestables, ...extraTestables];
}
get origin(): GraphDataOrigin | undefined {
return this._origin;
}
setOrigin(val: GraphDataOrigin): void {
if (this._origin) {
throw new IllegalStateError(`Graph origin has already been set`);
} else {
this._origin = val;
}
}
getExtensionElements<T extends PackageableElement>(
extensionElementClass: Clazz<T>,
): T[] {
return this.getExtensionForElementClass(extensionElementClass).elements;
}
getExtensionForElementClass<T extends PackageableElement>(
_class: Clazz<T>,
): PureGraphExtension<T> {
const extensions = this.extensions.filter(
(extension) => extension.getElementClass() === _class,
);
if (extensions.length === 0) {
throw new UnsupportedOperationError(
`Can't find graph extension for the specified element class: no compatible graph extensions available from plugins`,
);
} else if (extensions.length > 1) {
throw new IllegalStateError(
`Found multiple extensions for the specified element class`,
);
}
return extensions[0] as PureGraphExtension<T>;
}
getOwnNullableSection = (path: string): Section | undefined =>
this.elementSectionIndex.get(path);
getOwnNullableSectionIndex = (path: string): SectionIndex | undefined =>
this.sectionIndicesIndex.get(path);
getOwnNullableProfile = (path: string): Profile | undefined =>
this.profilesIndex.get(path);
getOwnNullableType(path: string): Type | undefined {
return this.typesIndex.get(path);
}
getOwnNullableClass = (path: string): Class | undefined => {
const el = this.getOwnNullableType(path);
return el instanceof Class ? el : undefined;
};
getOwnNullableEnumeration = (path: string): Enumeration | undefined => {
const el = this.getOwnNullableType(path);
return el instanceof Enumeration ? el : undefined;
};
getOwnNullableMeasure = (path: string): Measure | undefined => {
const el = this.getOwnNullableType(path);
return el instanceof Measure ? el : undefined;
};
getOwnNullableAssociation = (path: string): Association | undefined =>
this.associationsIndex.get(path);
getOwnNullableFunction = (
path: string,
): ConcreteFunctionDefinition | undefined => this.functionsIndex.get(path);
getOwnNullableFunctionActivator = (
path: string,
): FunctionActivator | undefined => this.functionActivatorsIndex.get(path);
getOwnNullableStore = (path: string): Store | undefined =>
this.storesIndex.get(path);
getOwnNullableMapping = (path: string): Mapping | undefined =>
this.mappingsIndex.get(path);
getOwnNullableConnection = (
path: string,
): PackageableConnection | undefined => this.connectionsIndex.get(path);
getOwnNullableRuntime = (path: string): PackageableRuntime | undefined =>
this.runtimesIndex.get(path);
getOwnNullableService = (path: string): Service | undefined =>
this.servicesIndex.get(path);
getOwnNullableGenerationSpecification = (
path: string,
): GenerationSpecification | undefined =>
this.generationSpecificationsIndex.get(path);
getOwnNullableFileGeneration = (
path: string,
): FileGenerationSpecification | undefined =>
this.fileGenerationsIndex.get(path);
getOwnNullableDataElement = (path: string): DataElement | undefined =>
this.dataElementsIndex.get(path);
getOwnNullableExecutionEnviornment = (
path: string,
): ExecutionEnvironmentInstance | undefined =>
this.executionEnvironmentsIndex.get(path);
getOwnNullableProduct = (path: string): DataProduct | undefined =>
this.productsIndex.get(path);
getOwnSectionIndex = (path: string): SectionIndex =>
guaranteeNonNullable(
this.getOwnNullableSectionIndex(path),
`Can't find section index '${path}'`,
);
getOwnProfile = (path: string): Profile =>
guaranteeNonNullable(
this.getOwnNullableProfile(path),
`Can't find profile '${path}'`,
);
getOwnType = (path: string): Type =>
guaranteeNonNullable(
this.getOwnNullableType(path),
`Can't find type '${path}'`,
);
getOwnClass = (path: string): Class =>
guaranteeNonNullable(
this.getOwnNullableClass(path),
`Can't find class '${path}'`,
);
getOwnEnumeration = (path: string): Enumeration =>
guaranteeNonNullable(
this.getOwnNullableEnumeration(path),
`Can't find enumeration '${path}'`,
);
getOwnMeasure = (path: string): Measure =>
guaranteeNonNullable(
this.getOwnNullableMeasure(path),
`Can't find measure '${path}'`,
);
getOwnAssociation = (path: string): Association =>
guaranteeNonNullable(
this.getOwnNullableAssociation(path),
`Can't find association '${path}'`,
);
getOwnFunction = (path: string): ConcreteFunctionDefinition =>
guaranteeNonNullable(
this.getOwnNullableFunction(path),
`Can't find function '${path}'`,
);
getOwnFunctionActivator = (path: string): FunctionActivator =>
guaranteeNonNullable(
this.getOwnNullableFunctionActivator(path),
`Can't find function activator '${path}'`,
);
getOwnStore = (path: string): Store =>
guaranteeNonNullable(
this.getOwnNullableStore(path),
`Can't find store '${path}'`,
);
getOwnDatabase = (path: string): Database =>
guaranteeType(
this.getOwnNullableStore(path),
Database,
`Can't find database '${path}'`,
);
getOwnFlatDataStore = (path: string): FlatData =>
guaranteeType(
this.getOwnNullableStore(path),
FlatData,
`Can't find flat-data store '${path}'`,
);
getOwnMapping = (path: string): Mapping =>
guaranteeNonNullable(
this.getOwnNullableMapping(path),
`Can't find mapping '${path}'`,
);
getOwnConnection = (path: string): PackageableConnection =>
guaranteeNonNullable(
this.getOwnNullableConnection(path),
`Can't find connection '${path}'`,
);
getOwnRuntime = (path: string): PackageableRuntime =>
guaranteeNonNullable(
this.getOwnNullableRuntime(path),
`Can't find runtime '${path}'`,
);
getOwnService = (path: string): Service =>
guaranteeNonNullable(
this.getOwnNullableService(path),
`Can't find service '${path}'`,
);
getOwnGenerationSpecification = (path: string): GenerationSpecification =>
guaranteeNonNullable(
this.getOwnNullableGenerationSpecification(path),
`Can't find generation specification '${path}'`,
);
getOwnFileGeneration = (path: string): FileGenerationSpecification =>
guaranteeNonNullable(
this.getOwnNullableFileGeneration(path),
`Can't find file generation '${path}'`,
);
getOwnDataElement = (path: string): DataElement =>
guaranteeNonNullable(
this.getOwnNullableDataElement(path),
`Can't find data element '${path}'`,
);
getOwnExecutionEnvironment = (path: string): ExecutionEnvironmentInstance =>
guaranteeNonNullable(
this.getOwnNullableExecutionEnviornment(path),
`Can't find execution environment element '${path}'`,
);
getOwnDataProduct = (path: string): DataProduct =>
guaranteeNonNullable(
this.getOwnNullableProduct(path),
`Can't find data product element '${path}'`,
);
getOwnNullableExtensionElement<T extends PackageableElement>(
path: string,
extensionElementClass: Clazz<T>,
): T | undefined {
const extension = this.getExtensionForElementClass(extensionElementClass);
return extension.getElement(path);
}
setOwnSection(path: string, val: Section): void {
this.elementSectionIndex.set(path, val);
}
setOwnSectionIndex(path: string, val: SectionIndex): void {
this.sectionIndicesIndex.set(path, val);
}
setOwnProfile(path: string, val: Profile): void {
this.profilesIndex.set(path, val);
}
setOwnType(path: string, val: Type): void {
this.typesIndex.set(path, val);
}
setOwnAssociation(path: string, val: Association): void {
this.associationsIndex.set(path, val);
}
setOwnFunction(path: string, val: ConcreteFunctionDefinition): void {
this.functionsIndex.set(path, val);
}
setOwnFunctionActivator(path: string, val: FunctionActivator): void {
this.functionActivatorsIndex.set(path, val);
}
setOwnStore(path: string, val: Store): void {
this.storesIndex.set(path, val);
}
setOwnMapping(path: string, val: Mapping): void {
this.mappingsIndex.set(path, val);
}
setOwnConnection(path: string, val: PackageableConnection): void {
this.connectionsIndex.set(path, val);
}
setOwnRuntime(path: string, val: PackageableRuntime): void {
this.runtimesIndex.set(path, val);
}
setOwnService(path: string, val: Service): void {
this.servicesIndex.set(path, val);
}
setOwnGenerationSpecification(
path: string,
val: GenerationSpecification,
): void {
this.generationSpecificationsIndex.set(path, val);
}
setOwnFileGeneration(path: string, val: FileGenerationSpecification): void {
this.fileGenerationsIndex.set(path, val);
}
setOwnDataElement(path: string, val: DataElement): void {
this.dataElementsIndex.set(path, val);
}
setOwnExecutionEnvironment(
path: string,
val: ExecutionEnvironmentInstance,
): void {
this.executionEnvironmentsIndex.set(path, val);
}
setOwnDataProduct(path: string, val: DataProduct): void {
this.productsIndex.set(path, val);
}
INTERNAL__setOwnUnknown(path: string, val: INTERNAL__UnknownElement): void {
this.INTERNAL__unknownIndex.set(path, val);
}
INTERNAL__setOwnUnknownElement(
path: string,
val: INTERNAL__UnknownPackageableElement,
): void {
this.INTERNAL__unknownElementsIndex.set(path, val);
}
setOwnElementInExtension<T extends PackageableElement>(
path: string,
val: T,
extensionElementClass: Clazz<T>,
): void {
const extension = this.getExtensionForElementClass(extensionElementClass);
extension.setElement(path, val);
}
get allOwnElements(): PackageableElement[] {
return [
...this.ownProfiles,
...this.ownEnumerations,
...this.ownMeasures,
...this.ownClasses,
...this.ownAssociations,
...this.ownFunctions,
...this.ownFunctionActivators,
...this.ownStores,
...this.ownMappings,
...this.ownServices,
...this.ownRuntimes,
...this.ownConnections,
...this.ownGenerationSpecifications,
...this.ownFileGenerations,
...this.ownProducts,
...this.ownDataElements,
...this.ownExecutionEnvironments,
...Array.from(this.INTERNAL__unknownElementsIndex.values()),
...Array.from(this.INTERNAL__unknownIndex.values()),
...this.extensions.flatMap((extension) => extension.elements),
];
}
get knownAllOwnElements(): PackageableElement[] {
return this.allOwnElements.filter(
(element) => !isType(element, INTERNAL__UnknownElement),
);
}
/**
* Dispose the current graph and any potential reference from parts outside of the graph to the graph
* This is a MUST to prevent memory-leak as we might have references between metamodels from this graph
* and other graphs
*/
async dispose(): Promise<void> {
if (this.allOwnElements.length) {
await Promise.all<void>(
this.allOwnElements.map((element) =>
promisify(() => {
element.dispose();
}),
),
);
}
}
getNullablePackage = (path: string): Package | undefined =>
!path
? this.root
: returnUndefOnError(() =>
getOrCreatePackage(this.root, path, false, undefined),
);
getOwnNullableElement(
path: string,
includePackage?: boolean,
): PackageableElement | undefined {
let element: PackageableElement | undefined =
this.sectionIndicesIndex.get(path) ??
this.INTERNAL__unknownIndex.get(path) ??
this.INTERNAL__unknownElementsIndex.get(path) ??
this.typesIndex.get(path) ??
this.profilesIndex.get(path) ??
this.associationsIndex.get(path) ??
this.functionsIndex.get(path) ??
this.functionActivatorsIndex.get(path) ??
this.storesIndex.get(path) ??
this.mappingsIndex.get(path) ??
this.servicesIndex.get(path) ??
this.runtimesIndex.get(path) ??
this.connectionsIndex.get(path) ??
this.fileGenerationsIndex.get(path) ??
this.productsIndex.get(path) ??
this.dataElementsIndex.get(path) ??
this.executionEnvironmentsIndex.get(path) ??
this.generationSpecificationsIndex.get(path);
if (!element) {
for (const extension of this.extensions) {
const extensionElement = extension.getElement(path);
if (extensionElement) {
element = extensionElement;
break;
}
}
}
if (includePackage && !element) {
return this.getNullablePackage(path);
}
return element;
}
protected addOwnElement(
element: PackageableElement,
packagePath: string | undefined,
): void {
addElementToPackage(
packagePath
? getOrCreateGraphPackage(this, packagePath, undefined)
: this.root,
element,
);
if (element instanceof Mapping) {
this.setOwnMapping(element.path, element);
} else if (element instanceof Store) {
this.setOwnStore(element.path, element);
} else if (element instanceof Type) {
this.setOwnType(element.path, element);
} else if (element instanceof Association) {
this.setOwnAssociation(element.path, element);
} else if (element instanceof Profile) {
this.setOwnProfile(element.path, element);
} else if (element instanceof ConcreteFunctionDefinition) {
this.setOwnFunction(element.path, element);
} else if (element instanceof FunctionActivator) {
this.setOwnFunctionActivator(element.path, element);
} else if (element instanceof Service) {
this.setOwnService(element.path, element);
} else if (element instanceof PackageableConnection) {
this.setOwnConnection(element.path, element);
} else if (element instanceof PackageableRuntime) {
this.setOwnRuntime(element.path, element);
} else if (element instanceof FileGenerationSpecification) {
this.setOwnFileGeneration(element.path, element);
} else if (element instanceof GenerationSpecification) {
this.setOwnGenerationSpecification(element.path, element);
} else if (element instanceof DataElement) {
this.setOwnDataElement(element.path, element);
} else if (element instanceof ExecutionEnvironmentInstance) {
this.setOwnExecutionEnvironment(element.path, element);
} else if (element instanceof DataProduct) {
this.setOwnDataProduct(element.path, element);
} else if (element instanceof Package) {
// do nothing
} else {
const extension = this.getExtensionForElementClass(
getClass<PackageableElement>(element),
);
extension.setElement(element.path, element);
}
}
deleteOwnElement(element: PackageableElement): void {
if (element.package) {
deleteElementFromPackage(element.package, element);
}
if (element instanceof INTERNAL__UnknownElement) {
this.INTERNAL__unknownIndex.delete(element.path);
} else if (element instanceof INTERNAL__UnknownPackageableElement) {
this.INTERNAL__unknownElementsIndex.delete(element.path);
} else if (element instanceof Mapping) {
this.mappingsIndex.delete(element.path);
} else if (element instanceof Store) {
this.storesIndex.delete(element.path);
} else if (element instanceof Type) {
this.typesIndex.delete(element.path);
if (element instanceof Measure) {
if (element.canonicalUnit) {
this.typesIndex.delete(element.canonicalUnit.path);
}
element.nonCanonicalUnits.forEach((unit) =>
this.typesIndex.delete(unit.path),
);
}
} else if (element instanceof Association) {
this.associationsIndex.delete(element.path);
} else if (element instanceof Profile) {
this.profilesIndex.delete(element.path);
} else if (element instanceof ConcreteFunctionDefinition) {
this.functionsIndex.delete(element.path);
} else if (element instanceof FunctionActivator) {
this.functionActivatorsIndex.delete(element.path);
} else if (element instanceof Service) {
this.servicesIndex.delete(element.path);
} else if (element instanceof PackageableRuntime) {
this.runtimesIndex.delete(element.path);
} else if (element instanceof PackageableConnection) {
this.connectionsIndex.delete(element.path);
} else if (element instanceof FileGenerationSpecification) {
this.fileGenerationsIndex.delete(element.path);
} else if (element instanceof GenerationSpecification) {
this.generationSpecificationsIndex.delete(element.path);
} else if (element instanceof DataElement) {
this.dataElementsIndex.delete(element.path);
} else if (element instanceof ExecutionEnvironmentInstance) {
this.executionEnvironmentsIndex.delete(element.path);
} else if (element instanceof DataProduct) {
this.productsIndex.delete(element.path);
} else if (element instanceof Package) {
element.children.forEach((child) => this.deleteOwnElement(child));
} else {
const extension = this.getExtensionForElementClass(
getClass<PackageableElement>(element),
);
extension.deleteElement(element.path);
}
}
/**
* NOTE: this method has to do with graph modification
* and as of the current set up, we should not allow it to be called
* on any immutable graphs. As such, we protect it and let the main graph
* expose it. The other benefit of exposing this in the main graph is that we could
* do better duplicated path check
*/
protected renameOwnElement(
element: PackageableElement,
oldPath: string,
newPath: string,
): void {
// validation before renaming
if (oldPath === newPath) {
return;
}
if (!newPath) {
throw new UnsupportedOperationError(
`Can't rename element '${oldPath} to '${newPath}': path is empty'`,
);
}
if (
(element instanceof Package && !isValidPath(newPath)) ||
(!(element instanceof Package) && !isValidFullPath(newPath))
) {
throw new UnsupportedOperationError(
`Can't rename element '${oldPath} to '${newPath}': invalid path'`,
);
}
const existingElement = this.getOwnNullableElement(newPath, true);
if (existingElement) {
throw new UnsupportedOperationError(
`Can't rename element '${oldPath} to '${newPath}': another element with the same path already existed'`,
);
}
const [newPackagePath, newElementName] =
resolvePackagePathAndElementName(newPath);
// if we're not renaming package, we can simply add new package
// if the element new package does not exist. For renaming package
// it's a little bit more complicated as we need to rename its children
// we will handle this case later
if (!(element instanceof Package)) {
const newParentPackage =
this.getNullablePackage(newPackagePath) ??
(newPackagePath !== ''
? getOrCreateGraphPackage(this, newPackagePath, undefined)
: this.root);
// update element name
element.name = newElementName;
if (element instanceof ConcreteFunctionDefinition) {
element.functionName = getFunctionName(element, element.name);
}
// update element package if needed
if (element.package !== newParentPackage) {
if (element.package) {
deleteElementFromPackage(element.package, element);
}
addElementToPackage(newParentPackage, element);
}
}
// update index in the graph
if (element instanceof INTERNAL__UnknownElement) {
this.INTERNAL__unknownIndex.delete(oldPath);
this.INTERNAL__unknownIndex.set(newPath, element);
element.content = {
...element.content,
name: element.name,
package: element.package?.path,
};
} else if (element instanceof INTERNAL__UnknownPackageableElement) {
this.INTERNAL__unknownElementsIndex.delete(oldPath);
this.INTERNAL__unknownElementsIndex.set(newPath, element);
element.content = {
...element.content,
name: element.name,
package: element.package?.path,
};
} else if (element instanceof INTERNAL__UnknownFunctionActivator) {
this.functionActivatorsIndex.delete(oldPath);
this.functionActivatorsIndex.set(newPath, element);
element.content = {
...element.content,
name: element.name,
package: element.package?.path,
};
} else if (element instanceof INTERNAL__UnknownStore) {
this.storesIndex.delete(oldPath);
this.storesIndex.set(newPath, element);
element.content = {
...element.content,
name: element.name,
package: element.package?.path,
};
} else if (element instanceof Mapping) {
this.mappingsIndex.delete(oldPath);
this.mappingsIndex.set(newPath, element);
} else if (element instanceof Store) {
this.storesIndex.delete(oldPath);
this.storesIndex.set(newPath, element);
} else if (element instanceof Type) {
this.typesIndex.delete(oldPath);
this.typesIndex.set(newPath, element);
if (element instanceof Measure) {
if (element.canonicalUnit) {
this.typesIndex.delete(element.canonicalUnit.path);
this.typesIndex.set(
element.canonicalUnit.path.replace(oldPath, newPath),
element.canonicalUnit,
);
}
element.nonCanonicalUnits.forEach((unit) => {
this.typesIndex.delete(unit.path);
this.typesIndex.set(unit.path.replace(oldPath, newPath), unit);
});
}
} else if (element instanceof Association) {
this.associationsIndex.delete(oldPath);
this.associationsIndex.set(newPath, element);
} else if (element instanceof Profile) {
this.profilesIndex.delete(oldPath);
this.profilesIndex.set(newPath, element);
} else if (element instanceof ConcreteFunctionDefinition) {
this.functionsIndex.delete(oldPath);
this.functionsIndex.set(newPath, element);
} else if (element instanceof FunctionActivator) {
this.functionActivatorsIndex.delete(oldPath);
this.functionActivatorsIndex.set(newPath, element);
} else if (element instanceof Service) {
this.servicesIndex.delete(oldPath);
this.servicesIndex.set(newPath, element);
} else if (element instanceof PackageableRuntime) {
this.runtimesIndex.delete(oldPath);
this.runtimesIndex.set(newPath, element);
} else if (element instanceof PackageableConnection) {
this.connectionsIndex.delete(oldPath);
this.connectionsIndex.set(newPath, element);
} else if (element instanceof FileGenerationSpecification) {
this.fileGenerationsIndex.delete(oldPath);
this.fileGenerationsIndex.set(newPath, element);
} else if (element instanceof DataElement) {
this.dataElementsIndex.delete(oldPath);
this.dataElementsIndex.set(newPath, element);
} else if (element instanceof ExecutionEnvironmentInstance) {
this.executionEnvironmentsIndex.delete(oldPath);
this.executionEnvironmentsIndex.set(newPath, element);
} else if (element instanceof GenerationSpecification) {
this.generationSpecificationsIndex.delete(oldPath);
this.generationSpecificationsIndex.set(newPath, element);
} else if (element instanceof Package) {
// Since we will modify the package name, we need to first store the original
// paths of all of its children
const childElements = new Map<string, PackageableElement>();
element.children.forEach((childElement) => {
childElements.set(childElement.path, childElement);
});
// update element name
element.name = newElementName;
if (!element.package) {
throw new IllegalStateError(`Can't rename root package`);
}
/**
* Update element package if needed.
*
* NOTE: to be clean, first completely remove the package from its parent package
* Only then would we find or create the new parent package. This way, if we rename
* package `example::something` to `example::something::somethingElse`, we will not
* end up in a loop. If we did not first remove the package from its parent package
* we would end up having the `somethingElse` package containing itself as a child.
*/
const currentParentPackage = element.package;
if (currentParentPackage !== this.getNullablePackage(newPackagePath)) {
deleteElementFromPackage(currentParentPackage, element);
const newParentPackage =
newPackagePath !== ''
? getOrCreateGraphPackage(this, newPackagePath, undefined)
: this.root;
addElementToPackage(newParentPackage, element);
}
childElements.forEach((childElement, childElementOriginalPath) => {
// NOTE: since we already modified the parent package path, we need to pass in the child element's original path
// here to be sure we're properly, if we rely on the `childElement.path` that would cause a bug
this.renameOwnElement(
childElement,
childElementOriginalPath,
childElementOriginalPath.replace(oldPath, newPath),
);
});
} else {
const extension = this.getExtensionForElementClass(
getClass<PackageableElement>(element),
);
extension.renameElement(oldPath, newPath);
}
}
/**
* TODO: this will be removed once we have full support for section index
* See https://github.com/finos/legend-studio/issues/1067
*/
TEMPORARY__deleteOwnSectionIndex(): void {
this.sectionIndicesIndex.forEach((sectionIndex) => {
sectionIndex.setIsDeleted(true);
this.sectionIndicesIndex.delete(sectionIndex.path);
});
// NOTE: we have to do this because right now we don't add section index to the package tree
// as such `this.sectionIndicesIndex.delete(sectionIndex.path)` won't work because the path
// is without the package
this.sectionIndicesIndex = new Map<string, SectionIndex>();
this.elementSectionIndex = new Map<string, Section>();
}
}