@itwin/core-backend
Version:
iTwin.js backend components
314 lines • 15.4 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 RpcInterface
*/
import { assert, BeDuration, BentleyStatus, CompressedId64Set, Id64, IModelStatus, Logger, } from "@itwin/core-bentley";
import { Code, ImageSourceFormat, IModel, IModelError, IModelReadRpcInterface, NoContentError, RpcInterface, RpcManager, RpcPendingResponse, SyncMode, ViewStoreRpc, } from "@itwin/core-common";
import { BackendLoggerCategory } from "../BackendLoggerCategory";
import { SpatialCategory } from "../Category";
import { ConcurrentQuery } from "../ConcurrentQuery";
import { CustomViewState3dCreator } from "../CustomViewState3dCreator";
import { generateGeometrySummaries } from "../GeometrySummary";
import { PromiseMemoizer } from "../PromiseMemoizer";
import { RpcTrace } from "../rpc/tracing";
import { ViewStateHydrator } from "../ViewStateHydrator";
import { RpcBriefcaseUtility } from "./RpcBriefcaseUtility";
import { _nativeDb } from "../internal/Symbols";
class ViewStateRequestMemoizer extends PromiseMemoizer {
_timeoutMs;
static _instance;
static async perform(props) {
if (!this._instance)
this._instance = new ViewStateRequestMemoizer();
return this._instance.perform(props);
}
constructor() {
const memoize = async (props) => {
const db = await RpcBriefcaseUtility.findOpenIModel(props.accessToken, props.tokenProps);
const viewCreator = new CustomViewState3dCreator(db);
return viewCreator.getCustomViewState3dData(props.options);
};
const stringify = (props) => {
const token = props.tokenProps;
const modelIds = props.options.modelIds;
return `${token.key}-${token.iTwinId}-${token.iModelId}-${token.changeset?.id}:${modelIds}`;
};
super(memoize, stringify);
this._timeoutMs = 20 * 1000;
}
async perform(props) {
const memo = this.memoize(props);
// Rejections must be caught so that the memoization entry is deleted.
await BeDuration.race(this._timeoutMs, memo.promise).catch(() => undefined);
if (memo.isPending)
throw new RpcPendingResponse(); // eslint-disable-line @typescript-eslint/only-throw-error
this.deleteMemoized(props);
if (memo.isFulfilled) {
assert(undefined !== memo.result);
return memo.result;
}
assert(memo.isRejected);
throw memo.error;
}
}
function currentActivity() {
return RpcTrace.expectCurrentActivity;
}
async function getIModelForRpc(tokenProps) {
return RpcBriefcaseUtility.findOpenIModel(RpcTrace.expectCurrentActivity.accessToken, tokenProps);
}
/** The backend implementation of IModelReadRpcInterface.
* @internal
*/
export class IModelReadRpcImpl extends RpcInterface {
static register() { RpcManager.registerImpl(IModelReadRpcInterface, IModelReadRpcImpl); }
async getConnectionProps(tokenProps) {
return RpcBriefcaseUtility.openWithTimeout(currentActivity(), tokenProps, SyncMode.FixedVersion);
}
async getCustomViewState3dData(tokenProps, options) {
const accessToken = currentActivity().accessToken;
return ViewStateRequestMemoizer.perform({ accessToken, tokenProps, options });
}
async hydrateViewState(tokenProps, options) {
const iModelDb = await getIModelForRpc(tokenProps);
const viewHydrater = new ViewStateHydrator(iModelDb);
return viewHydrater.getHydrateResponseProps(options);
}
async queryAllUsedSpatialSubCategories(tokenProps) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.queryAllUsedSpatialSubCategories();
}
async querySubCategories(tokenProps, compressedCategoryIds) {
const iModelDb = await getIModelForRpc(tokenProps);
const decompressedIds = CompressedId64Set.decompressArray(compressedCategoryIds);
return iModelDb.querySubCategories(decompressedIds);
}
async queryRows(tokenProps, request) {
const iModelDb = await getIModelForRpc(tokenProps);
if (iModelDb.isReadonly && request.usePrimaryConn === true) {
Logger.logWarning(BackendLoggerCategory.IModelDb, "usePrimaryConn is only supported on imodel that is opened in read/write mode. The option will be ignored.", request);
request.usePrimaryConn = false;
}
return ConcurrentQuery.executeQueryRequest(iModelDb[_nativeDb], request);
}
async queryBlob(tokenProps, request) {
const iModelDb = await getIModelForRpc(tokenProps);
if (iModelDb.isReadonly && request.usePrimaryConn === true) {
Logger.logWarning(BackendLoggerCategory.IModelDb, "usePrimaryConn is only supported on imodel that is opened in read/write mode. The option will be ignored.", request);
request.usePrimaryConn = false;
}
return ConcurrentQuery.executeBlobRequest(iModelDb[_nativeDb], request);
}
async queryModelRanges(tokenProps, modelIds) {
const results = await this.queryModelExtents(tokenProps, modelIds);
if (results.length === 1 && results[0].status !== IModelStatus.Success)
throw new IModelError(results[0].status, "error querying model range");
return results.filter((x) => x.status === IModelStatus.Success).map((x) => x.extents);
}
async queryModelExtents(tokenProps, modelIds) {
const iModel = await getIModelForRpc(tokenProps);
return iModel.models.queryExtents(modelIds);
}
async getModelProps(tokenProps, modelIdsList) {
const modelIds = new Set(modelIdsList);
const iModelDb = await getIModelForRpc(tokenProps);
const modelJsonArray = [];
for (const id of modelIds) {
try {
const modelProps = iModelDb.models.getModelProps(id);
modelJsonArray.push(modelProps);
}
catch (error) {
if (modelIds.size === 1)
throw error; // if they're asking for more than one model, don't throw on error.
}
}
return modelJsonArray;
}
async queryModelProps(tokenProps, params) {
const ids = await this.queryEntityIds(tokenProps, params);
return this.getModelProps(tokenProps, [...ids]);
}
async getElementProps(tokenProps, elementIdsList) {
const elementIds = new Set(elementIdsList);
const iModelDb = await getIModelForRpc(tokenProps);
const elementProps = [];
for (const id of elementIds) {
try {
elementProps.push(iModelDb.elements.getElementProps({ id }));
}
catch (error) {
if (elementIds.size === 1)
throw error; // if they're asking for more than one element, don't throw on error.
}
}
return elementProps;
}
async loadElementProps(tokenProps, identifier, options) {
const props = options ? { ...options } : {};
if (typeof identifier === "string") {
if (Id64.isId64(identifier))
props.id = identifier;
else
props.federationGuid = identifier;
}
else {
props.code = Code.fromJSON(identifier);
}
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.elements.tryGetElementProps(props);
}
async getGeometrySummary(tokenProps, request) {
const iModel = await getIModelForRpc(tokenProps);
return generateGeometrySummaries(request, iModel);
}
async queryElementProps(tokenProps, params) {
const ids = await this.queryEntityIds(tokenProps, params);
const res = this.getElementProps(tokenProps, [...ids]);
return res;
}
async queryEntityIds(tokenProps, params) {
const iModelDb = await getIModelForRpc(tokenProps);
const res = iModelDb.queryEntityIds(params);
return [...res];
}
async getClassHierarchy(tokenProps, classFullName) {
const iModelDb = await getIModelForRpc(tokenProps);
const classArray = [];
while (true) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
const classMetaData = iModelDb.getMetaData(classFullName);
classArray.push(classFullName);
if (!classMetaData.baseClasses || classMetaData.baseClasses.length === 0)
break;
classFullName = classMetaData.baseClasses[0];
}
return classArray;
}
async getAllCodeSpecs(tokenProps) {
const codeSpecs = [];
const iModelDb = await getIModelForRpc(tokenProps);
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement("SELECT ECInstanceId AS id, name, jsonProperties FROM BisCore.CodeSpec", (statement) => {
for (const row of statement)
codeSpecs.push({ id: row.id, name: row.name, jsonProperties: JSON.parse(row.jsonProperties) });
});
return codeSpecs;
}
async getViewStateData(tokenProps, viewDefinitionId, options) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.views.getViewStateProps(viewDefinitionId, options);
}
async readFontJson(tokenProps) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb[_nativeDb].readFontMap();
}
async requestSnap(tokenProps, sessionId, props) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.requestSnap(sessionId, props);
}
async cancelSnap(tokenProps, sessionId) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.cancelSnap(sessionId);
}
async getGeometryContainment(tokenProps, props) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.getGeometryContainment(props);
}
async getMassProperties(tokenProps, props) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.getMassProperties(props);
}
async getMassPropertiesPerCandidate(tokenProps, props) {
const iModelDb = await getIModelForRpc(tokenProps);
const getSingleCandidateMassProperties = async (candidate) => {
try {
const massPropResults = [];
for (const op of props.operations) {
const massProperties = await iModelDb.getMassProperties({ operation: op, candidates: [candidate] });
massPropResults.push(massProperties);
}
let singleCandidateResult = { status: BentleyStatus.ERROR, candidate }; // eslint-disable-line @typescript-eslint/no-deprecated
if (massPropResults.some((r) => r.status !== BentleyStatus.ERROR)) {
singleCandidateResult.status = BentleyStatus.SUCCESS;
for (const r of massPropResults.filter((mpr) => mpr.status !== BentleyStatus.ERROR)) {
singleCandidateResult = { ...singleCandidateResult, ...r };
}
}
return singleCandidateResult;
}
catch {
return { status: BentleyStatus.ERROR, candidate };
}
};
const promises = []; // eslint-disable-line @typescript-eslint/no-deprecated
for (const candidate of CompressedId64Set.iterable(props.candidates)) {
promises.push(getSingleCandidateMassProperties(candidate));
}
return Promise.all(promises);
}
async getToolTipMessage(tokenProps, id) {
const iModelDb = await getIModelForRpc(tokenProps);
const el = iModelDb.elements.getElement(id);
return (el === undefined) ? [] : el.getToolTipMessage();
}
/** Send a view thumbnail to the frontend. This is a binary transfer with the metadata in a 16-byte prefix header.
* @deprecated in 3.6.0 - might be removed in next major version. Use queryViewThumbnail instead
*/
async getViewThumbnail(tokenProps, viewId) {
const iModelDb = await getIModelForRpc(tokenProps);
const thumbnail = iModelDb.views.getThumbnail(viewId);
if (undefined === thumbnail || 0 === thumbnail.image.length)
throw new NoContentError();
const val = new Uint8Array(thumbnail.image.length + 16); // allocate a new buffer 16 bytes larger than the image size
new Uint32Array(val.buffer, 0, 4).set([thumbnail.image.length, thumbnail.format === "jpeg" ? ImageSourceFormat.Jpeg : ImageSourceFormat.Png, thumbnail.width, thumbnail.height]); // Put the metadata in the first 16 bytes.
val.set(thumbnail.image, 16); // put the image data at offset 16 after metadata
return val;
}
async getDefaultViewId(tokenProps) {
const iModelDb = await getIModelForRpc(tokenProps);
const spec = { namespace: "dgn_View", name: "DefaultView" };
const blob = iModelDb.queryFilePropertyBlob(spec);
if (undefined === blob || 8 !== blob.length)
return Id64.invalid;
const view = new Uint32Array(blob.buffer);
return Id64.fromUint32Pair(view[0], view[1]);
}
async getSpatialCategoryId(tokenProps, categoryName) {
const iModelDb = await getIModelForRpc(tokenProps);
const dictionary = iModelDb.models.getModel(IModel.dictionaryId);
return SpatialCategory.queryCategoryIdByName(iModelDb, dictionary.id, categoryName);
}
async getIModelCoordinatesFromGeoCoordinates(tokenProps, props) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.getIModelCoordinatesFromGeoCoordinates(props);
}
async getGeoCoordinatesFromIModelCoordinates(tokenProps, props) {
const iModelDb = await getIModelForRpc(tokenProps);
return iModelDb.getGeoCoordinatesFromIModelCoordinates(props);
}
async queryTextureData(tokenProps, textureLoadProps) {
const db = await getIModelForRpc(tokenProps);
return db.queryTextureData(textureLoadProps);
}
async generateElementMeshes(tokenProps, props) {
const db = await getIModelForRpc(tokenProps);
return db[_nativeDb].generateElementMeshes(props);
}
/** @internal */
async callViewStore(tokenProps, version, forWrite, methodName, ...args) {
if (!RpcInterface.isVersionCompatible(ViewStoreRpc.version, version))
throw new Error("ViewStoreRpc version mismatch");
const db = await getIModelForRpc(tokenProps);
const viewStore = await db.views.accessViewStore({ accessLevel: forWrite ? "write" : "read" });
const access = viewStore[forWrite ? "writeLocker" : "reader"];
const func = access[methodName];
if (typeof func !== "function")
throw new IModelError(IModelStatus.FunctionNotFound, `Illegal ViewStore RPC call "${methodName}"`);
return func.call(access, ...args);
}
}
//# sourceMappingURL=IModelReadRpcImpl.js.map