@itwin/core-backend
Version:
iTwin.js backend components
305 lines • 14.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module ElementAspects
*/
import { RelatedElement } from "@itwin/core-common";
import { Entity } from "./Entity";
import { assert, DbResult } from "@itwin/core-bentley";
import { _verifyChannel } from "./internal/Symbols";
import { SheetOwnsSheetInformationAspect } from "./NavigationRelationship";
import { Sheet } from "./Element";
import { ECVersion } from "@itwin/ecschema-metadata";
/** An Element Aspect is a class that defines a set of properties that are related to (and owned by) a single element.
* Semantically, an ElementAspect can be considered part of the Element. Thus, an ElementAspect is deleted if its owning Element is deleted.
* BIS Guideline: Subclass ElementUniqueAspect or ElementMultiAspect rather than subclassing ElementAspect directly.
* @public @preview
*/
export class ElementAspect extends Entity {
static get className() { return "ElementAspect"; }
element;
/** Construct an aspect from its JSON representation and its containing iModel. */
constructor(props, iModel) {
super(props, iModel);
this.element = RelatedElement.fromJSON(props.element); // eslint-disable-line @typescript-eslint/no-non-null-assertion
}
toJSON() {
const val = super.toJSON();
val.element = this.element;
return val;
}
/** Called before a new ElementAspect is inserted.
* @note throw an exception to disallow the insert
* @note If you override this method, you must call super.
* @beta
*/
static onInsert(arg) {
const { props, iModel } = arg;
iModel.channels[_verifyChannel](arg.model);
iModel.locks.checkExclusiveLock(props.element.id, "element", "insert aspect");
}
/** Called after a new ElementAspect was inserted.
* @note If you override this method, you must call super.
* @beta
*/
static onInserted(_arg) { }
/** Called before an ElementAspect is updated.
* @note throw an exception to disallow the update
* @note If you override this method, you must call super.
* @beta
*/
static onUpdate(arg) {
const { props, iModel } = arg;
iModel.channels[_verifyChannel](arg.model);
iModel.locks.checkExclusiveLock(props.element.id, "element", "update aspect");
}
/** Called after an ElementAspect was updated.
* @note If you override this method, you must call super.
* @beta
*/
static onUpdated(_arg) { }
/** Called before an ElementAspect is deleted.
* @note throw an exception to disallow the delete
* @note If you override this method, you must call super.
* @beta
*/
static onDelete(arg) {
const { aspectId, iModel } = arg;
iModel.channels[_verifyChannel](arg.model);
const { element } = iModel.elements.getAspect(aspectId);
iModel.locks.checkExclusiveLock(element.id, "element", "delete aspect");
}
/** Called after an ElementAspect was deleted.
* @note If you override this method, you must call super.
* @beta
*/
static onDeleted(_arg) { }
}
/** An Element Unique Aspect is an ElementAspect where there can be only zero or one instance of the Element Aspect class per Element.
* @public @preview
*/
export class ElementUniqueAspect extends ElementAspect {
static get className() { return "ElementUniqueAspect"; }
}
/** An Element Multi-Aspect is an ElementAspect where there can be **n** instances of the Element Aspect class per Element.
* @public @preview
*/
export class ElementMultiAspect extends ElementAspect {
static get className() { return "ElementMultiAspect"; }
}
/**
* @public @preview
*/
export class ChannelRootAspect extends ElementUniqueAspect {
static get className() { return "ChannelRootAspect"; }
/** Insert a ChannelRootAspect on the specified element.
* @deprecated in 4.0 - will not be removed until after 2026-06-13. Use [[ChannelControl.makeChannelRoot]]. This method does not enforce the rule that channels may not nest and is therefore dangerous.
*/
static insert(iModel, ownerId, channelName) {
const props = { classFullName: this.classFullName, element: { id: ownerId }, owner: channelName };
iModel.elements.insertAspect(props);
}
}
const minimumBisCoreVersion = new ECVersion(1, 0, 25);
/** An [[ElementUniqueAspect]] that captures common metadata about a single [[Sheet]].
* Use [[getSheetInformation]] to retrieve the metadata for a Sheet and [[setSheetInformation]] to create, update, or delete it.
* @beta
*/
export class SheetInformationAspect extends ElementUniqueAspect {
static get className() { return "SheetInformationAspect"; }
/** The sheet's metadata. */
sheetInformation;
static onInsert(arg) {
super.onInsert(arg);
const sheet = arg.iModel.elements.tryGetElement(arg.props.element);
if (!(sheet instanceof Sheet)) {
throw new Error("SheetInformationAspect can only be applied to a Sheet element");
}
}
constructor(props, iModel) {
super(props, iModel);
const designedDate = undefined !== props.designedDate ? new Date(props.designedDate) : undefined;
this.sheetInformation = {
designedBy: props.designedBy,
designedDate,
drawnBy: props.drawnBy,
checkedBy: props.checkedBy,
};
}
toJSON() {
const props = super.toJSON();
for (const key of ["designedBy", "drawnBy", "checkedBy"]) {
const value = this.sheetInformation[key];
if (undefined !== value) {
props[key] = value;
}
}
if (undefined !== this.sheetInformation.designedDate) {
props.designedDate = this.sheetInformation.designedDate.toISOString();
}
return props;
}
static findForSheet(sheetId, iModel) {
if (!iModel.meetsMinimumSchemaVersion("BisCore", minimumBisCoreVersion)) {
return undefined;
}
const aspects = iModel.elements.getAspects(sheetId, this.classFullName);
if (aspects[0]) {
assert(aspects[0] instanceof SheetInformationAspect);
return aspects[0];
}
return undefined;
}
/** Retrieves the metadata hosted by the aspect on the specified sheet, returning `undefined` if no such metadata could be retrieved.
* @see [[setSheetInformation]] to create, update, or delete the aspect.
*/
static getSheetInformation(sheetId, iModel) {
const aspect = this.findForSheet(sheetId, iModel);
return aspect?.sheetInformation;
}
/** Sets the `information` for the [[Sheet]] element specified by ``sheetId`.
* If `information` is `undefined`, any existing aspect will be deleted.
* Otherwise, a new aspect will be inserted, or an existing aspect will be updated with the new metadata.
* @throws Error if the iModel contains a version of the BisCore schema older than 01.00.25.
*/
static setSheetInformation(information, sheetId, iModel) {
iModel.requireMinimumSchemaVersion("BisCore", minimumBisCoreVersion, "SheetInformationAspect");
const aspect = this.findForSheet(sheetId, iModel);
if (!information) {
if (aspect) {
iModel.elements.deleteAspect(aspect.id);
}
return;
}
if (aspect) {
aspect.sheetInformation = { ...information };
iModel.elements.updateAspect(aspect.toJSON());
}
else {
const info = { ...information };
for (const key of Object.keys(info)) {
if (info[key] === undefined) {
delete info[key];
}
}
if (undefined !== info.designedDate) {
info.designedDate = info.designedDate.toISOString();
}
const props = {
classFullName: this.classFullName,
element: {
id: sheetId,
relClassName: SheetOwnsSheetInformationAspect.classFullName,
},
...info,
};
iModel.elements.insertAspect(props);
}
}
}
/** An ElementMultiAspect that stores synchronization information for an Element originating from an external source.
* @note The associated ECClass was added to the BisCore schema in version 1.0.2
* @public @preview
*/
export class ExternalSourceAspect extends ElementMultiAspect {
static get className() { return "ExternalSourceAspect"; }
/** An element that scopes the combination of `kind` and `identifier` to uniquely identify the object from the external source.
* @note Warning: in a future major release the `scope` property will be optional, since the scope is intended to be potentially invalid.
* all references should treat it as potentially undefined, but we cannot change the type yet since that is a breaking change.
*/
scope;
/** The identifier of the object in the source repository. */
identifier;
/** The kind of object within the source repository. */
kind;
/** The cryptographic hash (any algorithm) of the source object's content. If defined, it must be guaranteed to change when the source object's content changes. */
checksum;
/** An optional value that is typically a version number or a pseudo version number like last modified time.
* It will be used by the synchronization process to detect that a source object is unchanged so that computing a cryptographic hash can be avoided.
* If present, this value must be guaranteed to change when any of the source object's content changes.
*/
version;
/** A place where additional JSON properties can be stored. For example, provenance information or properties relating to the synchronization process.
* @note Warning: in a future major release, the type of `jsonProperties` will be changed to object, and itwin.js will automatically stringify it when writing to the iModel.
* This will be a breaking change, since application code will have to change from supplying a string to supplying an object.
*/
jsonProperties;
/** The source of the imported/synchronized object. Should point to an instance of [ExternalSource]($backend). */
source;
/** Construct an aspect from its JSON representation and its containing iModel. */
constructor(props, iModel) {
super(props, iModel);
this.scope = RelatedElement.fromJSON(props.scope); // eslint-disable-line @typescript-eslint/no-non-null-assertion
this.source = RelatedElement.fromJSON(props.source);
this.identifier = props.identifier;
this.kind = props.kind;
this.checksum = props.checksum;
this.version = props.version;
this.jsonProperties = props.jsonProperties;
}
/** Look up the elements that contain one or more ExternalSourceAspect with the specified Scope, Kind, and Identifier.
* The result of this function is an array of all of the ExternalSourceAspects that were found, each associated with the owning element.
* A given element could have more than one ExternalSourceAspect with the given scope, kind, and identifier.
* Also, many elements could have ExternalSourceAspect with the same scope, kind, and identifier.
* Therefore, the result array could have more than one entry with the same elementId.
* Aspects are never shared. Each aspect has its own unique ECInstanceId.
* @param iModelDb The iModel to query
* @param scope The scope of the ExternalSourceAspects to find
* @param kind The kind of the ExternalSourceAspects to find
* @param identifier The identifier of the ExternalSourceAspects to find
* @returns the query results
*/
static findAllBySource(iModelDb, scope, kind, identifier) {
const sql = `SELECT Element.Id, ECInstanceId FROM ${ExternalSourceAspect.classFullName} WHERE (Scope.Id=:scope AND Kind=:kind AND Identifier=:identifier)`;
const found = [];
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(sql, (statement) => {
statement.bindId("scope", scope);
statement.bindString("kind", kind);
statement.bindString("identifier", identifier);
while (DbResult.BE_SQLITE_ROW === statement.step()) {
found.push({ elementId: statement.getValue(0).getId(), aspectId: statement.getValue(1).getId() });
}
});
return found;
}
toJSON() {
const val = super.toJSON();
val.scope = this.scope;
val.source = this.source;
val.identifier = this.identifier;
val.kind = this.kind;
val.checksum = this.checksum;
val.version = this.version;
val.jsonProperties = this.jsonProperties;
return val;
}
collectReferenceIds(referenceIds) {
super.collectReferenceIds(referenceIds);
if (this.scope)
referenceIds.addElement(this.scope.id);
referenceIds.addElement(this.element.id);
if (this.source)
referenceIds.addElement(this.source.id);
}
}
/** @public @preview */
(function (ExternalSourceAspect) {
/** Standard values for the `Kind` property of `ExternalSourceAspect`.
* @public @preview
*/
let Kind;
(function (Kind) {
/** Indicates that the [[ExternalSourceAspect]] is storing [[Element]] provenance */
Kind["Element"] = "Element";
/** Indicates that the [[ExternalSourceAspect]] is storing [[Relationship]] provenance */
Kind["Relationship"] = "Relationship";
/** Indicates that the [[ExternalSourceAspect]] is storing *scope* provenance
* @see [[ExternalSourceAspect.scope]]
*/
Kind["Scope"] = "Scope";
})(Kind = ExternalSourceAspect.Kind || (ExternalSourceAspect.Kind = {}));
})(ExternalSourceAspect || (ExternalSourceAspect = {}));
//# sourceMappingURL=ElementAspect.js.map