1 | /*
|
2 | * Licensed under the Apache License, Version 2.0 (the "License");
|
3 | * you may not use this file except in compliance with the License.
|
4 | * You may obtain a copy of the License at
|
5 | *
|
6 | * http://www.apache.org/licenses/LICENSE-2.0
|
7 | *
|
8 | * Unless required by applicable law or agreed to in writing, software
|
9 | * distributed under the License is distributed on an "AS IS" BASIS,
|
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11 | * See the License for the specific language governing permissions and
|
12 | * limitations under the License.
|
13 | */
|
14 |
|
15 | ;
|
16 |
|
17 | const fs = require('fs');
|
18 | const fsPath = require('path');
|
19 | const slash = require('slash');
|
20 |
|
21 | const DefaultModelFileLoader = require('./introspect/loaders/defaultmodelfileloader');
|
22 | const Factory = require('./factory');
|
23 | const Globalize = require('./globalize');
|
24 | const IllegalModelException = require('./introspect/illegalmodelexception');
|
25 | const ModelFile = require('./introspect/modelfile');
|
26 | const ModelFileDownloader = require('./introspect/loaders/modelfiledownloader');
|
27 | const ModelUtil = require('./modelutil');
|
28 | const Serializer = require('./serializer');
|
29 | const TypeNotFoundException = require('./typenotfoundexception');
|
30 |
|
31 | const debug = require('debug')('concerto:ModelManager');
|
32 |
|
33 | /**
|
34 | * Manages the Concerto model files.
|
35 | *
|
36 | * The structure of {@link Resource}s (Assets, Transactions, Participants) is modelled
|
37 | * in a set of Concerto files. The contents of these files are managed
|
38 | * by the {@link ModelManager}. Each Concerto file has a single namespace and contains
|
39 | * a set of asset, transaction and participant type definitions.
|
40 | *
|
41 | * Concerto applications load their Concerto files and then call the {@link ModelManager#addModelFile addModelFile}
|
42 | * method to register the Concerto file(s) with the ModelManager.
|
43 | *
|
44 | * Use the {@link Concerto} class to validate instances.
|
45 | *
|
46 | * @class
|
47 | * @memberof module:concerto-core
|
48 | */
|
49 | class ModelManager {
|
50 | /**
|
51 | * Create the ModelManager.
|
52 | * @param {object} [options] - Serializer options
|
53 | */
|
54 | constructor(options) {
|
55 | this.modelFiles = {};
|
56 | this.factory = new Factory(this);
|
57 | this.serializer = new Serializer(this.factory, this, options);
|
58 | this.decoratorFactories = [];
|
59 | this._isModelManager = true;
|
60 | this.addRootModel();
|
61 | }
|
62 |
|
63 | /**
|
64 | * Adds root types
|
65 | * @private
|
66 | */
|
67 | addRootModel() {
|
68 | this.addModelFile( `namespace concerto
|
69 | abstract concept Concept {}
|
70 | abstract concept Asset identified {}
|
71 | abstract concept Participant identified {}
|
72 | abstract concept Transaction {}
|
73 | abstract concept Event {}
|
74 | `, 'concerto.cto');
|
75 | }
|
76 |
|
77 | /**
|
78 | * Visitor design pattern
|
79 | * @param {Object} visitor - the visitor
|
80 | * @param {Object} parameters - the parameter
|
81 | * @return {Object} the result of visiting or null
|
82 | */
|
83 | accept(visitor, parameters) {
|
84 | return visitor.visit(this, parameters);
|
85 | }
|
86 |
|
87 | /**
|
88 | * Validates a Concerto file (as a string) to the ModelManager.
|
89 | * Concerto files have a single namespace.
|
90 | *
|
91 | * Note that if there are dependencies between multiple files the files
|
92 | * must be added in dependency order, or the addModelFiles method can be
|
93 | * used to add a set of files irrespective of dependencies.
|
94 | * @param {string} modelFile - The Concerto file as a string
|
95 | * @param {string} [fileName] - a file name to associate with the model file
|
96 | * @throws {IllegalModelException}
|
97 | */
|
98 | validateModelFile(modelFile, fileName) {
|
99 | if (typeof modelFile === 'string') {
|
100 | let m = new ModelFile(this, modelFile, fileName);
|
101 | m.validate();
|
102 | } else {
|
103 | modelFile.validate();
|
104 | }
|
105 | }
|
106 |
|
107 | /**
|
108 | * Throws an error with details about the existing namespace.
|
109 | * @param {ModelFile} modelFile The model file that is trying to declare an existing namespace
|
110 | * @private
|
111 | */
|
112 | _throwAlreadyExists(modelFile) {
|
113 | const existingModelFileName = this.modelFiles[modelFile.getNamespace()].getName();
|
114 | const postfix = existingModelFileName ? ` in file ${existingModelFileName}` : '';
|
115 | const prefix = modelFile.getName() ? ` specified in file ${modelFile.getName()}` : '';
|
116 | let errMsg = `Namespace ${modelFile.getNamespace()}${prefix} is already declared${postfix}`;
|
117 | throw new Error(errMsg);
|
118 | }
|
119 |
|
120 | /**
|
121 | * Adds a Concerto file (as a string) to the ModelManager.
|
122 | * Concerto files have a single namespace. If a Concerto file with the
|
123 | * same namespace has already been added to the ModelManager then it
|
124 | * will be replaced.
|
125 | * Note that if there are dependencies between multiple files the files
|
126 | * must be added in dependency order, or the addModelFiles method can be
|
127 | * used to add a set of files irrespective of dependencies.
|
128 | * @param {string} modelFile - The Concerto file as a string
|
129 | * @param {string} fileName - an optional file name to associate with the model file
|
130 | * @param {boolean} [disableValidation] - If true then the model files are not validated
|
131 | * @throws {IllegalModelException}
|
132 | * @return {Object} The newly added model file (internal).
|
133 | */
|
134 | addModelFile(modelFile, fileName, disableValidation) {
|
135 | const NAME = 'addModelFile';
|
136 | debug(NAME, 'addModelFile', modelFile, fileName);
|
137 |
|
138 | let m = null;
|
139 |
|
140 | if (typeof modelFile === 'string') {
|
141 | m = new ModelFile(this, modelFile, fileName);
|
142 | } else {
|
143 | m = modelFile;
|
144 | }
|
145 |
|
146 | if (!this.modelFiles[m.getNamespace()]) {
|
147 | if (!disableValidation) {
|
148 | m.validate();
|
149 | }
|
150 | this.modelFiles[m.getNamespace()] = m;
|
151 | } else {
|
152 | this._throwAlreadyExists(m);
|
153 | }
|
154 |
|
155 | return m;
|
156 | }
|
157 |
|
158 | /**
|
159 | * Updates a Concerto file (as a string) on the ModelManager.
|
160 | * Concerto files have a single namespace. If a Concerto file with the
|
161 | * same namespace has already been added to the ModelManager then it
|
162 | * will be replaced.
|
163 | * @param {string} modelFile - The Concerto file as a string
|
164 | * @param {string} [fileName] - a file name to associate with the model file
|
165 | * @param {boolean} [disableValidation] - If true then the model files are not validated
|
166 | * @throws {IllegalModelException}
|
167 | * @returns {Object} The newly added model file (internal).
|
168 | */
|
169 | updateModelFile(modelFile, fileName, disableValidation) {
|
170 | const NAME = 'updateModelFile';
|
171 | debug(NAME, 'updateModelFile', modelFile, fileName);
|
172 | if (typeof modelFile === 'string') {
|
173 | let m = new ModelFile(this, modelFile, fileName);
|
174 | return this.updateModelFile(m,fileName,disableValidation);
|
175 | } else {
|
176 | let existing = this.modelFiles[modelFile.getNamespace()];
|
177 | if (!existing) {
|
178 | throw new Error(`Model file for namespace ${modelFile.getNamespace()} not found`);
|
179 | }
|
180 | if (!disableValidation) {
|
181 | modelFile.validate();
|
182 | }
|
183 | }
|
184 | this.modelFiles[modelFile.getNamespace()] = modelFile;
|
185 | return modelFile;
|
186 | }
|
187 |
|
188 | /**
|
189 | * Remove the Concerto file for a given namespace
|
190 | * @param {string} namespace - The namespace of the model file to delete.
|
191 | */
|
192 | deleteModelFile(namespace) {
|
193 | if (!this.modelFiles[namespace]) {
|
194 | throw new Error('Model file does not exist');
|
195 | } else {
|
196 | delete this.modelFiles[namespace];
|
197 | }
|
198 | }
|
199 |
|
200 | /**
|
201 | * Add a set of Concerto files to the model manager.
|
202 | * @param {object[]} modelFiles - An array of Concerto files as strings or ModelFile objects.
|
203 | * @param {string[]} [fileNames] - A array of file names to associate with the model files
|
204 | * @param {boolean} [disableValidation] - If true then the model files are not validated
|
205 | * @returns {Object[]} The newly added model files (internal).
|
206 | */
|
207 | addModelFiles(modelFiles, fileNames, disableValidation) {
|
208 | const NAME = 'addModelFiles';
|
209 | debug(NAME, 'addModelFiles', modelFiles, fileNames);
|
210 | const originalModelFiles = {};
|
211 | Object.assign(originalModelFiles, this.modelFiles);
|
212 | let newModelFiles = [];
|
213 |
|
214 | try {
|
215 | // create the model files
|
216 | for (let n = 0; n < modelFiles.length; n++) {
|
217 | const modelFile = modelFiles[n];
|
218 | let fileName = null;
|
219 |
|
220 | if (fileNames) {
|
221 | fileName = fileNames[n];
|
222 | }
|
223 |
|
224 | const m = typeof modelFile === 'string' ? new ModelFile(this, modelFile, fileName) : modelFile;
|
225 | if (!this.modelFiles[m.getNamespace()]) {
|
226 | this.modelFiles[m.getNamespace()] = m;
|
227 | newModelFiles.push(m);
|
228 | } else {
|
229 | this._throwAlreadyExists(m);
|
230 | }
|
231 | }
|
232 |
|
233 | // re-validate all the model files
|
234 | if (!disableValidation) {
|
235 | this.validateModelFiles();
|
236 | }
|
237 |
|
238 | // return the model files.
|
239 | return newModelFiles;
|
240 | } catch (err) {
|
241 | this.modelFiles = {};
|
242 | Object.assign(this.modelFiles, originalModelFiles);
|
243 | throw err;
|
244 | } finally {
|
245 | debug(NAME, newModelFiles);
|
246 | }
|
247 | }
|
248 |
|
249 |
|
250 | /**
|
251 | * Validates all models files in this model manager
|
252 | */
|
253 | validateModelFiles() {
|
254 | for (let ns in this.modelFiles) {
|
255 | this.modelFiles[ns].validate();
|
256 | }
|
257 | }
|
258 |
|
259 | /**
|
260 | * Downloads all ModelFiles that are external dependencies and adds or
|
261 | * updates them in this ModelManager.
|
262 | * @param {Object} [options] - Options object passed to ModelFileLoaders
|
263 | * @param {ModelFileDownloader} [modelFileDownloader] - an optional ModelFileDownloader
|
264 | * @throws {IllegalModelException} if the models fail validation
|
265 | * @return {Promise} a promise when the download and update operation is completed.
|
266 | */
|
267 | async updateExternalModels(options, modelFileDownloader) {
|
268 |
|
269 | const NAME = 'updateExternalModels';
|
270 | debug(NAME, 'updateExternalModels', options);
|
271 |
|
272 | if(!modelFileDownloader) {
|
273 | modelFileDownloader = new ModelFileDownloader(new DefaultModelFileLoader(this));
|
274 | }
|
275 |
|
276 | const externalModelFiles = await modelFileDownloader.downloadExternalDependencies(this.getModelFiles(), options)
|
277 | .catch(error => {
|
278 | // If we're not able to download the latest dependencies, see whether the models all validate based on the available cached models.
|
279 | if(error.code === 'MISSING_DEPENDENCY'){
|
280 | try {
|
281 | this.validateModelFiles();
|
282 | return [];
|
283 | } catch (validationError){
|
284 | // The validation error tells us the first model that is missing from the model manager, but the dependency download
|
285 | // will fail at the first external model, regardless of whether there is already a local copy.
|
286 | // As a hint to the user we display the URL of the external model that can't be found.
|
287 | const modelFile = this.getModelFileByFileName(validationError.fileName);
|
288 | const namespaces = modelFile.getExternalImports();
|
289 | const missingNs = Object.keys(namespaces).find((ns) => validationError.shortMessage.includes(ns));
|
290 | const url = modelFile.getImportURI(missingNs);
|
291 | const err = new Error(`Unable to download external model dependency '${url}'`);
|
292 | err.code = 'MISSING_DEPENDENCY';
|
293 | throw err;
|
294 | }
|
295 | } else {
|
296 | throw error;
|
297 | }
|
298 | });
|
299 | const originalModelFiles = {};
|
300 | Object.assign(originalModelFiles, this.modelFiles);
|
301 |
|
302 | try {
|
303 | externalModelFiles.forEach((mf) => {
|
304 | const existing = this.modelFiles[mf.getNamespace()];
|
305 |
|
306 | if (existing) {
|
307 | this.updateModelFile(mf, mf.getName(), true); // disable validation
|
308 | } else {
|
309 | this.addModelFile(mf, mf.getName(), true); // disable validation
|
310 | }
|
311 | });
|
312 |
|
313 | // now everything is applied, we need to revalidate all models
|
314 | this.validateModelFiles();
|
315 | return externalModelFiles;
|
316 | } catch (err) {
|
317 | this.modelFiles = {};
|
318 | Object.assign(this.modelFiles, originalModelFiles);
|
319 | throw err;
|
320 | }
|
321 | }
|
322 |
|
323 | /**
|
324 | * Write all models in this model manager to the specified path in the file system
|
325 | *
|
326 | * @param {string} path to a local directory
|
327 | * @param {Object} [options] - Options object
|
328 | * @param {boolean} options.includeExternalModels -
|
329 | * If true, external models are written to the file system. Defaults to true
|
330 | */
|
331 | writeModelsToFileSystem(path, options = {}) {
|
332 | if(!path){
|
333 | throw new Error('`path` is a required parameter of writeModelsToFileSystem');
|
334 | }
|
335 |
|
336 | const opts = Object.assign({
|
337 | includeExternalModels: true,
|
338 | }, options);
|
339 |
|
340 | this.getModelFiles().forEach(function (file) {
|
341 | if (file.isExternal() && !opts.includeExternalModels) {
|
342 | return;
|
343 | }
|
344 | // Always assume file names have been normalized from `\` to `/`
|
345 | const filename = slash(file.fileName).split('/').pop();
|
346 | fs.writeFileSync(path + fsPath.sep + filename, file.definitions);
|
347 | });
|
348 | }
|
349 |
|
350 | /**
|
351 | * Get the array of model file instances
|
352 | * @param {Boolean} [includeConcertoNamespace] - whether to include the concerto namespace
|
353 | * (default to false)
|
354 | * @return {ModelFile[]} The ModelFiles registered
|
355 | * @private
|
356 | */
|
357 | getModelFiles(includeConcertoNamespace) {
|
358 | let keys = Object.keys(this.modelFiles);
|
359 | let result = [];
|
360 |
|
361 | for (let n = 0; n < keys.length; n++) {
|
362 | const ns = keys[n];
|
363 | if(includeConcertoNamespace || ns !== 'concerto') {
|
364 | result.push(this.modelFiles[ns]);
|
365 | }
|
366 | }
|
367 |
|
368 | return result;
|
369 | }
|
370 |
|
371 | /**
|
372 | * Gets all the Concerto models
|
373 | * @param {Object} [options] - Options object
|
374 | * @param {boolean} options.includeExternalModels -
|
375 | * If true, external models are written to the file system. Defaults to true
|
376 | * @return {Array<{name:string, content:string}>} the name and content of each CTO file
|
377 | */
|
378 | getModels(options) {
|
379 | const modelFiles = this.getModelFiles();
|
380 | let models = [];
|
381 | const opts = Object.assign({
|
382 | includeExternalModels: true,
|
383 | }, options);
|
384 |
|
385 | modelFiles.forEach(function (file) {
|
386 | if (file.isExternal() && !opts.includeExternalModels) {
|
387 | return;
|
388 | }
|
389 | let fileName;
|
390 | if (file.fileName === 'UNKNOWN' || file.fileName === null || !file.fileName) {
|
391 | fileName = file.namespace + '.cto';
|
392 | } else {
|
393 | let fileIdentifier = file.fileName;
|
394 | fileName = fsPath.basename(fileIdentifier);
|
395 | }
|
396 | models.push({ 'name' : fileName, 'content' : file.definitions });
|
397 | });
|
398 | return models;
|
399 | }
|
400 |
|
401 | /**
|
402 | * Check that the type is valid and returns the FQN of the type.
|
403 | * @param {string} context - error reporting context
|
404 | * @param {string} type - fully qualified type name
|
405 | * @return {string} - the resolved type name (fully qualified)
|
406 | * @throws {IllegalModelException} - if the type is not defined
|
407 | * @private
|
408 | */
|
409 | resolveType(context, type) {
|
410 | // is the type a primitive?
|
411 | if (ModelUtil.isPrimitiveType(type)) {
|
412 | return type;
|
413 | }
|
414 |
|
415 | let ns = ModelUtil.getNamespace(type);
|
416 | let modelFile = this.getModelFile(ns);
|
417 | if (!modelFile) {
|
418 | let formatter = Globalize.messageFormatter('modelmanager-resolvetype-nonsfortype');
|
419 | throw new IllegalModelException(formatter({
|
420 | type: type,
|
421 | context: context
|
422 | }));
|
423 | }
|
424 |
|
425 | if (modelFile.isLocalType(type)) {
|
426 | return type;
|
427 | }
|
428 |
|
429 | let formatter = Globalize.messageFormatter('modelmanager-resolvetype-notypeinnsforcontext');
|
430 | throw new IllegalModelException(formatter({
|
431 | context: context,
|
432 | type: type,
|
433 | namespace: modelFile.getNamespace()
|
434 | }));
|
435 | }
|
436 |
|
437 | /**
|
438 | * Remove all registered Concerto files
|
439 | */
|
440 | clearModelFiles() {
|
441 | this.modelFiles = {};
|
442 | this.addRootModel();
|
443 | }
|
444 |
|
445 | /**
|
446 | * Get the ModelFile associated with a namespace
|
447 | *
|
448 | * @param {string} namespace - the namespace containing the ModelFile
|
449 | * @return {ModelFile} registered ModelFile for the namespace or null
|
450 | */
|
451 | getModelFile(namespace) {
|
452 | return this.modelFiles[namespace];
|
453 | }
|
454 |
|
455 | /**
|
456 | * Get the ModelFile associated with a file name
|
457 | *
|
458 | * @param {string} fileName - the fileName associated with the ModelFile
|
459 | * @return {ModelFile} registered ModelFile for the namespace or null
|
460 | * @private
|
461 | */
|
462 | getModelFileByFileName(fileName) {
|
463 | return this.getModelFiles().filter(mf => mf.getName() === fileName)[0];
|
464 | }
|
465 |
|
466 | /**
|
467 | * Get the namespaces registered with the ModelManager.
|
468 | * @return {string[]} namespaces - the namespaces that have been registered.
|
469 | */
|
470 | getNamespaces() {
|
471 | return Object.keys(this.modelFiles);
|
472 | }
|
473 |
|
474 | /**
|
475 | * Look up a type in all registered namespaces.
|
476 | *
|
477 | * @param {string} qualifiedName - fully qualified type name.
|
478 | * @return {ClassDeclaration} - the class declaration for the specified type.
|
479 | * @throws {TypeNotFoundException} - if the type cannot be found or is a primitive type.
|
480 | * @private
|
481 | */
|
482 | getType(qualifiedName) {
|
483 |
|
484 | const namespace = ModelUtil.getNamespace(qualifiedName);
|
485 |
|
486 | const modelFile = this.getModelFile(namespace);
|
487 | if (!modelFile) {
|
488 | const formatter = Globalize.messageFormatter('modelmanager-gettype-noregisteredns');
|
489 | throw new TypeNotFoundException(qualifiedName, formatter({
|
490 | type: qualifiedName
|
491 | }));
|
492 | }
|
493 |
|
494 | const classDecl = modelFile.getType(qualifiedName);
|
495 | if (!classDecl) {
|
496 | const formatter = Globalize.messageFormatter('modelmanager-gettype-notypeinns');
|
497 | throw new TypeNotFoundException(qualifiedName, formatter({
|
498 | type: ModelUtil.getShortName(qualifiedName),
|
499 | namespace: namespace
|
500 | }));
|
501 | }
|
502 |
|
503 | return classDecl;
|
504 | }
|
505 |
|
506 | /**
|
507 | * Get the AssetDeclarations defined in this model manager
|
508 | * @return {AssetDeclaration[]} the AssetDeclarations defined in the model manager
|
509 | */
|
510 | getAssetDeclarations() {
|
511 | return this.getModelFiles().reduce((prev, cur) => {
|
512 | return prev.concat(cur.getAssetDeclarations());
|
513 | }, []);
|
514 | }
|
515 |
|
516 | /**
|
517 | * Get the TransactionDeclarations defined in this model manager
|
518 | * @return {TransactionDeclaration[]} the TransactionDeclarations defined in the model manager
|
519 | */
|
520 | getTransactionDeclarations() {
|
521 | return this.getModelFiles().reduce((prev, cur) => {
|
522 | return prev.concat(cur.getTransactionDeclarations());
|
523 | }, []);
|
524 | }
|
525 |
|
526 | /**
|
527 | * Get the EventDeclarations defined in this model manager
|
528 | * @return {EventDeclaration[]} the EventDeclaration defined in the model manager
|
529 | */
|
530 | getEventDeclarations() {
|
531 | return this.getModelFiles().reduce((prev, cur) => {
|
532 | return prev.concat(cur.getEventDeclarations());
|
533 | }, []);
|
534 | }
|
535 |
|
536 | /**
|
537 | * Get the ParticipantDeclarations defined in this model manager
|
538 | * @return {ParticipantDeclaration[]} the ParticipantDeclaration defined in the model manager
|
539 | */
|
540 | getParticipantDeclarations() {
|
541 | return this.getModelFiles().reduce((prev, cur) => {
|
542 | return prev.concat(cur.getParticipantDeclarations());
|
543 | }, []);
|
544 | }
|
545 |
|
546 | /**
|
547 | * Get the EnumDeclarations defined in this model manager
|
548 | * @return {EnumDeclaration[]} the EnumDeclaration defined in the model manager
|
549 | */
|
550 | getEnumDeclarations() {
|
551 | return this.getModelFiles().reduce((prev, cur) => {
|
552 | return prev.concat(cur.getEnumDeclarations());
|
553 | }, []);
|
554 | }
|
555 |
|
556 | /**
|
557 | * Get the Concepts defined in this model manager
|
558 | * @return {ConceptDeclaration[]} the ConceptDeclaration defined in the model manager
|
559 | */
|
560 | getConceptDeclarations() {
|
561 | return this.getModelFiles().reduce((prev, cur) => {
|
562 | return prev.concat(cur.getConceptDeclarations());
|
563 | }, []);
|
564 | }
|
565 |
|
566 | /**
|
567 | * Get a factory for creating new instances of types defined in this model manager.
|
568 | * @return {Factory} A factory for creating new instances of types defined in this model manager.
|
569 | */
|
570 | getFactory() {
|
571 | return this.factory;
|
572 | }
|
573 |
|
574 | /**
|
575 | * Get a serializer for serializing instances of types defined in this model manager.
|
576 | * @return {Serializer} A serializer for serializing instances of types defined in this model manager.
|
577 | */
|
578 | getSerializer() {
|
579 | return this.serializer;
|
580 | }
|
581 |
|
582 | /**
|
583 | * Get the decorator factories for this model manager.
|
584 | * @return {DecoratorFactory[]} The decorator factories for this model manager.
|
585 | */
|
586 | getDecoratorFactories() {
|
587 | return this.decoratorFactories;
|
588 | }
|
589 |
|
590 | /**
|
591 | * Add a decorator factory to this model manager.
|
592 | * @param {DecoratorFactory} factory The decorator factory to add to this model manager.
|
593 | */
|
594 | addDecoratorFactory(factory) {
|
595 | this.decoratorFactories.push(factory);
|
596 | }
|
597 |
|
598 | /**
|
599 | * Checks if this fully qualified type name is derived from another.
|
600 | * @param {string} fqt1 The fully qualified type name to check.
|
601 | * @param {string} fqt2 The fully qualified type name it is may be derived from.
|
602 | * @returns {boolean} True if this instance is an instance of the specified fully
|
603 | * qualified type name, false otherwise.
|
604 | */
|
605 | derivesFrom(fqt1, fqt2) {
|
606 | // Check to see if this is an exact instance of the specified type.
|
607 | let typeDeclaration = this.getType(fqt1);
|
608 | while (typeDeclaration) {
|
609 | if (typeDeclaration.getFullyQualifiedName() === fqt2) {
|
610 | return true;
|
611 | }
|
612 | typeDeclaration = typeDeclaration.getSuperTypeDeclaration();
|
613 | }
|
614 | return false;
|
615 | }
|
616 |
|
617 | /**
|
618 | * Alternative instanceof that is reliable across different module instances
|
619 | * @see https://github.com/hyperledger/composer-concerto/issues/47
|
620 | *
|
621 | * @param {object} object - The object to test against
|
622 | * @returns {boolean} - True, if the object is an instance of a ModelManager
|
623 | */
|
624 | static [Symbol.hasInstance](object){
|
625 | return typeof object !== 'undefined' && object !== null && Boolean(object._isModelManager);
|
626 | }
|
627 | }
|
628 |
|
629 | module.exports = ModelManager;
|