Skip to content

Instantly share code, notes, and snippets.

@6174
Created September 1, 2014 01:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 6174/303dde3221af4560ab4b to your computer and use it in GitHub Desktop.
Save 6174/303dde3221af4560ab4b to your computer and use it in GitHub Desktop.
ServiceRegistry pattern
"use strict";
/**
* The <code>AliasRegistry</code> is provides access to the aliases used within
* the application.
*
* <p>An alias is simply an alternate logical name for a class, such that specifying
* this logical name in your source code, whether it be Javascript, HTML or CSS, will
* cause the class to be bundled and sent to the browser. It is therefore, at it's
* simplest, a mechanism for ensuring that all the code your app needs, and no more,
* is bundled and sent to the browser. Though class dependencies are usually specified
* by directly referring to other classes, there are times when this is
* inappropriate:</p>
*
* <ol>
* <li>We sometimes need a level of indirection, so that dependencies can be expressed
* without knowing the concrete class that will end up being used (e.g. services).</li>
* <li>It is sometimes useful to specify components declaratively, where it would be confusing
* to have to refer to the classes that provide some feature, rather than talking in terms
* of the feature itself.</li>
* </ol>
*
* <p>Another useful facet of aliases is that they can be used to automatically
* discover all of the classes which implement a particular interface, which
* makes it a good candidate for creating SPI type, auto-discovery mechanisms.</p>
*
* @module br/AliasRegistry
*/
var br = require('br/Core');
var Errors = require('./Errors');
/**
* @class
* @alias module:br/AliasRegistry
*/
var AliasRegistry = function()
{
this._aliasData = null;
this._isAliasDataSet = false;
}
/**
* Returns an array containing the names of all aliases in use within the application.
*
* @type Array
*/
AliasRegistry.prototype.getAllAliases = function getAllAliases() {
ensureAliasDataHasBeenSet.call(this);
return Object.keys(this._aliasData);
};
/**
* Returns a filtered subset of the aliases provided by
* {@link module:br/AliasRegistry.getAllAliases}.
*
* <p>An alias is considered to be associated with an interface if the XML configuration for that
* alias specifically mentions the given interface, or if the class the alias points to happens to
* implement the given interface.</p>
*
* @param {function} interface the interface being used to filter the aliases by.
* @type Array
*/
AliasRegistry.prototype.getAliasesByInterface = function getAliasesByInterface(protocol) {
ensureAliasDataHasBeenSet.call(this);
var allAliases = this.getAllAliases();
var filteredAliases = [];
for(var i = 0, length = allAliases.length; i < length; ++i) {
var alias = allAliases[i];
var aliasInterface = this._aliasData[alias]["interface"];
if(aliasInterface === protocol) {
filteredAliases.push(alias);
} else if (this.isAliasAssigned(alias)) {
var aliasClass = this.getClass(alias);
if(br.classIsA(aliasClass, protocol)) {
filteredAliases.push(alias);
}
}
}
return filteredAliases;
};
/**
* Returns a class corresponding to the requested alias name.
*
* @throws {Errors.IllegalState} if the given alias doesn't exist.
* @param {String} aliasName alias name.
* @type function
*/
AliasRegistry.prototype.getClass = function getClass(aliasName) {
ensureAliasDataHasBeenSet.call(this);
if (!this.isAliasAssigned(aliasName)) {
throw new Errors.IllegalStateError("No class has been found for alias '" + aliasName +"'");
}
return this._aliasData[aliasName]["class"];
};
/**
* Returns whether the given alias is defined.
*
* @param {String} aliasName alias name.
* @type boolean
*/
AliasRegistry.prototype.isAlias = function isAlias(aliasName) {
ensureAliasDataHasBeenSet.call(this);
return aliasName in this._aliasData;
};
/**
* Returns whether the given alias has been assigned a value &mdash; i.e. whether an alias has a
* class value.
*
* @param {String} aliasName alias name.
* @type boolean
*/
AliasRegistry.prototype.isAliasAssigned = function isAliasAssigned(aliasName) {
ensureAliasDataHasBeenSet.call(this);
return this.isAlias(aliasName) && this._aliasData[aliasName]["class"] !== undefined;
};
/**
* Sets the alias data.
*
* If the alias data is inconsistent, this will throw Errors.
*/
AliasRegistry.prototype.setAliasData = function setAliasData(unverifiedAliasData) {
if (this._isAliasDataSet === true) {
throw new Errors.IllegalStateError("Alias data has already been set; unable to set again.");
}
this._isAliasDataSet = true;
this._aliasData = unverifiedAliasData;
var aliases = this.getAllAliases();
var incorrectAliases = [];
var i;
for (i = 0; i < aliases.length; ++i) {
var aliasId = aliases[i];
var alias = this._aliasData[aliasId];
if (this.isAliasAssigned(aliasId) && alias["interface"]) {
var aliasClass = alias["class"];
var protocol = alias["interface"];
if (br.classIsA(aliasClass, protocol) == false) {
incorrectAliases.push(aliasId);
}
}
}
if(incorrectAliases.length > 0) {
var errorMessage = 'The classes for the following aliases do not implement their required interfaces: \n';
for(i = 0; i < incorrectAliases.length; ++i)
{
var incorrectAlias = incorrectAliases[i];
errorMessage += '[' + incorrectAlias + ']: "' + this._aliasData[incorrectAlias]["className"] + '" should implement "' + this._aliasData[incorrectAlias].interfaceName + '";\n';
}
this._isAliasDataSet = false;
this._aliasData = null;
throw new Errors.IllegalStateError(errorMessage);
}
};
/**
* @private
*/
function ensureAliasDataHasBeenSet() {
if (this._isAliasDataSet !== true) {
throw new Errors.IllegalStateError("Alias data has not been set.");
}
}
module.exports = new AliasRegistry();
"use strict";
/**
* The <code>ServiceRegistry</code> is a static class and does not need to be constructed.
*
* The <code>ServiceRegistry</code> is used to allow a given application access to application
* services.
*
* <p>Services are typically registered or requested using an alias name, but older applications
* may still register and request using interfaces, which is also still supported. Applications
* that use aliases don't normally need to manually register services as these are created lazily
* upon first request, but will still need to manually register services that can't be created
* using a zero-arg constructor.</p>
*
* <p>The <code>ServiceRegistry</code> is initialized as follows:</p>
*
* <ol>
* <li>The application invokes {@link br.ServiceRegistry.initializeServices} which
* causes all delayed readiness services to be created.</li>
* <li>Once {@link br.ServiceRegistry.initializeServices} has finished (once one of the
* call-backs fire), the application should then register any services that can't be created
* lazily using zero-arg constructors.</li>
* <li>The application can now start doing it's proper work.</li>
* </ol>
*
* <p>Because blades aren't allowed to depend directly on classes in other blades, interface
* definitions are instead created for particular pieces of functionality, and blades can choose
* to register themselves as being providers of that functionality. The
* <code>ServiceRegistry</code> and the {@link br.EventHub} are both useful in this
* regard:
*
* <ul>
* <li>Many-To-One dependencies are resolved by having a single service instance available via
* the <code>ServiceRegistry</code>.</li>
* <li>Many-To-Many dependencies are resolved by having zero or more classes register with the
* {@link br.EventHub}.</li>
* </ul>
*
* @name br.ServiceRegistry
* @class
* @module br/ServiceRegistry
* @see {@link http://bladerunnerjs.org/docs/concepts/service_registry/}
* @see {@link http://bladerunnerjs.org/docs/use/service_registry/}
*/
var Errors = require('./Errors');
var AliasRegistry = require('./AliasRegistry');
var registry = {};
// Main API //////////////////////////////////////////////////////////////////////////////////////
/**
* Register an object that will be responsible for implementing the given interface within the
* application.
*
* @param {String} identifier The alias used to uniquely identify the service.
* @param {Object} serviceInstance The object responsible for providing the service.
* @throws {Error} If a service has already been registered for the given interface or if no
* instance object is provided.
*/
function registerService(alias, serviceInstance) {
if (serviceInstance === undefined) {
throw new Errors.InvalidParametersError("The service instance is undefined.");
}
if (alias in registry) {
throw new Errors.IllegalStateError("Service: " + alias + " has already been registered.");
}
registry[alias] = serviceInstance;
}
exports.registerService = registerService;
/**
* De-register a service that is currently registered in the <code>ServiceRegistry</code>.
*
* @param {String} sIdentifier The alias or interface name used to uniquely identify the service.
*/
function deregisterService(alias) {
delete registry[alias];
}
exports.deregisterService = deregisterService;
/**
* Retrieve the service linked to the identifier within the application. The identifier could be a
* service alias or a service interface.
*
* @param {String} identifier The alias or interface name used to uniquely identify the service.
* @throws {Error} If no service could be found for the given identifier.
* @type Object
*/
function getService(alias) {
initializeServiceIfRequired(alias);
if (registry[alias] === undefined){
throw new Errors.InvalidParametersError("br/ServiceRegistry could not locate a service for: " + alias);
}
return registry[alias];
}
exports.getService = getService;
/**
* Determine whether a service has been registered for a given identifier.
*
* @param {String} identifier The alias or interface name used to uniquely identify the service.
* @type boolean
*/
function isServiceRegistered(alias) {
return alias in registry;
}
exports.isServiceRegistered = isServiceRegistered;
/**
* Resets the <code>ServiceRegistry</code> back to its initial state.
*
* <p>This method isn't normally called within an application, but is called automatically before
* each test is run.</p>
*/
function clear() {
registry = {};
}
exports.clear = clear;
// private statics ///////////////////////////////////////////////////////////////////////////////
/** @private */
function initializeServiceIfRequired(alias) {
if (alias in registry === false) {
var isIdentifierAlias = AliasRegistry.isAliasAssigned(alias);
if (isIdentifierAlias) {
var ServiceClass = AliasRegistry.getClass(alias);
registry[alias] = new ServiceClass();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment