diff --git a/packages/concerto-cli/lib/commands.js b/packages/concerto-cli/lib/commands.js index 263af571fa..df1a907f78 100644 --- a/packages/concerto-cli/lib/commands.js +++ b/packages/concerto-cli/lib/commands.js @@ -17,9 +17,9 @@ const fs = require('fs'); const mkdirp = require('mkdirp'); -const { ModelManager, Factory, Serializer } = require('@accordproject/concerto-core'); -const ModelFile = require('@accordproject/concerto-core').ModelFile; -const DefaultModelFileLoader = require('@accordproject/concerto-core').DefaultModelFileLoader; +const ModelLoader = require('@accordproject/concerto-core').ModelLoader; +const Factory = require('@accordproject/concerto-core').Factory; +const Serializer = require('@accordproject/concerto-core').Serializer; const FileWriter = require('@accordproject/concerto-tools').FileWriter; const CodeGen = require('@accordproject/concerto-tools').CodeGen; @@ -30,77 +30,12 @@ const PlantUMLVisitor = CodeGen.PlantUMLVisitor; const TypescriptVisitor = CodeGen.TypescriptVisitor; const XmlSchemaVisitor = CodeGen.XmlSchemaVisitor; -const defaultSystemContent = `namespace org.accordproject.base -abstract asset Asset { } -abstract participant Participant { } -abstract transaction Transaction identified by transactionId { - o String transactionId -} -abstract event Event identified by eventId { - o String eventId -}`; -const defaultSystemName = '@org.accordproject.base'; - /** * Utility class that implements the commands exposed by the CLI. * @class * @memberof module:concerto-cli */ class Commands { - - /** - * Add model file - * - * @param {object} modelFileLoader - the model loader - * @param {object} modelManager - the model manager - * @param {string} ctoFile - the model file - * @param {boolean} system - whether this is a system model - * @return {object} the model manager - */ - static async addModel(modelFileLoader, modelManager, ctoFile, system) { - let modelFile = null; - if (system && !ctoFile) { - modelFile = new ModelFile(modelManager, defaultSystemContent, defaultSystemName, true); - } else if(modelFileLoader.accepts(ctoFile)) { - modelFile = await modelFileLoader.load(ctoFile); - } else { - const content = fs.readFileSync(ctoFile, 'utf8'); - modelFile = new ModelFile(modelManager, content, ctoFile); - } - - if (system) { - modelManager.addModelFile(modelFile, modelFile.getName(), false, true); - } else { - modelManager.addModelFile(modelFile, modelFile.getName(), true, false); - } - - return modelManager; - } - - /** - * Load system and models in a new model manager - * - * @param {string} ctoSystemFile - the system model file - * @param {string[]} ctoFiles - the CTO files (can be local file paths or URLs) - * @return {object} the model manager - */ - static async loadModelManager(ctoSystemFile, ctoFiles) { - let modelManager = new ModelManager(); - const modelFileLoader = new DefaultModelFileLoader(modelManager); - - // Load system model - modelManager = await Commands.addModel(modelFileLoader,modelManager,ctoSystemFile,true); - - // Load user models - for( let ctoFile of ctoFiles ) { - modelManager = await Commands.addModel(modelFileLoader,modelManager,ctoFile,false); - } - - // Validate update models - await modelManager.updateExternalModels(); - return modelManager; - } - /** * Validate a sample JSON against the model * @@ -112,7 +47,7 @@ class Commands { static async validate(sample, ctoSystemFile, ctoFiles) { const json = JSON.parse(fs.readFileSync(sample, 'utf8')); - const modelManager = await Commands.loadModelManager(ctoSystemFile, ctoFiles); + const modelManager = await ModelLoader.loadModelManager(ctoSystemFile, ctoFiles); const factory = new Factory(modelManager); const serializer = new Serializer(factory, modelManager); @@ -129,7 +64,7 @@ class Commands { * @param {string} output the output directory */ static async compile(target, ctoSystemFile, ctoFiles, output) { - const modelManager = await Commands.loadModelManager(ctoSystemFile, ctoFiles); + const modelManager = await ModelLoader.loadModelManager(ctoSystemFile, ctoFiles); let visitor = null; @@ -174,7 +109,7 @@ class Commands { * @param {string} output the output directory */ static async get(ctoSystemFile, ctoFiles, output) { - const modelManager = await Commands.loadModelManager(ctoSystemFile, ctoFiles); + const modelManager = await ModelLoader.loadModelManager(ctoSystemFile, ctoFiles); mkdirp.sync(output); modelManager.writeModelsToFileSystem(output); return `Loaded external models in '${output}'.`; diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 677cad8c65..23059ef095 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -179,6 +179,9 @@ class ValidatedResource extends Resource { + void addArrayValue(string,string) throws Error + void validate() throws Error } +class ModelLoader { + + object loadModelManager(string,string[]) +} class ModelManager { + void constructor() + void validateModelFile(string,string) throws IllegalModelException diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 39280d46de..f389215cf2 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,8 +24,9 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # -Version 0.82.1 {cb6c3b228035279e6997a4e3a34ef348} 2019-10-22 +Version 0.82.1 {dee013e99a3c2d6acc4eddfb00aad2a2} 2019-10-22 - Make several constructors public +- Add model loader utility class Version 0.80.3 {6f5a9ab45943cb76732c14b11f47d044} 2019-08-24 - Add Ergo option to serializer diff --git a/packages/concerto-core/index.js b/packages/concerto-core/index.js index b9ac62db0b..1ac31966bd 100644 --- a/packages/concerto-core/index.js +++ b/packages/concerto-core/index.js @@ -37,6 +37,7 @@ module.exports.Globalize = require('./lib/globalize'); module.exports.Introspector = require('./lib/introspect/introspector'); module.exports.Logger = require('./lib/logger'); module.exports.ModelFile = require('./lib/introspect/modelfile'); +module.exports.ModelLoader = require('./lib/modelloader'); module.exports.ModelManager = require('./lib/modelmanager'); module.exports.DefaultModelFileLoader = require('./lib/introspect/loaders/defaultmodelfileloader'); module.exports.ParseException = require('./lib/introspect/parseexception'); diff --git a/packages/concerto-core/lib/modelloader.js b/packages/concerto-core/lib/modelloader.js new file mode 100644 index 0000000000..be65d17082 --- /dev/null +++ b/packages/concerto-core/lib/modelloader.js @@ -0,0 +1,99 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const fs = require('fs'); + +const DefaultModelFileLoader = require('./introspect/loaders/defaultmodelfileloader'); +const ModelFile = require('./introspect/modelfile'); +const ModelManager = require('./modelmanager'); + +const defaultSystemContent = `namespace org.accordproject.base +abstract asset Asset { } +abstract participant Participant { } +abstract transaction Transaction identified by transactionId { + o String transactionId +} +abstract event Event identified by eventId { + o String eventId +}`; +const defaultSystemName = '@org.accordproject.base'; + +/** + * Create a ModelManager from model files, with an optional system model. + * + * If a ctoFile is not provided, the Accord Project system model is used. + * + * @class + * @memberof module:concerto-core + */ +class ModelLoader { + /** + * Add model file + * + * @param {object} modelFileLoader - the model loader + * @param {object} modelManager - the model manager + * @param {string} ctoFile - the model file + * @param {boolean} system - whether this is a system model + * @return {object} the model manager + * @private + */ + static async addModel(modelFileLoader, modelManager, ctoFile, system) { + let modelFile = null; + if (system && !ctoFile) { + modelFile = new ModelFile(modelManager, defaultSystemContent, defaultSystemName, true); + } else if(modelFileLoader.accepts(ctoFile)) { + modelFile = await modelFileLoader.load(ctoFile); + } else { + const content = fs.readFileSync(ctoFile, 'utf8'); + modelFile = new ModelFile(modelManager, content, ctoFile); + } + + if (system) { + modelManager.addModelFile(modelFile, modelFile.getName(), false, true); + } else { + modelManager.addModelFile(modelFile, modelFile.getName(), true, false); + } + + return modelManager; + } + + /** + * Load system and models in a new model manager + * + * @param {string} ctoSystemFile - the system model file + * @param {string[]} ctoFiles - the CTO files (can be local file paths or URLs) + * @return {object} the model manager + */ + static async loadModelManager(ctoSystemFile, ctoFiles) { + let modelManager = new ModelManager(); + const modelFileLoader = new DefaultModelFileLoader(modelManager); + + // Load system model + modelManager = await ModelLoader.addModel(modelFileLoader,modelManager,ctoSystemFile,true); + + // Load user models + for( let ctoFile of ctoFiles ) { + modelManager = await ModelLoader.addModel(modelFileLoader,modelManager,ctoFile,false); + } + + // Validate update models + await modelManager.updateExternalModels(); + return modelManager; + } + +} + +module.exports = ModelLoader; diff --git a/packages/concerto-core/test/modelloader.js b/packages/concerto-core/test/modelloader.js new file mode 100644 index 0000000000..d327fa9157 --- /dev/null +++ b/packages/concerto-core/test/modelloader.js @@ -0,0 +1,106 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const Factory = require('../lib/factory'); +const ModelLoader = require('../lib/modelloader'); +const ModelManager = require('../lib/modelmanager'); +const TypeNotFoundException = require('../lib/typenotfoundexception'); +const Serializer = require('../lib/serializer'); + +const chai = require('chai'); +chai.use(require('chai-things')); +chai.use(require('chai-as-promised')); + +describe('ModelLoader', () => { + + let modelBase = './test/data/model/model-base.cto'; + let modelUrl = 'https://models.accordproject.org/patents/patent.cto'; + + beforeEach(() => { + }); + + afterEach(() => { + }); + + describe('#loadModelFromFile', function() { + it('should load models', async function() { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + (function() { + modelManager.getType('String'); + }).should.throw(TypeNotFoundException); + }); + + it('should throw an error for a namespace that does not exist', async function() { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + (function() { + modelManager.getType('org.acme.nosuchns.SimpleAsset'); + }).should.throw(TypeNotFoundException, /org.acme.nosuchns/); + }); + + it('should throw an error for an empty namespace', async function() { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + (function() { + modelManager.getType('NoSuchAsset'); + }).should.throw(TypeNotFoundException, /NoSuchAsset/); + }); + + it('should throw an error for a type that does not exist', async function() { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + (function() { + modelManager.getType('org.acme.base.NoSuchAsset'); + }).should.throw(TypeNotFoundException, /NoSuchAsset/); + }); + + it('should return the class declaration for a valid type', async function() { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + const declaration = modelManager.getType('org.acme.base.AbstractAsset'); + declaration.getFullyQualifiedName().should.equal('org.acme.base.AbstractAsset'); + }); + }); + + describe('#loadModelFromUrl', function() { + it('should load models', async function() { + const modelManager = await ModelLoader.loadModelManager(null, [modelUrl]); + (modelManager instanceof ModelManager).should.be.true; + }); + }); + + describe('#getFactory', () => { + + it('should return a factory', async () => { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + modelManager.getFactory().should.be.an.instanceOf(Factory); + }); + + }); + + describe('#getSerializer', () => { + + it('should return a serializer', async () => { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + modelManager.getSerializer().should.be.an.instanceOf(Serializer); + }); + + }); + + describe('#hasInstance', () => { + it('should return true for a valid ModelManager', async () => { + const modelManager = await ModelLoader.loadModelManager(null, [modelBase]); + (modelManager instanceof ModelManager).should.be.true; + }); + }); + +}); diff --git a/packages/concerto-core/test/modelmanager.js b/packages/concerto-core/test/modelmanager.js index d9a6d7e8ef..8d70c68634 100644 --- a/packages/concerto-core/test/modelmanager.js +++ b/packages/concerto-core/test/modelmanager.js @@ -14,13 +14,14 @@ 'use strict'; +const fs = require('fs'); + const AssetDeclaration = require('../lib/introspect/assetdeclaration'); const ConceptDeclaration = require('../lib/introspect/conceptdeclaration'); const DecoratorFactory = require('../lib/introspect/decoratorfactory'); const EnumDeclaration = require('../lib/introspect/enumdeclaration'); const EventDeclaration = require('../lib/introspect/eventdeclaration'); const Factory = require('../lib/factory'); -const fs = require('fs'); const ModelFile = require('../lib/introspect/modelfile'); const ModelFileDownloader = require('../lib/introspect/loaders/modelfiledownloader'); const ModelManager = require('../lib/modelmanager');