Source: composer-common/lib/factory.js

/*
 * 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 debug = require('debug')('ibm-concerto');
const Globalize = require('./globalize');
const InstanceGenerator = require('./serializer/instancegenerator');
const Relationship = require('./model/relationship');

const Resource = require('./model/resource');
const ValidatedResource = require('./model/validatedresource');

const Concept = require('./model/concept');
const ValidatedConcept = require('./model/validatedconcept');

const ResourceValidator = require('./serializer/resourcevalidator');
const TransactionDeclaration = require('./introspect/transactiondeclaration');
const TypedStack = require('./serializer/typedstack');
const uuid = require('uuid');

/**
 * Use the Factory to create instances of Resource: transactions, participants
 * and assets.
 * <p><a href="./diagrams/factory.svg"><img src="./diagrams/factory.svg" style="width:100%;"/></a></p>
 * @class
 * @memberof module:composer-common
 */
class Factory {

    /**
     * Create the factory.
     * <p>
     * <strong>Note: Only to be called by framework code. Applications should
     * retrieve instances from {@link Fabric-Composer}</strong>
     * </p>
     * @param {ModelManager} modelManager - The ModelManager to use for this registry
     */
    constructor(modelManager) {
        this.modelManager = modelManager;
    }

    /**
     * Create a new Resource with a given namespace, type name and id
     * @param {string} ns - the namespace of the Resource
     * @param {string} type - the type of the Resource
     * @param {string} id - the identifier
     * @param {Object} [options] - an optional set of options
     * @param {boolean} [options.disableValidation] - pass true if you want the factory to
     * return a {@link Resource} instead of a {@link ValidatedResource}. Defaults to false.
     * @param {boolean} [options.generate] - pass true if you want the factory to return a
     * resource instance with generated sample data.
     * @return {Resource} the new instance
     * @throws {ModelException} if the type is not registered with the ModelManager
     * @deprecated - use newResource instead
     */
    newInstance(ns, type, id, options) {
        return this.newResource(ns, type, id, options);
    }

    /**
     * Create a new Resource with a given namespace, type name and id
     * @param {string} ns - the namespace of the Resource
     * @param {string} type - the type of the Resource
     * @param {string} id - the identifier
     * @param {Object} [options] - an optional set of options
     * @param {boolean} [options.disableValidation] - pass true if you want the factory to
     * return a {@link Resource} instead of a {@link ValidatedResource}. Defaults to false.
     * @param {boolean} [options.generate] - pass true if you want the factory to return a
     * resource instance with generated sample data.
     * @return {Resource} the new instance
     * @throws {ModelException} if the type is not registered with the ModelManager
     */
    newResource(ns, type, id, options) {
        let modelFile = this.modelManager.getModelFile(ns);
        if(!modelFile) {
            let formatter = Globalize.messageFormatter('factory-newinstance-notregisteredwithmm');
            throw new Error(formatter({
                namespace: ns
            }));
        }

        if(!modelFile.isDefined(type)) {
            let formatter = Globalize.messageFormatter('factory-newinstance-typenotdeclaredinns');

            throw new Error(formatter({
                namespace: ns,
                type: type
            }));
        }

        let classDecl = modelFile.getType(type);

        if(classDecl.isAbstract()) {
            throw new Error('Cannot create abstract type ' + classDecl.getFullyQualifiedName());
        }

        let newObj = null;
        options = options || {};
        if(options.disableValidation) {
            newObj = new Resource(this.modelManager,ns,type,id);
        }
        else {
            newObj = new ValidatedResource(this.modelManager,ns,type,id, new ResourceValidator());
        }
        newObj.assignFieldDefaults();

        if(options.generate) {
            let visitor = new InstanceGenerator();
            let parameters = {
                stack: new TypedStack(newObj),
                modelManager: this.modelManager,
                factory: this
            };
            classDecl.accept(visitor, parameters);
        }

        // if we have an identifier, we set it now
        let idField = classDecl.getIdentifierFieldName();
        if(idField) {
            newObj[idField] = id;
        }

        debug('Factory.newInstance created %s', id );
        return newObj;
    }

    /**
     * Create a new Resource with a given namespace, type name and id
     * @param {string} ns - the namespace of the Resource
     * @param {string} type - the type of the Resource
     * @param {Object} [options] - an optional set of options
     * @param {boolean} [options.disableValidation] - pass true if you want the factory to
     * return a {@link Resource} instead of a {@link ValidatedResource}. Defaults to false.
     * @param {boolean} [options.generate] - pass true if you want the factory to return a
     * resource instance with generated sample data.
     * @return {Resource} the new instance
     * @throws {ModelException} if the type is not registered with the ModelManager
     */
    newConcept(ns, type, options) {
        let modelFile = this.modelManager.getModelFile(ns);
        if(!modelFile) {
            let formatter = Globalize.messageFormatter('factory-newinstance-notregisteredwithmm');
            throw new Error(formatter({
                namespace: ns
            }));
        }

        if(!modelFile.isDefined(type)) {
            let formatter = Globalize.messageFormatter('factory-newinstance-typenotdeclaredinns');

            throw new Error(formatter({
                namespace: ns,
                type: type
            }));
        }

        let classDecl = modelFile.getType(type);

        if(classDecl.isAbstract()) {
            throw new Error('Cannot create abstract type ' + classDecl.getFullyQualifiedName());
        }

        let newObj = null;
        options = options || {};
        if(options.disableValidation) {
            newObj = new Concept(this.modelManager,ns,type);
        }
        else {
            newObj = new ValidatedConcept(this.modelManager,ns,type, new ResourceValidator());
        }
        newObj.assignFieldDefaults();

        if(options.generate) {
            let visitor = new InstanceGenerator();
            let parameters = {
                stack: new TypedStack(newObj),
                modelManager: this.modelManager,
                factory: this
            };
            classDecl.accept(visitor, parameters);
        }

        debug('Factory.newInstance created concept %s', classDecl.getFullyQualifiedName() );
        return newObj;
    }

    /**
     * Create a new Relationship with a given namespace, type and identifier.
`     * A relationship is a typed pointer to an instance. I.e the relationship
     * with namespace = 'org.acme', type = 'Vehicle' and id = 'ABC' creates`
     * a pointer that points at an instance of org.acme.Vehicle with the id
     * ABC.
     *
     * @param {string} ns - the namespace of the Resource
     * @param {string} type - the type of the Resource
     * @param {string} id - the identifier
     * @return {Relationship} - the new relationship instance
     * @throws {ModelException} if the type is not registered with the ModelManager
     */
    newRelationship(ns, type, id) {
        let modelFile = this.modelManager.getModelFile(ns);
        if(!modelFile) {
            let formatter = Globalize.messageFormatter('factory-newrelationship-notregisteredwithmm');

            throw new Error(formatter({
                namespace: ns
            }));
        }

        if(!modelFile.isDefined(type)) {
            let formatter = Globalize.messageFormatter('factory-newinstance-typenotdeclaredinns');

            throw new Error(formatter({
                namespace: ns,
                type: type
            }));
        }

        let relationship = new Relationship(this.modelManager,ns,type,id);
        return relationship;
    }

    /**
     * Create a new transaction object. The identifier of the transaction is
     * set to a UUID.
     * @param {string} ns - the namespace of the transaction.
     * @param {string} type - the type of the transaction.
     * @param {string} [id] - an optional identifier for the transaction; if you do not specify
     * one then an identifier will be automatically generated.
     * @param {Object} [options] - an optional set of options
     * @param {boolean} [options.generate] - pass true if you want the factory to return a
     * resource instance with generated sample data.
     * @return {Resource} A resource for the new transaction.
     */
    newTransaction(ns, type, id, options) {
        if (!ns) {
            throw new Error('ns not specified');
        } else if (!type) {
            throw new Error('type not specified');
        }
        id = id || uuid.v4();
        let transaction = this.newInstance(ns, type, id, options);
        const classDeclaration = transaction.getClassDeclaration();

        if (!(classDeclaration instanceof TransactionDeclaration)) {
            throw new Error(transaction.getClassDeclaration().getFullyQualifiedName() + ' is not a transaction');
        }

        // set the timestamp
        transaction.timestamp = new Date();

        return transaction;
    }

    /**
     * Stop serialization of this object.
     * @return {Object} An empty object.
     */
    toJSON() {
        return {};
    }
}

module.exports = Factory;