Skip to content

Commit

Permalink
refactor(core) Add utility for loading a model manager
Browse files Browse the repository at this point in the history
Signed-off-by: Jerome Simeon <jeromesimeon@me.com>
  • Loading branch information
jeromesimeon committed Oct 26, 2019
1 parent 8eec864 commit 999fa14
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 73 deletions.
77 changes: 6 additions & 71 deletions packages/concerto-cli/lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
*
Expand All @@ -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);

Expand All @@ -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;

Expand Down Expand Up @@ -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}'.`;
Expand Down
3 changes: 3 additions & 0 deletions packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/concerto-core/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/concerto-core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
99 changes: 99 additions & 0 deletions packages/concerto-core/lib/modelloader.js
Original file line number Diff line number Diff line change
@@ -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;
106 changes: 106 additions & 0 deletions packages/concerto-core/test/modelloader.js
Original file line number Diff line number Diff line change
@@ -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;
});
});

});
3 changes: 2 additions & 1 deletion packages/concerto-core/test/modelmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit 999fa14

Please sign in to comment.