packageDocumentation module import deepAssign from "deep-assign" ; import { AccessToken , BentleyError , GuidString , IModelHubStatus , Logger } from "@itwin/core-bentley" ; import { FileHandler } from "../itwin-client/FileHandler" ; import { ProgressCallback } from "../itwin-client/Request" ; import { ECJsonTypeMap , WsgInstance } from "../wsg/ECJsonTypeMap" ; import { IModelHubClientLoggerCategory } from "../IModelHubClientLoggerCategories" ; import { IModelBaseHandler } from "./BaseHandler" ; import { ArgumentCheck , IModelHubClientError , IModelHubError } from "./Errors" ; import { addSelectFileAccessKey , InstanceIdQuery } from "./HubQuery" ; const loggerCategory = IModelHubClientLoggerCategory . IModelHub ; internal @ ECJsonTypeMap . classToJson ( "wsg" , "ContextScope.iModel" , { schemaPropertyName : "schemaName" , classPropertyName : "className" } ) export class HubIModel extends WsgInstance { /** Id of the iModel. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "instanceId" ) public id ? : GuidString ; /** Description of the iModel. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.Description" ) public description ? : string ; /** Name of the iModel. iModels must have unique names per iTwin. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.Name" ) public name ? : string ; /** Id of the user that created the iModel. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.UserCreated" ) public userCreated ? : string ; /** Date when iModel was created. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.CreatedDate" ) public createdDate ? : string ; /** Set to true, when iModel is ready to be used. See [[IModelHandler.create]]. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.Initialized" ) public initialized ? : boolean ; internal @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.iModelTemplate" ) public iModelTemplate ? : string ; /** Type of the iModel */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.Type" ) public iModelType ? : IModelType ; /** Extent of iModel. Array of coordinates: [0] - south latitude, [1] - west longitude, [2] - north latitude, [3] - east longitude */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.Extent" ) public extent ? : number [ ] ; /** Set to true, when iModel has custom access control. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.Secured" ) public secured ? : boolean ; /** Data center location id where iModel is stored. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.DataLocationId" ) public dataLocationId ? : string ; } public export enum InitializationState { /** Initialization was successful. */ Successful = 0 , /** Initialization has not started, seed file has not yet been uploaded. */ NotStarted = 1 , /** Initialization has been scheduled and has not completed yet. */ Scheduled = 2 , /** Initialization failed with a generic error. */ Failed = 3 , /** Initialization failed due to file having outdated schemas. */ OutdatedFile = 4 , /** Initialization failed due to file having [[Code]] values that are too long. */ CodeTooLong = 5 , /** Initialization failed due to file being a [[Briefcase]]. Only standalone and master files are supported for iModel creation, see [BriefcaseId]($common). */ SeedFileIsBriefcase = 6 } public export enum IModelType { /** iModel has no type. */ Undefined = 0 , /** iModel contains metadata used for other iModel creation. */ Library = 1 } internal @ ECJsonTypeMap . classToJson ( "wsg" , "iModelScope.SeedFile" , { schemaPropertyName : "schemaName" , classPropertyName : "className" } ) export class SeedFile extends WsgInstance { /** Id of the iModel. */ @ ECJsonTypeMap . propertyToJson ( "wsg" , "instanceId" ) public id ? : GuidString ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.FileName" ) public fileName ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.FileDescription" ) public fileDescription ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.FileSize" ) public fileSize ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.FileId" ) public fileId ? : GuidString ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.Index" ) public index ? : number ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.IModelName" ) public iModelName ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.MergedChangeSetId" ) public mergedChangeSetId ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.UserUploaded" ) public userUploaded ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.UploadedDate" ) public uploadedDate ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.IsUploaded" ) public isUploaded ? : boolean ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "properties.InitializationState" ) public initializationState ? : InitializationState ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "relationshipInstances[FileAccessKey].relatedInstance[AccessKey].properties.DownloadUrl" ) public downloadUrl ? : string ; @ ECJsonTypeMap . propertyToJson ( "wsg" , "relationshipInstances[FileAccessKey].relatedInstance[AccessKey].properties.UploadUrl" ) public uploadUrl ? : string ; } /** Query object for getting SeedFiles. You can use this to modify the query. See [[SeedFileHandler.get]]. */ class SeedFileQuery extends InstanceIdQuery { returns public selectDownloadUrl ( ) { addSelectFileAccessKey ( this . _query ) ; return this ; } returns public latest ( ) { this . _query . $orderby = "Index+desc" ; return this ; } } /** Handler for all methods related to @see SeedFile instances. */ class SeedFileHandler { private _handler : IModelBaseHandler ; private _fileHandler ? : FileHandler ; param handler param fileHandler constructor ( handler , fileHandler ? ) { this . _handler = handler ; this . _fileHandler = fileHandler ; } param iModelId param fileId private getRelativeUrl ( iModelId , fileId ? ) { return `/Repositories/iModel--${ iModelId }/iModelScope/SeedFile/${ fileId || "" }` ; } param accessToken param iModelId param query returns public async get ( accessToken , iModelId , query = new SeedFileQuery ( ) ) { Logger . logInfo ( loggerCategory , "Started querying seed files" , ( ) => ( { iModelId } ) ) ; const seedFiles = await this . _handler . getInstances < SeedFile > ( accessToken , SeedFile , this . getRelativeUrl ( iModelId , query . getId ( ) ) , query . getQueryOptions ( ) ) ; Logger . logInfo ( loggerCategory , "Finished querying seed files" , ( ) => ( { iModelId , count : seedFiles . length } ) ) ; return seedFiles ; } param accessToken param iModelId param seedFile param seedPath param progressCallback public async uploadSeedFile ( accessToken , iModelId , seedPath , seedFileDescription ? , progressCallback ? ) { Logger . logInfo ( loggerCategory , "Started uploading seed file" , ( ) => ( { iModelId , seedPath } ) ) ; const seedFile = new SeedFile ( ) ; seedFile . fileName = this . _fileHandler ! . basename ( seedPath ) ; seedFile . fileSize = this . _fileHandler ! . getFileSize ( seedPath ) . toString ( ) ; if ( seedFileDescription ) seedFile . fileDescription = seedFileDescription ; const createdSeedFile = await this . _handler . postInstance < SeedFile > ( accessToken , SeedFile , this . getRelativeUrl ( iModelId ) , seedFile ) ; await this . _fileHandler ! . uploadFile ( accessToken , createdSeedFile . uploadUrl ! , seedPath , progressCallback ) ; createdSeedFile . uploadUrl = undefined ; createdSeedFile . downloadUrl = undefined ; createdSeedFile . isUploaded = true ; const confirmSeedFile = await this . _handler . postInstance < SeedFile > ( accessToken , SeedFile , this . getRelativeUrl ( iModelId , createdSeedFile . id ) , createdSeedFile ) ; Logger . logTrace ( loggerCategory , "Finished uploading seed file" , ( ) => confirmSeedFile ) ; return confirmSeedFile ; } } internal export class IModelQuery extends InstanceIdQuery { param name returns throws public byName ( name ) { ArgumentCheck . defined ( "name" , name ) ; this . addFilter ( `Name+eq+'${ encodeURIComponent ( name ) }'` ) ; return this ; } param iModelType returns throws public byiModelType ( iModelType ) { ArgumentCheck . defined ( "iModelType" , iModelType ) ; this . addFilter ( `Type+eq+${ iModelType }` ) ; return this ; } param type returns throws internal public byiModelTemplate ( iModelTemplate ) { ArgumentCheck . defined ( "iModelTemplate" , iModelTemplate , true ) ; this . addFilter ( `iModelTemplate+eq+'${ encodeURIComponent ( iModelTemplate ) }'` ) ; return this ; } } internal export interface CloneIModelTemplate { /** Source iModel's Id. */ imodelId : string ; /** Id of the [[ChangeSet]] on source iModel that should be used as the baseline version. */ changeSetId ? : string ; } internal export type EmptyIModelTemplate = "Empty" ; const iModelTemplateEmpty = "Empty" ; public export interface IModelCreateOptions { /** iModel seed file path. If not defined, iModel will be created from template. */ path ? : string ; /** Description of the iModel on the Hub. */ description ? : string ; /** Callback for tracking progress. */ progressCallback ? : ProgressCallback ; /**
      * Time to wait for iModel initialization. When it times out the initialization will continue, but the promise will be rejected.
      * Default is 2 minutes.
      */ timeOutInMilliseconds ? : number ; internal template ? : CloneIModelTemplate | EmptyIModelTemplate ; /** Type of iModel. */ iModelType ? : IModelType ; /** Extent of iModel. Array of coordinates: [0] - south latitude, [1] - west longitude, [2] - north latitude, [3] - east longitude */ extent ? : number [ ] ; } internal export class DefaultIModelCreateOptionsProvider { protected _defaultOptions : IModelCreateOptions ; /**  Creates an instance of DefaultRequestOptionsProvider and sets up the default options. */ constructor ( ) { this . _defaultOptions = { timeOutInMilliseconds : 120000 } ; } param options public async assignOptions ( options ) { const clonedOptions = { ... options } ; deepAssign ( options , this . _defaultOptions ) ; deepAssign ( options , clonedOptions ) ; if ( ! options . template ) options . template = iModelTemplateEmpty ; } param options public templateToString ( options ) { if ( ! options . template || options . template === iModelTemplateEmpty ) return options . template ; return `${ options . template . imodelId }:${ options . template . changeSetId || "" }` ; } } note internal export class IModelsHandler { private _handler : IModelBaseHandler ; private _fileHandler ? : FileHandler ; private _seedFileHandler : SeedFileHandler ; private static _defaultCreateOptionsProvider : DefaultIModelCreateOptionsProvider ; private static readonly _imodelExtentLength : number = 4 ; private static readonly _imodelExtentLatitudeLimit : number = 90 ; private static readonly _imodelExtentLongitudeLimit : number = 180 ; param handler param fileHandler note internal constructor ( handler , fileHandler ? ) { this . _handler = handler ; this . _fileHandler = fileHandler ; this . _seedFileHandler = new SeedFileHandler ( this . _handler , this . _fileHandler ) ; } param iTwinId param iModelId private getRelativeUrl ( iTwinId , iModelId ? ) { return `/Repositories/Context--${ this . _handler . formatITwinIdForUrl ( iTwinId ) }/ContextScope/iModel/${ iModelId || "" }` ; } param accessToken param iTwinId param query returns throws throws public async get ( accessToken , iTwinId , query = new IModelQuery ( ) ) { Logger . logInfo ( loggerCategory , `Started querying iModels in context` , ( ) => ( { iTwinId } ) ) ; ArgumentCheck . defined ( "iTwinId" , iTwinId ) ; const imodels = await this . _handler . getInstances < HubIModel > ( accessToken , HubIModel , this . getRelativeUrl ( iTwinId , query . getId ( ) ) , query . getQueryOptions ( ) ) ; Logger . logInfo ( loggerCategory , `Finished querying iModels in context` , ( ) => ( { iTwinId , count : imodels . length } ) ) ; return imodels ; } param accessToken param iTwinId param iModelId throws throws throws public async delete ( accessToken , iTwinId , iModelId ) { Logger . logInfo ( loggerCategory , "Started deleting iModel" , ( ) => ( { iModelId , iTwinId } ) ) ; ArgumentCheck . validGuid ( "iTwinId" , iTwinId ) ; ArgumentCheck . validGuid ( "iModelId" , iModelId ) ; if ( this . _handler . getCustomRequestOptions ( ) . isSet ) { const imodel = new HubIModel ( ) ; imodel . id = iModelId ; imodel . changeState = "deleted" ; await this . _handler . deleteInstance ( accessToken , this . getRelativeUrl ( iTwinId , iModelId ) , imodel ) ; } else { await this . _handler . delete ( accessToken , this . getRelativeUrl ( iTwinId , iModelId ) ) ; } Logger . logInfo ( loggerCategory , "Finished deleting iModel" , ( ) => ( { iModelId , iTwinId } ) ) ; } param iTwinId param iModelName param description param iModelTemplate param iModelType private async createIModelInstance ( accessToken , iTwinId , iModelName , description ? , iModelTemplate ? , iModelType ? , extent ? ) { Logger . logInfo ( loggerCategory , `Creating iModel with name ${ iModelName }` , ( ) => ( { iTwinId } ) ) ; let imodel ; const iModel = new HubIModel ( ) ; iModel . name = iModelName ; if ( description ) iModel . description = description ; if ( iModelTemplate ) iModel . iModelTemplate = iModelTemplate ; if ( extent ) iModel . extent = extent ; if ( iModelType ) iModel . iModelType = iModelType ; try { imodel = await this . _handler . postInstance < HubIModel > ( accessToken , HubIModel , this . getRelativeUrl ( iTwinId ) , iModel ) ; Logger . logTrace ( loggerCategory , `Created iModel instance with name ${ iModelName }` , ( ) => ( { iTwinId } ) ) ; } catch ( err ) { if ( ! ( err instanceof IModelHubError ) || IModelHubStatus . iModelAlreadyExists !== err . errorNumber ) { Logger . logWarning ( loggerCategory , `Can not create iModel: ${ BentleyError . getErrorMessage ( err ) }` , ( ) => ( { iTwinId } ) ) ; throw err ; } const initialized = err . data . iModelInitialized ; if ( initialized ) { Logger . logWarning ( loggerCategory , `Error creating iModel: iModel with name ${ iModelName } already exists and is initialized` , ( ) => ( { iTwinId } ) ) ; throw err ; } Logger . logInfo ( loggerCategory , `Querying iModel by name ${ iModelName }` , ( ) => ( { iTwinId } ) ) ; const imodels = await this . get ( accessToken , iTwinId , new IModelQuery ( ) . byName ( iModelName ) ) ; Logger . logTrace ( loggerCategory , `Queried iModel by name ${ iModelName }` , ( ) => ( { iTwinId } ) ) ; if ( imodels . length > 0 ) { imodel = imodels [ 0 ] ; } else { Logger . logTrace ( loggerCategory , `iModel by name: iModel ${ iModelName } not found` , ( ) => ( { iTwinId } ) ) ; throw new Error ( `iModel by name: iModel ${ iModelName } not found` ) ; } } return imodel ; } param accessToken param iModelId returns throws throws internal public async getInitializationState ( accessToken , iModelId ) { const seedFiles = await this . _seedFileHandler . get ( accessToken , iModelId , new SeedFileQuery ( ) . latest ( ) ) ; if ( seedFiles . length < 1 ) throw new IModelHubError ( IModelHubStatus . FileDoesNotExist ) ; return seedFiles [ 0 ] . initializationState ! ; } param options returns private async setupOptionDefaults ( options ) { if ( ! IModelsHandler . _defaultCreateOptionsProvider ) IModelsHandler . _defaultCreateOptionsProvider = new DefaultIModelCreateOptionsProvider ( ) ; return IModelsHandler . _defaultCreateOptionsProvider . assignOptions ( options ) ; } param iTwinId param imodel param timeOutInMilliseconds private async waitForInitialization ( accessToken , iTwinId , imodel , timeOutInMilliseconds ) { const errorMessage = "iModel initialization failed" ; const retryDelay = timeOutInMilliseconds / 10 ; for ( let retries = 10 ; retries > 0 ; -- retries ) { try { const initState = await this . getInitializationState ( accessToken , imodel . id ! ) ; if ( initState === InitializationState . Successful ) { Logger . logTrace ( loggerCategory , "Created iModel" , ( ) => ( { iTwinId , iModelId : imodel . id } ) ) ; imodel . initialized = true ; return imodel ; } if ( initState !== InitializationState . NotStarted && initState !== InitializationState . Scheduled ) { Logger . logWarning ( loggerCategory , errorMessage ) ; throw new IModelHubError ( IModelHubStatus . SeedFileInitializationFailed , `Seed file initialization failed with status ${ InitializationState [ initState ] }` ) ; } await new Promise ( ( resolve ) => setTimeout ( resolve , retryDelay ) ) ; } catch ( err ) { Logger . logWarning ( loggerCategory , errorMessage ) ; throw err ; } } Logger . logWarning ( loggerCategory , errorMessage ) ; throw IModelHubClientError . initializationTimeout ( ) ; } param extent throws private validateExtent ( extent ) { if ( extent . length === 0 ) return ; if ( extent . length !== IModelsHandler . _imodelExtentLength ) throw IModelHubClientError . invalidArgument ( "extent" ) ; this . validateExtentCoordinate ( extent [ 0 ] , IModelsHandler . _imodelExtentLatitudeLimit ) ; this . validateExtentCoordinate ( extent [ 1 ] , IModelsHandler . _imodelExtentLongitudeLimit ) ; this . validateExtentCoordinate ( extent [ 2 ] , IModelsHandler . _imodelExtentLatitudeLimit ) ; this . validateExtentCoordinate ( extent [ 3 ] , IModelsHandler . _imodelExtentLongitudeLimit ) ; } param coordinate param limit throws private validateExtentCoordinate ( coordinate , limit ) { if ( coordinate < - limit || coordinate > limit ) throw IModelHubClientError . invalidArgument ( "extent" ) ; } param accessToken param iTwinId param name param createOptions throws throws internal public async create ( accessToken , iTwinId , name , createOptions ? ) { Logger . logInfo ( loggerCategory , "Creating iModel" , ( ) => ( { iTwinId } ) ) ; ArgumentCheck . validGuid ( "iTwinId" , iTwinId ) ; ArgumentCheck . defined ( "name" , name ) ; createOptions = createOptions || { } ; await this . setupOptionDefaults ( createOptions ) ; if ( createOptions . extent ) this . validateExtent ( createOptions . extent ) ; if ( createOptions . path ) { createOptions . template = undefined ; if ( typeof window !== "undefined" ) throw IModelHubClientError . browser ( ) ; if ( ! this . _fileHandler ) throw IModelHubClientError . fileHandler ( ) ; if ( ! this . _fileHandler . exists ( createOptions . path ) || this . _fileHandler . isDirectory ( createOptions . path ) ) throw IModelHubClientError . fileNotFound ( ) ; } const template = IModelsHandler . _defaultCreateOptionsProvider . templateToString ( createOptions ) ; const imodel = await this . createIModelInstance ( accessToken , iTwinId , name , createOptions . description , template , createOptions . iModelType , createOptions . extent ) ; if ( createOptions . template === iModelTemplateEmpty ) { return imodel ; } if ( ! createOptions . template ) { try { await this . _seedFileHandler . uploadSeedFile ( accessToken , imodel . id ! , createOptions . path ! , createOptions . description , createOptions . progressCallback ) ; } catch ( err ) { await this . delete ( accessToken , iTwinId , imodel . id ! ) ; throw err ; } } return this . waitForInitialization ( accessToken , iTwinId , imodel , createOptions . timeOutInMilliseconds ! ) ; } param accessToken param iTwinId param imodel throws throws throws throws throws public async update ( accessToken , iTwinId , imodel ) { Logger . logInfo ( loggerCategory , "Updating iModel" , ( ) => ( { iTwinId , iModelId : imodel . id } ) ) ; ArgumentCheck . validGuid ( "iTwinId" , iTwinId ) ; const updatedIModel = await this . _handler . postInstance < HubIModel > ( accessToken , HubIModel , this . getRelativeUrl ( iTwinId , imodel . id ) , imodel ) ; Logger . logTrace ( loggerCategory , "Updated iModel" , ( ) => ( { iTwinId , iModelId : imodel . id } ) ) ; return updatedIModel ; } param accessToken param iModelId param path param progressCallback throws internal public async download ( accessToken , iModelId , path , progressCallback ? ) { Logger . logInfo ( loggerCategory , "Started downloading seed file" , ( ) => ( { iModelId } ) ) ; ArgumentCheck . validGuid ( "iModelId" , iModelId ) ; ArgumentCheck . defined ( "path" , path ) ; if ( typeof window !== "undefined" ) throw IModelHubClientError . browser ( ) ; if ( ! this . _fileHandler ) throw IModelHubClientError . fileHandler ( ) ; const seedFiles = await this . _seedFileHandler . get ( accessToken , iModelId , new SeedFileQuery ( ) . selectDownloadUrl ( ) . latest ( ) ) ; if ( ! seedFiles || ! seedFiles [ 0 ] || ! seedFiles [ 0 ] . downloadUrl ) throw IModelHubError . fromId ( IModelHubStatus . FileDoesNotExist , "Failed to get seed file." ) ; await this . _fileHandler . downloadFile ( accessToken , seedFiles [ 0 ] . downloadUrl , path , parseInt ( seedFiles [ 0 ] . fileSize ! , 10 ) , progressCallback ) ; Logger . logInfo ( loggerCategory , "Finished downloading seed file" , ( ) => ( { iModelId } ) ) ; } } note internal export class IModelHandler { private _handler : IModelsHandler ; param handler note internal constructor ( handler ) { this . _handler = handler ; } param accessToken param iTwinId returns throws throws public async get ( accessToken , iTwinId ) { Logger . logInfo ( loggerCategory , "Querying iModel" , ( ) => ( { iTwinId } ) ) ; const query = new IModelQuery ( ) . orderBy ( "CreatedDate+asc" ) . top ( 1 ) ; const imodels = await this . _handler . get ( accessToken , iTwinId , query ) ; if ( imodels . length < 1 ) throw new IModelHubError ( IModelHubStatus . iModelDoesNotExist ) ; return imodels [ 0 ] ; } param accessToken param iTwinId throws throws throws public async delete ( accessToken , iTwinId ) { const imodel = await this . get ( accessToken , iTwinId ) ; await this . _handler . delete ( accessToken , iTwinId , imodel . id ! ) ; } param accessToken param iTwinId returns throws throws throws internal public async getInitializationState ( accessToken , iTwinId ) { const imodel = await this . get ( accessToken , iTwinId ) ; return this . _handler . getInitializationState ( accessToken , imodel . id ! ) ; } param accessToken param iTwinId param name param createOptions throws throws internal public async create ( accessToken , iTwinId , name , createOptions ? ) { let imodelExists = true ; try { await this . get ( accessToken , iTwinId ) ; } catch ( err ) { if ( err instanceof IModelHubError && err . errorNumber === IModelHubStatus . iModelDoesNotExist ) imodelExists = false ; else throw err ; } if ( imodelExists ) throw new IModelHubError ( IModelHubStatus . iModelAlreadyExists ) ; return this . _handler . create ( accessToken , iTwinId , name , createOptions ) ; } param accessToken param iTwinId param imodel throws throws throws throws throws public async update ( accessToken , iTwinId , imodel ) { return this . _handler . update ( accessToken , iTwinId , imodel ) ; } param accessToken param iTwinId param path param progressCallback throws throws internal public async download ( accessToken , iTwinId , path , progressCallback ? ) { const imodel = await this . get ( accessToken , iTwinId ) ; await this . _handler . download ( accessToken , imodel . id ! , path , progressCallback ) ; } }