@itwin/core-backend
Version:
iTwin.js backend components
148 lines • 7.96 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 Elements
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createChannelControl = createChannelControl;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const ChannelControl_1 = require("../ChannelControl");
const Element_1 = require("../Element");
const IModelHost_1 = require("../IModelHost");
const NavigationRelationship_1 = require("../NavigationRelationship");
const Symbols_1 = require("./Symbols");
const semver = require("semver");
class ChannelAdmin {
_iModel;
static channelClassName = "bis:ChannelRootAspect";
[Symbols_1._implementationProhibited] = undefined;
_allowedChannels = new Set();
_allowedModels = new Set();
_deniedModels = new Map();
constructor(_iModel) {
this._iModel = _iModel;
// for backwards compatibility, allow the shared channel unless explicitly turned off in IModelHostOptions.
if (IModelHost_1.IModelHost.configuration?.allowSharedChannel !== false)
this._allowedChannels.add(ChannelControl_1.ChannelControl.sharedChannelName);
}
addAllowedChannel(channelKey) {
this._allowedChannels.add(channelKey);
this._deniedModels.clear();
}
removeAllowedChannel(channelKey) {
this._allowedChannels.delete(channelKey);
this._allowedModels.clear();
}
getChannelKey(elementId) {
if (elementId === core_common_1.IModel.rootSubjectId)
return ChannelControl_1.ChannelControl.sharedChannelName;
try {
const channel = this._iModel.withQueryReader(`SELECT Owner FROM ${ChannelAdmin.channelClassName} WHERE Element.Id=?`, (reader) => {
return reader.step() ? reader.current[0] : undefined;
}, new core_common_1.QueryBinder().bindId(1, elementId));
if (channel !== undefined)
return channel;
}
catch {
// Exception happens if the iModel is too old: ChannelRootAspect class not present in the BisCore schema (older than v1.0.10).
// In that case all data in such iModel is assumed to be in the shared channel.
return ChannelControl_1.ChannelControl.sharedChannelName;
}
const parentId = this._iModel.withPreparedSqliteStatement("SELECT ParentId,ModelId FROM bis_Element WHERE id=?", (stmt) => {
stmt.bindId(1, elementId);
if (core_bentley_1.DbResult.BE_SQLITE_ROW !== stmt.step())
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, "Element does not exist");
return stmt.getValueId(0) ?? stmt.getValueId(1); // if parent is undefined, use modelId
});
return this.getChannelKey(parentId);
}
[Symbols_1._verifyChannel](modelId) {
// Note: indirect changes are permitted to change any channel
if (this._allowedModels.has(modelId) || this._iModel[Symbols_1._nativeDb].getTxnMode() === "indirect")
return;
const deniedChannel = this._deniedModels.get(modelId);
if (undefined !== deniedChannel)
core_common_1.ChannelControlError.throwError("not-allowed", `Channel ${deniedChannel} is not allowed`, deniedChannel);
const channel = this.getChannelKey(modelId);
if (this._allowedChannels.has(channel)) {
this._allowedModels.add(modelId);
return;
}
this._deniedModels.set(modelId, channel);
return this[Symbols_1._verifyChannel](modelId);
}
makeChannelRoot(args) {
const txn = args.txn ?? this._iModel[Symbols_1._implicitTxn];
const channelKey = this.getChannelKey(args.elementId);
if (ChannelControl_1.ChannelControl.sharedChannelName !== channelKey)
core_common_1.ChannelControlError.throwError("may-not-nest", `Channel ${channelKey} may not nest`, channelKey);
if (this.queryChannelRoot(args.channelKey) !== undefined)
core_common_1.ChannelControlError.throwError("root-exists", `Channel ${args.channelKey} root already exist`, channelKey);
const props = {
classFullName: ChannelAdmin.channelClassName,
element: {
id: args.elementId,
relClassName: NavigationRelationship_1.ElementOwnsChannelRootAspect.classFullName,
},
owner: args.channelKey,
};
txn.insertAspect(props);
}
insertChannelSubject(args) {
const txn = args.txn ?? this._iModel[Symbols_1._implicitTxn];
// Check if channelKey already exists before inserting Subject.
// makeChannelRoot will check that again, but at that point the new Subject is already inserted.
// Prefer to check twice instead of deleting the Subject in the latter option.
if (this.queryChannelRoot(args.channelKey) !== undefined)
core_common_1.ChannelControlError.throwError("root-exists", `Channel ${args.channelKey} root already exist`, args.channelKey);
const elementId = Element_1.Subject.insert(txn, args.parentSubjectId ?? core_common_1.IModel.rootSubjectId, args.subjectName, args.description);
this.makeChannelRoot({ elementId, channelKey: args.channelKey, txn });
return elementId;
}
queryChannelRoot(channelKey) {
if (channelKey === ChannelControl_1.ChannelControl.sharedChannelName)
// RootSubject acts as the ChannelRoot element of the shared channel
return core_common_1.IModel.rootSubjectId;
try {
const channelRoot = this._iModel.withQueryReader(`SELECT Element.Id FROM ${ChannelAdmin.channelClassName} WHERE Owner=?`, (reader) => {
return reader.step() ? reader.current[0] : undefined;
}, new core_common_1.QueryBinder().bindString(1, channelKey));
return channelRoot;
}
catch {
// Exception happens if the iModel is too old: ChannelRootAspect class not present in the BisCore schema (older than v1.0.10).
// In that case all data in such iModel is assumed to be in the shared channel.
return undefined;
}
}
async upgradeChannel(options, iModel, data) {
// Validations
if (!this._allowedChannels.has(options.channelKey))
core_common_1.ChannelControlError.throwError("not-allowed", `Channel ${options.channelKey} is not allowed`, options.channelKey);
if (semver.gte(options.fromVersion, options.toVersion))
core_common_1.ChannelControlError.throwError("not-allowed", `Upgrading channel ${options.channelKey} from ${options.fromVersion} to ${options.toVersion} is not allowed`, options.channelKey);
if (!this.queryChannelRoot(options.channelKey) && options.channelKey !== ChannelControl_1.ChannelControl.sharedChannelName)
core_common_1.ChannelControlError.throwError("not-allowed", `Channel ${options.channelKey} not found`, options.channelKey);
const context = {
iModel,
channelKey: options.channelKey,
fromVersion: options.fromVersion,
toVersion: options.toVersion,
data,
};
try {
await options.callback(context);
}
catch (error) {
core_common_1.ChannelControlError.throwError(error, "channel-upgrade-failed", `Channel ${options.channelKey} upgrade failed: ${error.message}`);
}
}
}
function createChannelControl(iModel) {
return new ChannelAdmin(iModel);
}
//# sourceMappingURL=ChannelAdmin.js.map