UNPKG

@itwin/ecschema-metadata

Version:

ECObjects core concepts in typescript

846 lines • 55.7 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaReadHelper = void 0; const Context_1 = require("../Context"); const ECObjects_1 = require("../ECObjects"); const Exception_1 = require("../Exception"); const Schema_1 = require("../Metadata/Schema"); const SchemaItem_1 = require("../Metadata/SchemaItem"); const SchemaKey_1 = require("../SchemaKey"); const SchemaPartVisitorDelegate_1 = require("../SchemaPartVisitorDelegate"); const core_quantity_1 = require("@itwin/core-quantity"); const SchemaGraph_1 = require("../utils/SchemaGraph"); /** * This class properly handles the order the deserialization of ECSchemas and SchemaItems from serialized formats. * For example, when deserializing an ECClass most times all base class should be de-serialized before the given class. * @internal */ class SchemaReadHelper { _context; _visitorHelper; _parserType; _parser; // Cache of the schema currently being loaded. This schema is in the _context but to // avoid going back to the context every time, the cache is used. _schema; _schemaInfo; constructor(parserType, context, visitor) { this._context = (undefined !== context) ? context : new Context_1.SchemaContext(); this._visitorHelper = visitor ? new SchemaPartVisitorDelegate_1.SchemaPartVisitorDelegate(visitor) : undefined; this._parserType = parserType; } /** * Creates a complete SchemaInfo and starts parsing the schema from a serialized representation. * The info and schema promise will be registered with the SchemaContext. The complete schema can be retrieved by * calling getCachedSchema on the context. * @param schema The Schema to populate * @param rawSchema The serialized data to use to populate the Schema. * @param addSchemaToCache Optional parameter that indicates if the schema should be added to the SchemaContext. * The default is true. If false, the schema loading will not begin asynchronously in the background because the * schema promise must be added to the context. In this case, only the SchemaInfo is returned. */ async readSchemaInfo(schema, rawSchema, addSchemaToCache = true) { // Ensure context matches schema context if (schema.context) { if (this._context !== schema.context) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.DifferentSchemaContexts, "The SchemaContext of the schema must be the same SchemaContext held by the SchemaReadHelper."); } else { schema.setContext(this._context); } this._parser = new this._parserType(rawSchema); // Loads all of the properties on the Schema object await schema.fromJSON(this._parser.parseSchema()); this._schema = schema; const schemaReferences = []; const schemaInfo = { schemaKey: schema.schemaKey, alias: schema.alias, references: schemaReferences }; for (const reference of this._parser.getReferences()) { const refKey = new SchemaKey_1.SchemaKey(reference.name, SchemaKey_1.ECVersion.fromString(reference.version)); schemaReferences.push({ schemaKey: refKey }); } this._schemaInfo = schemaInfo; // Need to add this schema to the context to be able to locate schemaItems within the context. if (addSchemaToCache && !this._context.schemaExists(schema.schemaKey)) { await this._context.addSchemaPromise(schemaInfo, schema, this.loadSchema(schemaInfo, schema)); } return schemaInfo; } /** * Populates the given Schema from a serialized representation. * @param schema The Schema to populate * @param rawSchema The serialized data to use to populate the Schema. * @param addSchemaToCache Optional parameter that indicates if the schema should be added to the SchemaContext. * The default is true. If false, the schema will be loaded directly by this method and not from the context's schema cache. */ async readSchema(schema, rawSchema, addSchemaToCache = true) { if (!this._schemaInfo) { await this.readSchemaInfo(schema, rawSchema, addSchemaToCache); } // If not adding schema to cache (occurs in readSchemaInfo), we must load the schema here if (!addSchemaToCache) { const loadedSchema = await this.loadSchema(this._schemaInfo, schema); if (undefined === loadedSchema) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.UnableToLoadSchema, `Could not load schema ${schema.schemaKey.toString()}`); return loadedSchema; } const cachedSchema = await this._context.getCachedSchema(this._schemaInfo.schemaKey, ECObjects_1.SchemaMatchType.Latest); if (undefined === cachedSchema) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.UnableToLoadSchema, `Could not load schema ${schema.schemaKey.toString()}`); return cachedSchema; } /** * Called when a SchemaItem has been successfully loaded by the Helper. The default implementation simply * checks if the schema item is undefined. An implementation of the helper may choose to partially load * a schema item in which case this method would indicate if the item has been fully loaded. * @param schemaItem The SchemaItem to check. * @returns True if the SchemaItem has been fully loaded, false otherwise. */ isSchemaItemLoaded(schemaItem) { return schemaItem !== undefined; } /* Finish loading the rest of the schema */ async loadSchema(schemaInfo, schema) { // Verify that there are no schema reference cycles, this will start schema loading by loading their headers (await SchemaGraph_1.SchemaGraph.generateGraph(schemaInfo, this._context)).throwIfCycles(); for (const reference of schemaInfo.references) { await this.loadSchemaReference(schema, reference.schemaKey); } if (this._visitorHelper) await this._visitorHelper.visitSchema(schema, false); // Load all schema items for (const [itemName, itemType, rawItem] of this._parser.getItems()) { // Make sure the item has not already been loaded. No need to check the SchemaContext because all SchemaItems are added to a Schema, // which would be found when adding to the context. const schemaItem = await schema.getItem(itemName); if (this.isSchemaItemLoaded(schemaItem)) continue; const loadedItem = await this.loadSchemaItem(schema, itemName, itemType, rawItem); if (this.isSchemaItemLoaded(loadedItem) && this._visitorHelper) { await this._visitorHelper.visitSchemaPart(loadedItem); } } await this.loadCustomAttributes(schema, this._parser.getSchemaCustomAttributeProviders()); if (this._visitorHelper) await this._visitorHelper.visitSchema(schema); return schema; } /** * Populates the given Schema from a serialized representation. * @param schema The Schema to populate * @param rawSchema The serialized data to use to populate the Schema. */ readSchemaSync(schema, rawSchema) { this._parser = new this._parserType(rawSchema); // Loads all of the properties on the Schema object schema.fromJSONSync(this._parser.parseSchema()); this._schema = schema; // Need to add this schema to the context to be able to locate schemaItems within the context. if (!this._context.schemaExists(schema.schemaKey)) this._context.addSchemaSync(schema); // Load schema references first // Need to figure out if other schemas are present. for (const reference of this._parser.getReferences()) { this.loadSchemaReferenceSync(reference); } if (this._visitorHelper) this._visitorHelper.visitSchemaSync(schema, false); // Load all schema items for (const [itemName, itemType, rawItem] of this._parser.getItems()) { const loadedItem = this.loadSchemaItemSync(schema, itemName, itemType, rawItem); if (this.isSchemaItemLoaded(loadedItem) && this._visitorHelper) { this._visitorHelper.visitSchemaPartSync(loadedItem); } } this.loadCustomAttributesSync(schema, this._parser.getSchemaCustomAttributeProviders()); if (this._visitorHelper) this._visitorHelper.visitSchemaSync(schema); return schema; } /** * Ensures that the schema references can be located and adds them to the schema. * @param ref The object to read the SchemaReference's props from. */ async loadSchemaReference(schema, refKey) { const refSchema = await this._context.getSchema(refKey, ECObjects_1.SchemaMatchType.LatestWriteCompatible); if (undefined === refSchema) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.UnableToLocateSchema, `Could not locate the referenced schema, ${refKey.name}.${refKey.version.toString()}, of ${schema.schemaKey.name}`); if (schema.references.find((ref) => ref.schemaKey.matches(refSchema.schemaKey))) return refSchema; await schema.addReference(refSchema); const results = this.validateSchemaReferences(schema); let errorMessage = ""; for (const result of results) { errorMessage += `${result}\r\n`; } if (errorMessage) { throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `${errorMessage}`); } return refSchema; } /** * Ensures that the schema references can be located and adds them to the schema. * @param ref The object to read the SchemaReference's props from. */ loadSchemaReferenceSync(ref) { const schemaKey = new SchemaKey_1.SchemaKey(ref.name, SchemaKey_1.ECVersion.fromString(ref.version)); const refSchema = this._context.getSchemaSync(schemaKey, ECObjects_1.SchemaMatchType.LatestWriteCompatible); if (!refSchema) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.UnableToLocateSchema, `Could not locate the referenced schema, ${ref.name}.${ref.version}, of ${this._schema.schemaKey.name}`); this._schema.addReferenceSync(refSchema); SchemaGraph_1.SchemaGraph.generateGraphSync(this._schema).throwIfCycles(); const results = this.validateSchemaReferences(this._schema); let errorMessage = ""; for (const result of results) { errorMessage += `${result}\r\n`; } if (errorMessage) { throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `${errorMessage}`); } } /** * Validates schema references against multiple EC rules. * @param schema The schema to validate. */ *validateSchemaReferences(schema) { const aliases = new Map(); for (const schemaRef of schema.references) { if (schemaRef.customAttributes && schemaRef.customAttributes.has("CoreCustomAttributes.SupplementalSchema")) yield `Referenced schema '${schemaRef.name}' of schema '${schema.name}' is a supplemental schema. Supplemental schemas are not allowed to be referenced.`; if (schema.schemaKey.matches(schemaRef.schemaKey)) yield `Schema '${schema.name}' has reference cycles: '${schema.name} --> ${schemaRef.name}'`; if (aliases.has(schemaRef.alias)) { const currentRef = aliases.get(schemaRef.alias); yield `Schema '${schema.name}' has multiple schema references (${currentRef.name}, $schemaRef.name}) with the same alias '${schemaRef.alias}', which is not allowed.`; } else { aliases.set(schemaRef.alias, schemaRef); } } } /** * Given the schema item object, the anticipated type and the name a schema item is created and loaded into the schema provided. * @param schema The Schema the SchemaItem to. * @param name The name of the schema item to be loaded. * @param itemType The SchemaItemType of the item to load. * @param schemaItemObject The Object to populate the SchemaItem with. */ async loadSchemaItem(schema, name, itemType, schemaItemObject) { let schemaItem = await schema.getItem(name); if (this.isSchemaItemLoaded(schemaItem)) { return schemaItem; } switch ((0, ECObjects_1.parseSchemaItemType)(itemType)) { case ECObjects_1.SchemaItemType.EntityClass: schemaItem = schemaItem || await schema.createEntityClass(name); schemaItemObject && await this.loadEntityClass(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.StructClass: schemaItem = schemaItem || await schema.createStructClass(name); const structProps = schemaItemObject && this._parser.parseStructClass(schemaItemObject); structProps && await this.loadClass(schemaItem, structProps, schemaItemObject); break; case ECObjects_1.SchemaItemType.Mixin: schemaItem = schemaItem || await schema.createMixinClass(name); schemaItemObject && await this.loadMixin(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.CustomAttributeClass: schemaItem = schemaItem || await schema.createCustomAttributeClass(name); const caClassProps = schemaItemObject && this._parser.parseCustomAttributeClass(schemaItemObject); caClassProps && await this.loadClass(schemaItem, caClassProps, schemaItemObject); break; case ECObjects_1.SchemaItemType.RelationshipClass: schemaItem = schemaItem || await schema.createRelationshipClass(name); schemaItemObject && await this.loadRelationshipClass(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.KindOfQuantity: schemaItem = schemaItem || await schema.createKindOfQuantity(name); schemaItemObject && await this.loadKindOfQuantity(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Unit: schemaItem = schemaItem || await schema.createUnit(name); schemaItemObject && await this.loadUnit(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Constant: schemaItem = schemaItem || await schema.createConstant(name); schemaItemObject && await this.loadConstant(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.InvertedUnit: schemaItem = schemaItem || await schema.createInvertedUnit(name); schemaItemObject && await this.loadInvertedUnit(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Format: schemaItem = schemaItem || await schema.createFormat(name); schemaItemObject && await this.loadFormat(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Phenomenon: schemaItem = schemaItem || await schema.createPhenomenon(name); const phenomenonProps = schemaItemObject && this._parser.parsePhenomenon(schemaItemObject); phenomenonProps && await schemaItem.fromJSON(phenomenonProps); break; case ECObjects_1.SchemaItemType.UnitSystem: schemaItem = schemaItem || await schema.createUnitSystem(name); schemaItemObject && await schemaItem.fromJSON(this._parser.parseUnitSystem(schemaItemObject)); break; case ECObjects_1.SchemaItemType.PropertyCategory: schemaItem = schemaItem || await schema.createPropertyCategory(name); const propertyCategoryProps = schemaItemObject && this._parser.parsePropertyCategory(schemaItemObject); propertyCategoryProps && schemaItemObject && await schemaItem.fromJSON(propertyCategoryProps); break; case ECObjects_1.SchemaItemType.Enumeration: schemaItem = schemaItem || await schema.createEnumeration(name); const enumerationProps = schemaItemObject && this._parser.parseEnumeration(schemaItemObject); enumerationProps && await schemaItem.fromJSON(enumerationProps); break; // NOTE: we are being permissive here and allowing unknown types to silently fail. Not sure if we want to hard fail or just do a basic deserialization } return schemaItem; } /** * Load the customAttribute class dependencies for a set of CustomAttribute objects and add * them to a given custom attribute container. * @param container The CustomAttributeContainer that each CustomAttribute will be added to. * @param customAttributes An iterable set of parsed CustomAttribute objects. */ async loadCustomAttributes(container, caProviders) { for (const providerTuple of caProviders) { // First tuple entry is the CA class name. const caClass = await this.findSchemaItem(providerTuple[0]); // If custom attribute exist within the context and is referenced, validate the reference is defined in the container's schema if (caClass && caClass.key.schemaName !== container.schema.name && !container.schema.getReferenceSync(caClass.key.schemaName)) { throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `Unable to load custom attribute ${caClass.fullName} from container ${container.fullName}, ${caClass.key.schemaName} reference not defined`); } // Second tuple entry ia a function that provides the CA instance. const provider = providerTuple[1]; const customAttribute = provider(caClass); container.addCustomAttribute(customAttribute); } } /** * Given the schema item object, the anticipated type and the name a schema item is created and loaded into the schema provided. * @param schema The Schema the SchemaItem to. * @param name The name of the schema item to be loaded. * @param itemType The SchemaItemType of the item to load. * @param schemaItemObject The Object to populate the SchemaItem with. */ loadSchemaItemSync(schema, name, itemType, schemaItemObject) { let schemaItem = schema.getItemSync(name); if (this.isSchemaItemLoaded(schemaItem)) { return schemaItem; } switch ((0, ECObjects_1.parseSchemaItemType)(itemType)) { case ECObjects_1.SchemaItemType.EntityClass: schemaItem = schemaItem || schema.createEntityClassSync(name); this.loadEntityClassSync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.StructClass: schemaItem = schemaItem || schema.createStructClassSync(name); const structProps = this._parser.parseStructClass(schemaItemObject); this.loadClassSync(schemaItem, structProps, schemaItemObject); break; case ECObjects_1.SchemaItemType.Mixin: schemaItem = schemaItem || schema.createMixinClassSync(name); this.loadMixinSync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.CustomAttributeClass: schemaItem = schemaItem || schema.createCustomAttributeClassSync(name); const caClassProps = this._parser.parseCustomAttributeClass(schemaItemObject); this.loadClassSync(schemaItem, caClassProps, schemaItemObject); break; case ECObjects_1.SchemaItemType.RelationshipClass: schemaItem = schemaItem || schema.createRelationshipClassSync(name); this.loadRelationshipClassSync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.KindOfQuantity: schemaItem = schemaItem || schema.createKindOfQuantitySync(name); this.loadKindOfQuantitySync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Unit: schemaItem = schemaItem || schema.createUnitSync(name); this.loadUnitSync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Constant: schemaItem = schemaItem || schema.createConstantSync(name); this.loadConstantSync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.InvertedUnit: schemaItem = schemaItem || schema.createInvertedUnitSync(name); this.loadInvertedUnitSync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Format: schemaItem = schemaItem || schema.createFormatSync(name); this.loadFormatSync(schemaItem, schemaItemObject); break; case ECObjects_1.SchemaItemType.Phenomenon: schemaItem = schemaItem || schema.createPhenomenonSync(name); const phenomenonProps = this._parser.parsePhenomenon(schemaItemObject); schemaItem.fromJSONSync(phenomenonProps); break; case ECObjects_1.SchemaItemType.UnitSystem: schemaItem = schemaItem || schema.createUnitSystemSync(name); schemaItem.fromJSONSync(this._parser.parseUnitSystem(schemaItemObject)); break; case ECObjects_1.SchemaItemType.PropertyCategory: schemaItem = schemaItem || schema.createPropertyCategorySync(name); const propertyCategoryProps = this._parser.parsePropertyCategory(schemaItemObject); schemaItem.fromJSONSync(propertyCategoryProps); break; case ECObjects_1.SchemaItemType.Enumeration: schemaItem = schemaItem || schema.createEnumerationSync(name); const enumerationProps = this._parser.parseEnumeration(schemaItemObject); schemaItem.fromJSONSync(enumerationProps); break; // NOTE: we are being permissive here and allowing unknown types to silently fail. Not sure if we want to hard fail or just do a basic deserialization } return schemaItem; } /** * Given the full (Schema.ItemName) or qualified (alias:ItemName) item name, returns * a tuple of strings in the format ["SchemaName", "ItemName"]. The schema name may be * empty if the item comes from the schema being parsed. * @param fullOrQualifiedName The full or qualified name of the schema item. * @param schema The schema that will be used to lookup the schema name by alias, if necessary. */ static resolveSchemaAndItemName(fullOrQualifiedName, schema) { const [schemaName, itemName] = SchemaItem_1.SchemaItem.parseFullName(fullOrQualifiedName); // If a schema is provided we attempt to resolve the alias by looking at the reference schemas. if (undefined !== schema && -1 !== fullOrQualifiedName.indexOf(":")) { const refName = schema.getReferenceNameByAlias(schemaName); if (undefined === refName) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.UnableToLocateSchema, `Could not resolve schema alias '${schemaName}' for schema item '${itemName}.`); return [refName, itemName]; } return [schemaName, itemName]; } /** * Finds the a SchemaItem matching the name first by checking the schema that is being deserialized. If it does * not exist within the schema, the SchemaContext will be searched. * @param name The full (Schema.ItemName) or qualified (alias:ItemName) name of the SchemaItem to search for. * @param skipVisitor Used to break Mixin -appliesTo-> Entity -extends-> Mixin cycle. * @param loadCallBack Only called if the SchemaItem had to be loaded. * @return The SchemaItem if it had to be loaded, otherwise undefined. */ async findSchemaItem(name, skipVisitor = false, loadCallBack) { let schemaItem; // TODO: A better solution should be investigated for handling both an alias and the schema name. const [schemaName, itemName] = SchemaReadHelper.resolveSchemaAndItemName(name, this._schema); const isInThisSchema = (this._schema && this._schema.name.toLowerCase() === schemaName.toLowerCase()); if (undefined === schemaName || 0 === schemaName.length) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `The SchemaItem ${name} is invalid without a schema name`); if (isInThisSchema) { schemaItem = await this._schema.getItem(itemName); if (schemaItem) return schemaItem; const foundItem = this._parser.findItem(itemName); if (foundItem) { schemaItem = await this.loadSchemaItem(this._schema, ...foundItem); if (!skipVisitor && this.isSchemaItemLoaded(schemaItem) && this._visitorHelper) { await this._visitorHelper.visitSchemaPart(schemaItem); } if (loadCallBack && schemaItem) loadCallBack(schemaItem); return schemaItem; } throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `Unable to locate SchemaItem ${name}.`); } schemaItem = await this._context.getSchemaItem(new SchemaKey_1.SchemaItemKey(itemName, new SchemaKey_1.SchemaKey(schemaName))); if (undefined === schemaItem) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `Unable to locate SchemaItem ${name}.`); return schemaItem; } /** * Finds the a SchemaItem matching the name first by checking the schema that is being deserialized. If it does * not exist within the schema, the SchemaContext will be searched. * @param name The full (Schema.ItemName) or qualified (alias:ItemName) name of the SchemaItem to search for. * @param skipVisitor Used to break Mixin -appliesTo-> Entity -extends-> Mixin cycle. * @param loadCallBack Only called if the SchemaItem had to be loaded. * @return The SchemaItem if it had to be loaded, otherwise undefined. */ findSchemaItemSync(name, skipVisitor = false, loadCallBack) { let schemaItem; // TODO: A better solution should be investigated for handling both an alias and the schema name. const [schemaName, itemName] = SchemaReadHelper.resolveSchemaAndItemName(name, this._schema); const isInThisSchema = (this._schema && this._schema.name.toLowerCase() === schemaName.toLowerCase()); if (undefined === schemaName || schemaName.length === 0) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `The SchemaItem ${name} is invalid without a schema name`); if (isInThisSchema && undefined === this._schema.getItemSync(itemName)) { const foundItem = this._parser.findItem(itemName); if (foundItem) { schemaItem = this.loadSchemaItemSync(this._schema, ...foundItem); if (!skipVisitor && this.isSchemaItemLoaded(schemaItem) && this._visitorHelper) { this._visitorHelper.visitSchemaPartSync(schemaItem); } if (loadCallBack && schemaItem) loadCallBack(schemaItem); return schemaItem; } throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `Unable to locate SchemaItem ${name}.`); } schemaItem = this._context.getSchemaItemSync(new SchemaKey_1.SchemaItemKey(itemName, new SchemaKey_1.SchemaKey(schemaName))); if (undefined === schemaItem) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `Unable to locate SchemaItem ${name}.`); return schemaItem; } /** * Load dependencies on phenomenon and unitSystem for a Unit object and load the Unit from its serialized format. * @param unit The Unit object that we are loading dependencies for and "deserializing into". * @param rawUnit The serialized unit data */ async loadUnit(unit, rawUnit) { const unitProps = this._parser.parseUnit(rawUnit); await this.findSchemaItem(unitProps.phenomenon, true); await this.findSchemaItem(unitProps.unitSystem, true); await unit.fromJSON(unitProps); } /** * Load dependencies on phenomenon and unitSystem for a Unit object and load the Unit from its serialized format. * @param unit The Unit object that we are loading dependencies for and "deserializing into". * @param rawUnit The serialized unit data */ loadUnitSync(unit, rawUnit) { const unitProps = this._parser.parseUnit(rawUnit); this.findSchemaItemSync(unitProps.phenomenon, true); this.findSchemaItemSync(unitProps.unitSystem, true); unit.fromJSONSync(unitProps); } /** * Load the persistence unit and presentation unit dependencies for a KindOfQuantity object and load the KoQ from its serialized format. * @param koq The KindOfQuantity object that we are loading dependencies for and "deserializing into". * @param rawKoQ The serialized kind of quantity data */ async loadKindOfQuantity(koq, rawKoQ) { const koqProps = this._parser.parseKindOfQuantity(rawKoQ); await this.findSchemaItem(koqProps.persistenceUnit); if (undefined !== koqProps.presentationUnits) { for (const formatString of koqProps.presentationUnits) { for (const name of (0, core_quantity_1.getItemNamesFromFormatString)(formatString)) { await this.findSchemaItem(name); } } } await koq.fromJSON(koqProps); } /** * Load the persistence unit and presentation unit dependencies for a KindOfQuantity object and load the KoQ from its serialized format. * @param koq The KindOfQuantity object that we are loading dependencies for and "deserializing into". * @param rawKoQ The serialized kind of quantity data */ loadKindOfQuantitySync(koq, rawKoQ) { const koqProps = this._parser.parseKindOfQuantity(rawKoQ); this.findSchemaItemSync(koqProps.persistenceUnit); if (undefined !== koqProps.presentationUnits) { for (const formatString of koqProps.presentationUnits) { for (const name of (0, core_quantity_1.getItemNamesFromFormatString)(formatString)) { this.findSchemaItemSync(name); } } } koq.fromJSONSync(koqProps); } /** * Load the phenomenon dependency for a Constant object and load the Constant from its serialized format. * @param constant The Constant object that we are loading the phenomenon dependency for * @param rawConstant The serialized constant data */ async loadConstant(constant, rawConstant) { const constantProps = this._parser.parseConstant(rawConstant); await this.findSchemaItem(constantProps.phenomenon, true); await constant.fromJSON(constantProps); } /** * Load the phenomenon dependency for a Constant object and load the Constant from its serialized format. * @param constant The Constant object that we are loading dependencies for and "deserializing into". * @param rawConstant The serialized constant data */ loadConstantSync(constant, rawConstant) { const constantProps = this._parser.parseConstant(rawConstant); this.findSchemaItemSync(constantProps.phenomenon, true); constant.fromJSONSync(constantProps); } /** * Load the unit system and invertsUnit dependencies for an Inverted Unit object and load the Inverted Unit from its serialized format. * @param invertedUnit The InvertedUnit object that we are loading dependencies for and "deserializing into". * @param rawInvertedUnit The serialized inverted unit data. */ async loadInvertedUnit(invertedUnit, rawInvertedUnit) { const invertedUnitProps = this._parser.parseInvertedUnit(rawInvertedUnit); await this.findSchemaItem(invertedUnitProps.invertsUnit, true); await this.findSchemaItem(invertedUnitProps.unitSystem, true); await invertedUnit.fromJSON(invertedUnitProps); } /** * Load the unit system and invertsUnit dependencies for an Inverted Unit object and load the Inverted Unit from its serialized format. * @param invertedUnit The InvertedUnit object that we are loading dependencies for and "deserializing into". * @param rawInvertedUnit The serialized inverted unit data. */ loadInvertedUnitSync(invertedUnit, rawInvertedUnit) { const invertedUnitProps = this._parser.parseInvertedUnit(rawInvertedUnit); this.findSchemaItemSync(invertedUnitProps.invertsUnit, true); this.findSchemaItemSync(invertedUnitProps.unitSystem, true); invertedUnit.fromJSONSync(invertedUnitProps); } /** * Load the unit dependencies for a Format object and load the Format from its serialized format. * @param format The Format object that we are loading dependencies for and "deserializing into". * @param rawFormat The serialized format data. */ async loadFormat(format, rawFormat) { const formatProps = this._parser.parseFormat(rawFormat); if (undefined !== formatProps.composite) { const formatUnits = formatProps.composite.units; for (const unit of formatUnits) { await this.findSchemaItem(unit.name, true); } } await format.fromJSON(formatProps); } /** * Load the unit dependencies for a Format object and load the Format from its serialized format. * @param format The Format object that we are loading dependencies for and "deserializing into". * @param rawFormat The serialized format data. */ loadFormatSync(format, rawFormat) { const formatProps = this._parser.parseFormat(rawFormat); if (undefined !== formatProps.composite) { const formatUnits = formatProps.composite.units; for (const unit of formatUnits) { this.findSchemaItemSync(unit.name, true); } } format.fromJSONSync(formatProps); } /** * Load the base class and property type dependencies for an ECClass object and load the ECClass (and its properties) from its serialized format. * @param classObj The ECClass object that we are loading dependencies for and "deserializing into". * @param classProps The parsed class props object. * @param rawClass The serialized class data. */ async loadClass(classObj, classProps, rawClass) { const baseClassLoaded = async (baseClass) => { if (this._visitorHelper && this.isSchemaItemLoaded(baseClass)) await this._visitorHelper.visitSchemaPart(baseClass); }; // Load base class first if (undefined !== classProps.baseClass) { await this.findSchemaItem(classProps.baseClass, true, baseClassLoaded); } // Now deserialize the class itself, *before* any properties // (We need to do this to break Entity -navProp-> Relationship -constraint-> Entity cycle.) await classObj.fromJSON(classProps); for (const [propName, propType, rawProp] of this._parser.getProperties(rawClass, classObj.fullName)) { await this.loadPropertyTypes(classObj, propName, propType, rawProp); } await this.loadCustomAttributes(classObj, this._parser.getClassCustomAttributeProviders(rawClass)); } /** * Load the base class and property type dependencies for an ECClass object and load the ECClass (and its properties) from its serialized format. * @param classObj The ECClass object that we are loading dependencies for and "deserializing into". * @param classProps The parsed class props object. * @param rawClass The serialized class data. */ loadClassSync(classObj, classProps, rawClass) { const baseClassLoaded = async (baseClass) => { if (this._visitorHelper && this.isSchemaItemLoaded(baseClass)) this._visitorHelper.visitSchemaPartSync(baseClass); }; // Load base class first if (undefined !== classProps.baseClass) { this.findSchemaItemSync(classProps.baseClass, true, baseClassLoaded); } // Now deserialize the class itself, *before* any properties // (We need to do this to break Entity -navProp-> Relationship -constraint-> Entity cycle.) classObj.fromJSONSync(classProps); for (const [propName, propType, rawProp] of this._parser.getProperties(rawClass, classObj.fullName)) { this.loadPropertyTypesSync(classObj, propName, propType, rawProp); } this.loadCustomAttributesSync(classObj, this._parser.getClassCustomAttributeProviders(rawClass)); } /** * Load the mixin, base class, and property type dependencies for an EntityClass object and load the EntityClass (and properties) from its serialized format. * @param entity The EntityClass that we are loading dependencies for and "deserializing into". * @param rawEntity The serialized entity class data. */ async loadEntityClass(entity, rawEntity) { const entityClassProps = this._parser.parseEntityClass(rawEntity); // Load Mixin classes first if (undefined !== entityClassProps.mixins) { for (const mixinName of entityClassProps.mixins) await this.findSchemaItem(mixinName); } await this.loadClass(entity, entityClassProps, rawEntity); } /** * Load the mixin, base class, and property type dependencies for an EntityClass object and load the EntityClass (and properties) from its serialized format. * @param entity The EntityClass that we are loading dependencies for and "deserializing into". * @param rawEntity The serialized entity class data. */ loadEntityClassSync(entity, rawEntity) { const entityClassProps = this._parser.parseEntityClass(rawEntity); // Load Mixin classes first if (undefined !== entityClassProps.mixins) { for (const mixinName of entityClassProps.mixins) this.findSchemaItemSync(mixinName); } this.loadClassSync(entity, entityClassProps, rawEntity); } /** * Load the appliesTo class, base class, and property type dependencies for a Mixin object and load the Mixin (and properties) from its serialized format. * @param mixin The Mixin that we are loading dependencies for and "deserializing into". * @param rawMixin The serialized mixin data. */ async loadMixin(mixin, rawMixin) { const mixinProps = this._parser.parseMixin(rawMixin); const appliesToLoaded = async (appliesToClass) => { if (this._visitorHelper && this.isSchemaItemLoaded(appliesToClass)) await this._visitorHelper.visitSchemaPart(appliesToClass); }; await this.findSchemaItem(mixinProps.appliesTo, true, appliesToLoaded); await this.loadClass(mixin, mixinProps, rawMixin); } /** * Load the appliesTo class, base class, and property type dependencies for a Mixin object and load the Mixin (and properties) from its serialized format. * @param mixin The Mixin that we are loading dependencies for and "deserializing into". * @param rawMixin The serialized mixin data. */ loadMixinSync(mixin, rawMixin) { const mixinProps = this._parser.parseMixin(rawMixin); const appliesToLoaded = async (appliesToClass) => { if (this._visitorHelper && this.isSchemaItemLoaded(appliesToClass)) await this._visitorHelper.visitSchemaPart(appliesToClass); }; this.findSchemaItemSync(mixinProps.appliesTo, true, appliesToLoaded); this.loadClassSync(mixin, mixinProps, rawMixin); } /** * Load the relationship constraint, base class, and property type dependencies for a RelationshipClass object and load the RelationshipClass (and properties) from its serialized format. * @param rel The RelationshipClass that we are loading dependencies for and "deserializing into". * @param rawRel The serialized relationship class data. */ async loadRelationshipClass(rel, rawRel) { const relationshipClassProps = this._parser.parseRelationshipClass(rawRel); await this.loadClass(rel, relationshipClassProps, rawRel); await this.loadRelationshipConstraint(rel.source, relationshipClassProps.source); await this.loadRelationshipConstraint(rel.target, relationshipClassProps.target); const [sourceCustomAttributes, targetCustomAttributes] = this._parser.getRelationshipConstraintCustomAttributeProviders(rawRel); await this.loadCustomAttributes(rel.source, sourceCustomAttributes); await this.loadCustomAttributes(rel.target, targetCustomAttributes); } /** * Load the relationship constraint, base class, and property type dependencies for a RelationshipClass object and load the RelationshipClass (and properties) from its serialized format. * @param rel The RelationshipClass that we are loading dependencies for and "deserializing into". * @param rawRel The serialized relationship class data. */ loadRelationshipClassSync(rel, rawRel) { const relationshipClassProps = this._parser.parseRelationshipClass(rawRel); this.loadClassSync(rel, relationshipClassProps, rawRel); this.loadRelationshipConstraintSync(rel.source, relationshipClassProps.source); this.loadRelationshipConstraintSync(rel.target, relationshipClassProps.target); const [sourceCustomAttributes, targetCustomAttributes] = this._parser.getRelationshipConstraintCustomAttributeProviders(rawRel); this.loadCustomAttributesSync(rel.source, sourceCustomAttributes); this.loadCustomAttributesSync(rel.target, targetCustomAttributes); } /** * Load the abstract constraint and constraint class dependencies for a RelationshipConstraint object and load the RelationshipConstraint from its parsed props. * @param relConstraint The RelationshipConstraint that we are loading dependencies for and "deserializing into". * @param props The parsed relationship constraint props. */ async loadRelationshipConstraint(relConstraint, props) { if (undefined !== props.abstractConstraint) { await this.findSchemaItem(props.abstractConstraint); } if (undefined !== props.constraintClasses) { // TODO: this should be required for (const constraintClass of props.constraintClasses) { await this.findSchemaItem(constraintClass); } } await relConstraint.fromJSON(props); } /** * Load the abstract constraint and constraint class dependencies for a RelationshipConstraint object and load the RelationshipConstraint from its parsed props. * @param relConstraint The RelationshipConstraint that we are loading dependencies for and "deserializing into". * @param props The parsed relationship constraint props. */ loadRelationshipConstraintSync(relConstraint, props) { if (undefined !== props.abstractConstraint) { this.findSchemaItemSync(props.abstractConstraint); } if (undefined !== props.constraintClasses) { for (const constraintClass of props.constraintClasses) { this.findSchemaItemSync(constraintClass); } } relConstraint.fromJSONSync(props); } /** * Load the type dependencies for a serialized property, then creates and deserialized the Property object in the given ECClass. * @param classObj The ECClass that the Property should be created in. * @param propName The name of the Property. * @param propType The (serialized string) kind of property to create. * @param rawProperty The serialized property data. */ async loadPropertyTypes(classObj, propName, propType, rawProperty) { const loadTypeName = async (typeName) => { if (undefined === (0, ECObjects_1.parsePrimitiveType)(typeName)) { if (SchemaReadHelper.isECSpecVersionNewer(this._parser.getECSpecVersion)) return Exception_1.ECSchemaStatus.NewerECSpecVersion; await this.findSchemaItem(typeName); } return Exception_1.ECSchemaStatus.Success; }; const lowerCasePropType = propType.toLowerCase(); switch (lowerCasePropType) { case "primitiveproperty": const primPropertyProps = this._parser.parsePrimitiveProperty(rawProperty); if (await loadTypeName(primPropertyProps.typeName) === Exception_1.ECSchemaStatus.NewerECSpecVersion) primPropertyProps.typeName = "string"; const primProp = await classObj.createPrimitiveProperty(propName, primPropertyProps.typeName); return this.loadProperty(primProp, primPropertyProps, rawProperty); case "structproperty": const structPropertyProps = this._parser.parseStructProperty(rawProperty); await loadTypeName(structPropertyProps.typeName); const structProp = await classObj.createStructProperty(propName, structPropertyProps.typeName); return this.loadProperty(structProp, structPropertyProps, rawProperty); case "primitivearrayproperty": const primArrPropertyProps = this._parser.parsePrimitiveArrayProperty(rawProperty); if (await loadTypeName(primArrPropertyProps.typeName) === Exception_1.ECSchemaStatus.NewerECSpecVersion) primArrPropertyProps.typeName = "string"; const primArrProp = await classObj.createPrimitiveArrayProperty(propName, primArrPropertyProps.typeName); return this.loadProperty(primArrProp, primArrPropertyProps, rawProperty); case "structarrayproperty": const structArrPropertyProps = this._parser.parseStructArrayProperty(rawProperty); await loadTypeName(structArrPropertyProps.typeName); const structArrProp = await classObj.createStructArrayProperty(propName, structArrPropertyProps.typeName); return this.loadProperty(structArrProp, structArrPropertyProps, rawProperty); case "navigationproperty": if (classObj.schemaItemType !== ECObjects_1.SchemaItemType.EntityClass && classObj.schemaItemType !== ECObjects_1.SchemaItemType.RelationshipClass && classObj.schemaItemType !== ECObjects_1.SchemaItemType.Mixin) throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${propName} is invalid, because only EntityClasses, Mixins, and RelationshipClasses can have NavigationProperties.`); const navPropertyProps = this._parser.parseNavigationProperty(rawProperty); await this.findSchemaItem(navPropertyProps.relationshipName); const navProp = await classObj.createNavigationProperty(propName, navPropertyProps.relationshipName, navPropertyProps.direction); return this.loadProperty(navProp, navPropertyProps, rawProperty); } } /** * Load the type dependencies for a serialized property, then creates and deserialized the Property object in the given ECClass. * @param classObj The ECClass that the Property should be created in. * @param propName The name of the Property. * @param propType The (serialized string) kind of property to create. * @param rawProperty The serialized property data. */ loadPropertyTypesSync(classObj, propName, propType, rawProperty) { const loadTypeName = (typeName) => { if (undefined === (0, ECObjects_1.parsePrimitiveType)(typeName)) { if (SchemaReadHelper.isECSpecVersionNewer(this._parser.getECSpecVersion)) return Exception_1.ECSchemaStatus.NewerECSpecVersion; this.findSchemaItemSync(typeName); } return Exception_1.ECSchemaStatus.Success; }; const lowerCasePropType = propType.toLowerCase(); switch (lowerCasePropType) { case "primitiveproperty": const primPropertyProps = this._