@itwin/ecschema-metadata
Version:
ECObjects core concepts in typescript
846 lines • 55.7 kB
JavaScript
"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._