Source: composer-runtime/lib/registry.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 EventEmitter = require('events');
const Resource = require('composer-common').Resource;

/**
 * A class for managing and persisting resources.
 * @protected
 */
class Registry extends EventEmitter {

    /**
     * Constructor.
     * @param {string} dataCollection The data collection to use.
     * @param {Serializer} serializer The serializer to use.
     * @param {AccessController} accessController The access controller to use.
     * @param {string} type The type of the registry.
     * @param {string} id The ID of the registry.
     * @param {string} name The name of the registry.
     */
    constructor(dataCollection, serializer, accessController, type, id, name) {
        super();
        this.dataCollection = dataCollection;
        this.serializer = serializer;
        this.accessController = accessController;
        this.type = type;
        this.id = id;
        this.name = name;
    }

    /**
     * Get all the resources in this registry.
     * @return {Promise} A promise that will be resolved with an array of {@link
     * Resource} objects when complete, or rejected with an error.
     */
    getAll() {
        return this.dataCollection.getAll()
            .then((resources) => {
                return resources.map((resource) => {
                    return this.serializer.fromJSON(resource);
                }).filter((resource) => {
                    try {
                        this.accessController.check(resource, 'READ');
                        return true;
                    } catch (e) {
                        return false;
                    }
                });
            });
    }

    /**
     * Get the specified resource in this registry.
     * @param {string} id The ID of the resource.
     * @return {Promise} A promise that will be resolved with a {@link Resource}
     * object when complete, or rejected with an error.
     */
    get(id) {
        return Promise.resolve()
            .then(() => {
                return this.dataCollection.get(id);
            })
            .then((resource) => {
                let result = this.serializer.fromJSON(resource);
                try {
                    this.accessController.check(result, 'READ');
                    return result;
                } catch (e) {
                    throw new Error(`Object with ID '${id}' in collection with ID '${this.type}:${this.id}' does not exist`);
                }
            });
    }

    /**
     * Determine whether the specified resource exists in this registry.
     * @param {string} id The ID of the resource.
     * @return {Promise} A promise that will be resolved with a boolean
     * indicating whether the asset exists.
     */
    exists(id) {
        return this.dataCollection.exists(id)
            .then((exists) => {
                if (exists) {
                    return this.dataCollection.get(id)
                        .then((resource) => {
                            let result = this.serializer.fromJSON(resource);
                            try {
                                this.accessController.check(result, 'READ');
                                return true;
                            } catch (e) {
                                return false;
                            }
                        });
                }
            });
    }

    /**
     * An event signalling that a resource has been added to this registry.
     * @event Registry#resourceadded
     * @protected
     * @type {object}
     * @param {Registry} registry The registry.
     * @param {Resource} resource The resource.
     */

    /**
     * Add all of the specified resources to this registry.
     * @param {Resource[]} resources The resources to add to this registry.
     * @param {Object} [options] Options for processing the resources.
     * @param {boolean} [options.convertResourcesToRelationships] Permit resources
     * in the place of relationships, defaults to false.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    addAll(resources, options) {
        options = options || {};
        return resources.reduce((result, resource) => {
            return result.then(() => {
                return this.add(resource, options);
            });
        }, Promise.resolve());
    }

    /**
     * Add the specified resource to this registry.
     * @param {Resource} resource The resource to add to this registry.
     * @param {Object} [options] Options for processing the resources.
     * @param {boolean} [options.convertResourcesToRelationships] Permit resources
     * in the place of relationships, defaults to false.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    add(resource, options) {
        this.accessController.check(resource, 'CREATE');
        options = options || {};
        let id = resource.getIdentifier();
        let object = this.serializer.toJSON(resource, {
            convertResourcesToRelationships: options.convertResourcesToRelationships
        });
        return this.dataCollection.add(id, object)
            .then(() => {
                this.emit('resourceadded', {
                    registry: this,
                    resource: resource
                });
            });
    }

    /**
     * An event signalling that a resource has been updated in this registry.
     * @event Registry#resourceupdated
     * @protected
     * @type {object}
     * @param {Registry} registry The registry.
     * @param {Resource} oldResource The old version of the resource.
     * @param {Resource} newResource The new version of the resource.
     */

    /**
     * Update all of the specified resources in this registry.
     * @param {Resource[]} resources The resources to update in this registry.
     * @param {Object} [options] Options for processing the resources.
     * @param {boolean} [options.convertResourcesToRelationships] Permit resources
     * in the place of relationships, defaults to false.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    updateAll(resources, options) {
        options = options || {};
        return resources.reduce((result, resource) => {
            return result.then(() => {
                return this.update(resource, options);
            });
        }, Promise.resolve());
    }

    /**
     * Update the specified resource in this registry.
     * @param {Resource} resource The resource to update in this registry.
     * @param {Object} [options] Options for processing the resources.
     * @param {boolean} [options.convertResourcesToRelationships] Permit resources
     * in the place of relationships, defaults to false.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    update(resource, options) {
        options = options || {};
        let id = resource.getIdentifier();
        let object = this.serializer.toJSON(resource, {
            convertResourcesToRelationships: options.convertResourcesToRelationships
        });
        return this.dataCollection.get(id)
            .then((oldResource) => {
                return this.serializer.fromJSON(oldResource);
            })
            .then((oldResource) => {
                // We must perform access control checks on the old version of the resource!
                this.accessController.check(oldResource, 'UPDATE');
                return this.dataCollection.update(id, object)
                    .then(() => {
                        this.emit('resourceupdated', {
                            registry: this,
                            oldResource: oldResource,
                            newResource: resource
                        });
                    });
            });
    }

    /**
     * An event signalling that a resource has been removed from this registry.
     * @event Registry#resourceremoved
     * @protected
     * @type {object}
     * @param {Registry} registry The registry.
     * @param {string} resourceID The ID of the resource.
     */

    /**
     * Remove all of the specified resources from this registry.
     * @param {string[]|Resource[]} resources The resources to remove from this registry.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    removeAll(resources) {
        return resources.reduce((result, resource) => {
            return result.then(() => {
                return this.remove(resource);
            });
        }, Promise.resolve());
    }

    /**
     * Remove the specified resource from this registry.
     * @param {string|Resource} resource The resource to remove from this registry.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    remove(resource) {
        return Promise.resolve()
            .then(() => {
                // If the resource is a string, then we need to retrieve
                // the resource using its ID from the registry. We need to
                // do this to figure out the type of the resource for
                // access control.
                if (resource instanceof Resource) {
                    return resource;
                } else {
                    return this.dataCollection.get(resource)
                        .then((resource) => {
                            return this.serializer.fromJSON(resource);
                        });
                }
            })
            .then((resource) => {
                this.accessController.check(resource, 'DELETE');
                let id = resource.getIdentifier();
                return this.dataCollection.remove(id)
                    .then(() => {
                        this.emit('resourceremoved', {
                            registry: this,
                            resourceID: id
                        });
                    });
            });
    }

    /**
     * Return an object suitable for serialization.
     * @return {Object} An object suitable for serialization.
     */
    toJSON() {
        return {
            type: this.type,
            id: this.id,
            name: this.name
        };
    }

}

module.exports = Registry;