comparison src/implab/di/ServiceDescriptor.js @ 34:27e8e9e38e07 default tip

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