diff src/implab/di/ServiceDescriptor.js @ 0:fc2517695ee1

Initial commit, draft import of existing work
author cin
date Thu, 01 Jun 2017 13:20:03 +0300
parents
children 93fb6c09f2e1
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/ServiceDescriptor.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,291 @@
+define(
+    [
+        "dojo/_base/declare",
+        "../safe",
+        "dojo/_base/lang",
+        "dojo/_base/array",
+        "./Descriptor",
+        "./ValueDescriptor"
+    ],
+
+    function (declare, safe, lang, array, Descriptor, Value) {
+        var SINGLETON_ACTIVATION = 1,
+            CONTAINER_ACTIVATION = 2,
+            CONTEXT_ACTIVATION = 3,
+            CALL_ACTIVATION = 4,
+            HIERARCHY_ACTIVATION = 5;
+
+        var injectMethod = function (target, method, context, args) {
+            var m = target[method];
+            if (!m)
+                throw new Error("Method '" + method + "' not found");
+
+            if (args instanceof Array)
+                m.apply(target, context.parse(args, "." + method));
+            else
+                m.call(target, context.parse(args, "." + method));
+        };
+
+        var makeClenupCallback = function (target, method) {
+            if (typeof (method) === "string") {
+                return function () {
+                    target[method]();
+                };
+            } else {
+                return function () {
+                    method(target);
+                };
+            }
+        };
+
+        var cacheId = 0;
+
+        var cls = declare(
+            Descriptor, {
+                _instance: null,
+                _hasInstance: false,
+                _activationType: CALL_ACTIVATION,
+                _services: null,
+                _type: null,
+                _typeMap: null,
+                _factory: null,
+                _params: undefined,
+                _inject: null,
+                _cleanup: null,
+                _cacheId: null,
+                _owner: null,
+
+                constructor: function (opts) {
+                    safe.argumentNotNull(opts, "opts");
+                    safe.argumentNotNull(opts.owner, "opts.owner");
+
+                    this._owner = opts.owner;
+
+                    if (!(opts.type || opts.factory))
+                        throw new Error(
+                            "Either a type or a factory must be specified");
+
+                    if (typeof (opts.type) === "string" && !opts.typeMap)
+                        throw new Error(
+                            "The typeMap is required when the type is specified by its name");
+
+                    if (opts.activation)
+                        this._activationType = opts.activation;
+                    if (opts.type)
+                        this._type = opts.type;
+                    if (opts.params)
+                        this._params = opts.params;
+                    if (opts.inject)
+                        this._inject = opts.inject instanceof Array ? opts.inject : [opts.inject];
+                    if (opts.services)
+                        this._services = opts.services;
+                    if (opts.factory)
+                        this._factory = opts.factory;
+                    if (opts.typeMap)
+                        this._typeMap = opts.typeMap;
+                    if (opts.cleanup) {
+                        if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
+                            throw new Error(
+                                "The cleanup parameter must be either a function or a function name");
+
+                        this._cleanup = opts.cleanup;
+                    }
+
+                    this._cacheId = ++cacheId;
+                },
+
+                activate: function (context, name) {
+
+                    // if we have a local service records, register them first
+
+                    var instance;
+
+                    switch (this._activationType) {
+                        case 1: // SINGLETON
+                            // if the value is cached return it
+                            if (this._hasInstance)
+                                return this._instance;
+
+                            var tof = this._type || this._factory;
+
+                            // create the persistent cache identifier for the type
+                            if (safe.isPrimitive(tof))
+                                this._cacheId = this._type;
+                            else
+                                this._cacheId = safe.oid(tof);
+
+                            // singletons are bound to the root container
+                            var container = context.container.getRootContainer();
+
+                            if (container.has(this._cacheId)) {
+                                instance = container.get(this._cacheId);
+                            } else {
+                                instance = this._create(context, name);
+                                container.store(this._cacheId, instance);
+                                if (this._cleanup)
+                                    container.onDispose(
+                                        makeClenupCallback(instance, this._cleanup));
+                            }
+
+                            this._hasInstance = true;
+                            return (this._instance = instance);
+
+                        case 2: // CONTAINER
+                            //return a cached value
+                            if (this._hasInstance)
+                                return this._instance;
+
+                            // create an instance
+                            instance = this._create(context, name);
+
+                            // the instance is bound to the container
+                            if (this._cleanup)
+                                this._owner.onDispose(
+                                    makeClenupCallback(instance, this._cleanup));
+
+                            // cache and return the instance
+                            this._hasInstance = true;
+                            return (this._instance = instance);
+                        case 3: // CONTEXT
+                            //return a cached value if one exists
+                            if (context.has(this._cacheId))
+                                return context.get(this._cacheId);
+                            // context context activated instances are controlled by callers  
+                            return context.store(this._cacheId, this._create(
+                                context,
+                                name));
+                        case 4: // CALL
+                            // per-call created instances are controlled by callers
+                            return this._create(context, name);
+                        case 5: // HIERARCHY
+                            // hierarchy activated instances are behave much like container activated
+                            // except they are created and bound to the child container
+
+                            // return a cached value
+                            if (context.container.has(this._cacheId))
+                                return context.container.get(this._cacheId);
+
+                            instance = this._create(context, name);
+
+                            if (this._cleanup)
+                                context.container.onDispose(makeClenupCallback(
+                                    instance,
+                                    this._cleanup));
+
+                            return context.container.store(this._cacheId, instance);
+                        default:
+                            throw "Invalid activation type: " + this._activationType;
+                    }
+                },
+
+                isInstanceCreated: function () {
+                    return this._hasInstance;
+                },
+
+                getInstance: function () {
+                    return this._instance;
+                },
+
+                _create: function (context, name) {
+                    context.enter(name, this, Boolean(this._services));
+
+                    if (this._activationType != CALL_ACTIVATION &&
+                        context.visit(this._cacheId) > 0)
+                        throw new Error("Recursion detected");
+
+                    if (this._services) {
+                        for (var p in this._services) {
+                            var sv = this._services[p];
+                            context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false));
+                        }
+                    }
+
+                    var instance;
+
+                    if (!this._factory) {
+                        var ctor, type = this._type;
+
+                        if (typeof (type) === "string") {
+                            ctor = this._typeMap[type];
+                            if (!ctor)
+                                throw new Error("Failed to resolve the type '" +
+                                    type + "'");
+                        } else {
+                            ctor = type;
+                        }
+
+                        if (this._params === undefined) {
+                            this._factory = function () {
+                                return new ctor();
+                            };
+                        } else if (this._params instanceof Array) {
+                            this._factory = function () {
+                                var inst = lang.delegate(ctor.prototype);
+                                var ret = ctor.apply(inst, arguments);
+                                return typeof (ret) === "object" ? ret : inst;
+                            };
+                        } else {
+                            this._factory = function (param) {
+                                return new ctor(param);
+                            };
+                        }
+                    }
+
+                    if (this._params === undefined) {
+                        instance = this._factory();
+                    } else if (this._params instanceof Array) {
+                        instance = this._factory.apply(this, context.parse(
+                            this._params,
+                            ".params"));
+                    } else {
+                        instance = this._factory(context.parse(
+                            this._params,
+                            ".params"));
+                    }
+
+                    if (this._inject) {
+                        array.forEach(this._inject, function (spec) {
+                            for (var m in spec)
+                                injectMethod(instance, m, context, spec[m]);
+                        });
+                    }
+
+                    context.leave();
+
+                    return instance;
+                },
+
+                // @constructor {singleton} foo/bar/Baz
+                // @factory {singleton}
+                toString: function () {
+                    var parts = [];
+
+                    parts.push(this._type ? "@constructor" : "@factory");
+
+                    parts.push(activationNames[this._activationType]);
+
+                    if (typeof (this._type) === "string")
+                        parts.push(this._type);
+
+                    return parts.join(" ");
+                }
+
+            });
+
+        cls.SINGLETON = SINGLETON_ACTIVATION;
+        cls.CONTAINER = CONTAINER_ACTIVATION;
+        cls.CONTEXT = CONTEXT_ACTIVATION;
+        cls.CALL = CALL_ACTIVATION;
+        cls.HIERARCHY = HIERARCHY_ACTIVATION;
+
+        var activationNames = [
+            "",
+            "{singleton}",
+            "{container}",
+            "{context}",
+            "{call}",
+            "{hierarchy}"
+        ];
+
+        return cls;
+    });
\ No newline at end of file