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 });