Mercurial > pub > ImplabJs
comparison 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 | 
   comparison
  equal
  deleted
  inserted
  replaced
| -1:000000000000 | 0:fc2517695ee1 | 
|---|---|
| 1 define( | |
| 2 [ | |
| 3 "dojo/_base/declare", | |
| 4 "../safe", | |
| 5 "dojo/_base/lang", | |
| 6 "dojo/_base/array", | |
| 7 "./Descriptor", | |
| 8 "./ValueDescriptor" | |
| 9 ], | |
| 10 | |
| 11 function (declare, safe, lang, array, Descriptor, Value) { | |
| 12 var SINGLETON_ACTIVATION = 1, | |
| 13 CONTAINER_ACTIVATION = 2, | |
| 14 CONTEXT_ACTIVATION = 3, | |
| 15 CALL_ACTIVATION = 4, | |
| 16 HIERARCHY_ACTIVATION = 5; | |
| 17 | |
| 18 var injectMethod = function (target, method, context, args) { | |
| 19 var m = target[method]; | |
| 20 if (!m) | |
| 21 throw new Error("Method '" + method + "' not found"); | |
| 22 | |
| 23 if (args instanceof Array) | |
| 24 m.apply(target, context.parse(args, "." + method)); | |
| 25 else | |
| 26 m.call(target, context.parse(args, "." + method)); | |
| 27 }; | |
| 28 | |
| 29 var makeClenupCallback = function (target, method) { | |
| 30 if (typeof (method) === "string") { | |
| 31 return function () { | |
| 32 target[method](); | |
| 33 }; | |
| 34 } else { | |
| 35 return function () { | |
| 36 method(target); | |
| 37 }; | |
| 38 } | |
| 39 }; | |
| 40 | |
| 41 var cacheId = 0; | |
| 42 | |
| 43 var cls = declare( | |
| 44 Descriptor, { | |
| 45 _instance: null, | |
| 46 _hasInstance: false, | |
| 47 _activationType: CALL_ACTIVATION, | |
| 48 _services: null, | |
| 49 _type: null, | |
| 50 _typeMap: null, | |
| 51 _factory: null, | |
| 52 _params: undefined, | |
| 53 _inject: null, | |
| 54 _cleanup: null, | |
| 55 _cacheId: null, | |
| 56 _owner: null, | |
| 57 | |
| 58 constructor: function (opts) { | |
| 59 safe.argumentNotNull(opts, "opts"); | |
| 60 safe.argumentNotNull(opts.owner, "opts.owner"); | |
| 61 | |
| 62 this._owner = opts.owner; | |
| 63 | |
| 64 if (!(opts.type || opts.factory)) | |
| 65 throw new Error( | |
| 66 "Either a type or a factory must be specified"); | |
| 67 | |
| 68 if (typeof (opts.type) === "string" && !opts.typeMap) | |
| 69 throw new Error( | |
| 70 "The typeMap is required when the type is specified by its name"); | |
| 71 | |
| 72 if (opts.activation) | |
| 73 this._activationType = opts.activation; | |
| 74 if (opts.type) | |
| 75 this._type = opts.type; | |
| 76 if (opts.params) | |
| 77 this._params = opts.params; | |
| 78 if (opts.inject) | |
| 79 this._inject = opts.inject instanceof Array ? opts.inject : [opts.inject]; | |
| 80 if (opts.services) | |
| 81 this._services = opts.services; | |
| 82 if (opts.factory) | |
| 83 this._factory = opts.factory; | |
| 84 if (opts.typeMap) | |
| 85 this._typeMap = opts.typeMap; | |
| 86 if (opts.cleanup) { | |
| 87 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) | |
| 88 throw new Error( | |
| 89 "The cleanup parameter must be either a function or a function name"); | |
| 90 | |
| 91 this._cleanup = opts.cleanup; | |
| 92 } | |
| 93 | |
| 94 this._cacheId = ++cacheId; | |
| 95 }, | |
| 96 | |
| 97 activate: function (context, name) { | |
| 98 | |
| 99 // if we have a local service records, register them first | |
| 100 | |
| 101 var instance; | |
| 102 | |
| 103 switch (this._activationType) { | |
| 104 case 1: // SINGLETON | |
| 105 // if the value is cached return it | |
| 106 if (this._hasInstance) | |
| 107 return this._instance; | |
| 108 | |
| 109 var tof = this._type || this._factory; | |
| 110 | |
| 111 // create the persistent cache identifier for the type | |
| 112 if (safe.isPrimitive(tof)) | |
| 113 this._cacheId = this._type; | |
| 114 else | |
| 115 this._cacheId = safe.oid(tof); | |
| 116 | |
| 117 // singletons are bound to the root container | |
| 118 var container = context.container.getRootContainer(); | |
| 119 | |
| 120 if (container.has(this._cacheId)) { | |
| 121 instance = container.get(this._cacheId); | |
| 122 } else { | |
| 123 instance = this._create(context, name); | |
| 124 container.store(this._cacheId, instance); | |
| 125 if (this._cleanup) | |
| 126 container.onDispose( | |
| 127 makeClenupCallback(instance, this._cleanup)); | |
| 128 } | |
| 129 | |
| 130 this._hasInstance = true; | |
| 131 return (this._instance = instance); | |
| 132 | |
| 133 case 2: // CONTAINER | |
| 134 //return a cached value | |
| 135 if (this._hasInstance) | |
| 136 return this._instance; | |
| 137 | |
| 138 // create an instance | |
| 139 instance = this._create(context, name); | |
| 140 | |
| 141 // the instance is bound to the container | |
| 142 if (this._cleanup) | |
| 143 this._owner.onDispose( | |
| 144 makeClenupCallback(instance, this._cleanup)); | |
| 145 | |
| 146 // cache and return the instance | |
| 147 this._hasInstance = true; | |
| 148 return (this._instance = instance); | |
| 149 case 3: // CONTEXT | |
| 150 //return a cached value if one exists | |
| 151 if (context.has(this._cacheId)) | |
| 152 return context.get(this._cacheId); | |
| 153 // context context activated instances are controlled by callers | |
| 154 return context.store(this._cacheId, this._create( | |
| 155 context, | |
| 156 name)); | |
| 157 case 4: // CALL | |
| 158 // per-call created instances are controlled by callers | |
| 159 return this._create(context, name); | |
| 160 case 5: // HIERARCHY | |
| 161 // hierarchy activated instances are behave much like container activated | |
| 162 // except they are created and bound to the child container | |
| 163 | |
| 164 // return a cached value | |
| 165 if (context.container.has(this._cacheId)) | |
| 166 return context.container.get(this._cacheId); | |
| 167 | |
| 168 instance = this._create(context, name); | |
| 169 | |
| 170 if (this._cleanup) | |
| 171 context.container.onDispose(makeClenupCallback( | |
| 172 instance, | |
| 173 this._cleanup)); | |
| 174 | |
| 175 return context.container.store(this._cacheId, instance); | |
| 176 default: | |
| 177 throw "Invalid activation type: " + this._activationType; | |
| 178 } | |
| 179 }, | |
| 180 | |
| 181 isInstanceCreated: function () { | |
| 182 return this._hasInstance; | |
| 183 }, | |
| 184 | |
| 185 getInstance: function () { | |
| 186 return this._instance; | |
| 187 }, | |
| 188 | |
| 189 _create: function (context, name) { | |
| 190 context.enter(name, this, Boolean(this._services)); | |
| 191 | |
| 192 if (this._activationType != CALL_ACTIVATION && | |
| 193 context.visit(this._cacheId) > 0) | |
| 194 throw new Error("Recursion detected"); | |
| 195 | |
| 196 if (this._services) { | |
| 197 for (var p in this._services) { | |
| 198 var sv = this._services[p]; | |
| 199 context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false)); | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 var instance; | |
| 204 | |
| 205 if (!this._factory) { | |
| 206 var ctor, type = this._type; | |
| 207 | |
| 208 if (typeof (type) === "string") { | |
| 209 ctor = this._typeMap[type]; | |
| 210 if (!ctor) | |
| 211 throw new Error("Failed to resolve the type '" + | |
| 212 type + "'"); | |
| 213 } else { | |
| 214 ctor = type; | |
| 215 } | |
| 216 | |
| 217 if (this._params === undefined) { | |
| 218 this._factory = function () { | |
| 219 return new ctor(); | |
| 220 }; | |
| 221 } else if (this._params instanceof Array) { | |
| 222 this._factory = function () { | |
| 223 var inst = lang.delegate(ctor.prototype); | |
| 224 var ret = ctor.apply(inst, arguments); | |
| 225 return typeof (ret) === "object" ? ret : inst; | |
| 226 }; | |
| 227 } else { | |
| 228 this._factory = function (param) { | |
| 229 return new ctor(param); | |
| 230 }; | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 if (this._params === undefined) { | |
| 235 instance = this._factory(); | |
| 236 } else if (this._params instanceof Array) { | |
| 237 instance = this._factory.apply(this, context.parse( | |
| 238 this._params, | |
| 239 ".params")); | |
| 240 } else { | |
| 241 instance = this._factory(context.parse( | |
| 242 this._params, | |
| 243 ".params")); | |
| 244 } | |
| 245 | |
| 246 if (this._inject) { | |
| 247 array.forEach(this._inject, function (spec) { | |
| 248 for (var m in spec) | |
| 249 injectMethod(instance, m, context, spec[m]); | |
| 250 }); | |
| 251 } | |
| 252 | |
| 253 context.leave(); | |
| 254 | |
| 255 return instance; | |
| 256 }, | |
| 257 | |
| 258 // @constructor {singleton} foo/bar/Baz | |
| 259 // @factory {singleton} | |
| 260 toString: function () { | |
| 261 var parts = []; | |
| 262 | |
| 263 parts.push(this._type ? "@constructor" : "@factory"); | |
| 264 | |
| 265 parts.push(activationNames[this._activationType]); | |
| 266 | |
| 267 if (typeof (this._type) === "string") | |
| 268 parts.push(this._type); | |
| 269 | |
| 270 return parts.join(" "); | |
| 271 } | |
| 272 | |
| 273 }); | |
| 274 | |
| 275 cls.SINGLETON = SINGLETON_ACTIVATION; | |
| 276 cls.CONTAINER = CONTAINER_ACTIVATION; | |
| 277 cls.CONTEXT = CONTEXT_ACTIVATION; | |
| 278 cls.CALL = CALL_ACTIVATION; | |
| 279 cls.HIERARCHY = HIERARCHY_ACTIVATION; | |
| 280 | |
| 281 var activationNames = [ | |
| 282 "", | |
| 283 "{singleton}", | |
| 284 "{container}", | |
| 285 "{context}", | |
| 286 "{call}", | |
| 287 "{hierarchy}" | |
| 288 ]; | |
| 289 | |
| 290 return cls; | |
| 291 }); | 
