Use service compositor as a form of custom registrar. Also, add clarifying comments. WTD-518.
204 lines
7.4 KiB
JavaScript
204 lines
7.4 KiB
JavaScript
/*global define,Promise*/
|
|
|
|
/**
|
|
* Module defining ServiceCompositor. Created by vwoeltje on 11/5/14.
|
|
*/
|
|
define(
|
|
[],
|
|
function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Handles service compositing; that is, building up services
|
|
* from provider, aggregator, and decorator components.
|
|
*
|
|
* @constructor
|
|
*/
|
|
function ServiceCompositor(app, $log) {
|
|
var latest = {},
|
|
providerLists = {}; // Track latest services registered
|
|
|
|
// Log a warning; defaults to "no service provided by"
|
|
function warn(extension, category, message) {
|
|
var msg = message || "No service provided by";
|
|
$log.warn([
|
|
msg,
|
|
" ",
|
|
category,
|
|
" ",
|
|
extension.key,
|
|
" from bundle ",
|
|
extension.bundle.path,
|
|
"; skipping."
|
|
].join(""));
|
|
}
|
|
|
|
// Echo arguments; used to represent groups of non-built-in
|
|
// extensions as a single dependency.
|
|
function echoMany() {
|
|
return Array.prototype.slice.call(arguments);
|
|
}
|
|
|
|
// Echo arguments; used to represent groups of non-built-in
|
|
// extensions as a single dependency.
|
|
function echoSingle(value) {
|
|
return value;
|
|
}
|
|
|
|
// Generates utility functions to match types (one of
|
|
// provider, aggregator, or decorator) of component. Used
|
|
// to filter down to specific types, which are handled
|
|
// in order.
|
|
function hasType(type) {
|
|
return function (extension) {
|
|
return extension.type === type;
|
|
};
|
|
}
|
|
|
|
// Make a unique name for a service component.
|
|
function makeName(category, service, index) {
|
|
return [
|
|
service,
|
|
"[",
|
|
category,
|
|
"#",
|
|
index,
|
|
"]"
|
|
].join("");
|
|
}
|
|
|
|
// Register a specific provider instance with Angular, and
|
|
// record its name for subsequent stages.
|
|
function registerProvider(provider, index) {
|
|
var service = provider.provides,
|
|
dependencies = provider.depends || [],
|
|
name = makeName("provider", service, index);
|
|
|
|
if (!service) {
|
|
return warn(provider, "provider");
|
|
}
|
|
|
|
providerLists[service] = providerLists[service] || [];
|
|
providerLists[service].push(name);
|
|
|
|
// This provider is the latest candidate for resolving
|
|
// the composite service.
|
|
latest[service] = name;
|
|
|
|
app.service(name, dependencies.concat([provider]));
|
|
}
|
|
|
|
// Register an array of providers as a single dependency;
|
|
// aggregators will then depend upon this to consume all
|
|
// aggregated providers as a single dependency.
|
|
function registerProviderSets() {
|
|
Object.keys(providerLists).forEach(function (service) {
|
|
var name = makeName("provider", service, "*"),
|
|
list = providerLists[service];
|
|
|
|
app.service(name, list.concat([echoMany]));
|
|
});
|
|
}
|
|
|
|
// Registers an aggregator via Angular, including both
|
|
// its declared dependencies and the additional, implicit
|
|
// dependency upon the array of all providers.
|
|
function registerAggregator(aggregator, index) {
|
|
var service = aggregator.provides,
|
|
dependencies = aggregator.depends || [],
|
|
providerSetName = makeName("provider", service, "*"),
|
|
name = makeName("aggregator", service, index);
|
|
|
|
if (!service) {
|
|
return warn(aggregator, "aggregator");
|
|
}
|
|
|
|
// Aggregators need other services to aggregate, otherwise they
|
|
// do nothing.
|
|
if (!latest[service]) {
|
|
return warn(
|
|
aggregator,
|
|
"aggregator",
|
|
"No services to aggregate for"
|
|
);
|
|
}
|
|
|
|
dependencies = dependencies.concat([providerSetName]);
|
|
latest[service] = name;
|
|
|
|
app.service(name, dependencies.concat([aggregator]));
|
|
}
|
|
|
|
// Registers a decorator via Angular, including its implicit
|
|
// dependency on the latest service component which has come
|
|
// before it.
|
|
function registerDecorator(decorator, index) {
|
|
var service = decorator.provides,
|
|
dependencies = decorator.depends || [],
|
|
name = makeName("decorator", service, index);
|
|
|
|
if (!service) {
|
|
return warn(decorator, "decorator");
|
|
}
|
|
|
|
// Decorators need other services to decorate, otherwise they
|
|
// do nothing.
|
|
if (!latest[service]) {
|
|
return warn(
|
|
decorator,
|
|
"decorator",
|
|
"No services to decorate for"
|
|
);
|
|
}
|
|
|
|
dependencies = dependencies.concat([latest[service]]);
|
|
latest[service] = name;
|
|
|
|
app.service(name, dependencies.concat([decorator]));
|
|
}
|
|
|
|
// Alias the latest services of various types back to the
|
|
// more general service declaration.
|
|
function registerLatest() {
|
|
Object.keys(latest).forEach(function (service) {
|
|
app.service(service, [latest[service], echoSingle]);
|
|
});
|
|
}
|
|
|
|
function registerComposites(providers, aggregators, decorators) {
|
|
providers.forEach(registerProvider);
|
|
registerProviderSets();
|
|
aggregators.forEach(registerAggregator);
|
|
decorators.forEach(registerDecorator);
|
|
registerLatest();
|
|
}
|
|
|
|
function registerCompositeServices(components) {
|
|
registerComposites(
|
|
components.filter(hasType("provider")),
|
|
components.filter(hasType("aggregator")),
|
|
components.filter(hasType("decorator"))
|
|
);
|
|
}
|
|
|
|
return {
|
|
/**
|
|
* Register composite services with Angular. This will build
|
|
* up a dependency hierarchy between providers, aggregators,
|
|
* and/or decorators, such that a dependency upon the service
|
|
* type they expose shall be satisfied by their fully-wired
|
|
* whole.
|
|
*
|
|
* Note that this method assumes that a complete set of
|
|
* components shall be provided. Multiple calls to this
|
|
* method may not behave as expected.
|
|
*
|
|
* @param {Array} components extensions of category component
|
|
*/
|
|
registerCompositeServices: registerCompositeServices
|
|
};
|
|
}
|
|
|
|
return ServiceCompositor;
|
|
}
|
|
); |