/*
* 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;