|
1 YUI.add('cache-base', function (Y, NAME) { |
|
2 |
|
3 /** |
|
4 * The Cache utility provides a common configurable interface for components to |
|
5 * cache and retrieve data from a local JavaScript struct. |
|
6 * |
|
7 * @module cache |
|
8 * @main |
|
9 */ |
|
10 |
|
11 /** |
|
12 * Provides the base class for the YUI Cache utility. |
|
13 * |
|
14 * @submodule cache-base |
|
15 */ |
|
16 var LANG = Y.Lang, |
|
17 isDate = Y.Lang.isDate, |
|
18 |
|
19 /** |
|
20 * Base class for the YUI Cache utility. |
|
21 * @class Cache |
|
22 * @extends Base |
|
23 * @constructor |
|
24 */ |
|
25 Cache = function() { |
|
26 Cache.superclass.constructor.apply(this, arguments); |
|
27 }; |
|
28 |
|
29 ///////////////////////////////////////////////////////////////////////////// |
|
30 // |
|
31 // Cache static properties |
|
32 // |
|
33 ///////////////////////////////////////////////////////////////////////////// |
|
34 Y.mix(Cache, { |
|
35 /** |
|
36 * Class name. |
|
37 * |
|
38 * @property NAME |
|
39 * @type String |
|
40 * @static |
|
41 * @final |
|
42 * @value "cache" |
|
43 */ |
|
44 NAME: "cache", |
|
45 |
|
46 |
|
47 ATTRS: { |
|
48 ///////////////////////////////////////////////////////////////////////////// |
|
49 // |
|
50 // Cache Attributes |
|
51 // |
|
52 ///////////////////////////////////////////////////////////////////////////// |
|
53 |
|
54 /** |
|
55 * @attribute max |
|
56 * @description Maximum number of entries the Cache can hold. |
|
57 * Set to 0 to turn off caching. |
|
58 * @type Number |
|
59 * @default 0 |
|
60 */ |
|
61 max: { |
|
62 value: 0, |
|
63 setter: "_setMax" |
|
64 }, |
|
65 |
|
66 /** |
|
67 * @attribute size |
|
68 * @description Number of entries currently cached. |
|
69 * @type Number |
|
70 */ |
|
71 size: { |
|
72 readOnly: true, |
|
73 getter: "_getSize" |
|
74 }, |
|
75 |
|
76 /** |
|
77 * @attribute uniqueKeys |
|
78 * @description Validate uniqueness of stored keys. Default is false and |
|
79 * is more performant. |
|
80 * @type Boolean |
|
81 */ |
|
82 uniqueKeys: { |
|
83 value: false |
|
84 }, |
|
85 |
|
86 /** |
|
87 * @attribute expires |
|
88 * @description Absolute Date when data expires or |
|
89 * relative number of milliseconds. Zero disables expiration. |
|
90 * @type Date | Number |
|
91 * @default 0 |
|
92 */ |
|
93 expires: { |
|
94 value: 0, |
|
95 validator: function(v) { |
|
96 return Y.Lang.isDate(v) || (Y.Lang.isNumber(v) && v >= 0); |
|
97 } |
|
98 }, |
|
99 |
|
100 /** |
|
101 * @attribute entries |
|
102 * @description Cached entries. |
|
103 * @type Array |
|
104 */ |
|
105 entries: { |
|
106 readOnly: true, |
|
107 getter: "_getEntries" |
|
108 } |
|
109 } |
|
110 }); |
|
111 |
|
112 Y.extend(Cache, Y.Base, { |
|
113 ///////////////////////////////////////////////////////////////////////////// |
|
114 // |
|
115 // Cache private properties |
|
116 // |
|
117 ///////////////////////////////////////////////////////////////////////////// |
|
118 |
|
119 /** |
|
120 * Array of request/response objects indexed chronologically. |
|
121 * |
|
122 * @property _entries |
|
123 * @type Object[] |
|
124 * @private |
|
125 */ |
|
126 _entries: null, |
|
127 |
|
128 ///////////////////////////////////////////////////////////////////////////// |
|
129 // |
|
130 // Cache private methods |
|
131 // |
|
132 ///////////////////////////////////////////////////////////////////////////// |
|
133 |
|
134 /** |
|
135 * @method initializer |
|
136 * @description Internal init() handler. |
|
137 * @param config {Object} Config object. |
|
138 * @private |
|
139 */ |
|
140 initializer: function(config) { |
|
141 |
|
142 /** |
|
143 * @event add |
|
144 * @description Fired when an entry is added. |
|
145 * @param e {EventFacade} Event Facade with the following properties: |
|
146 * <dl> |
|
147 * <dt>entry (Object)</dt> <dd>The cached entry.</dd> |
|
148 * </dl> |
|
149 * @preventable _defAddFn |
|
150 */ |
|
151 this.publish("add", {defaultFn: this._defAddFn}); |
|
152 |
|
153 /** |
|
154 * @event flush |
|
155 * @description Fired when the cache is flushed. |
|
156 * @param e {EventFacade} Event Facade object. |
|
157 * @preventable _defFlushFn |
|
158 */ |
|
159 this.publish("flush", {defaultFn: this._defFlushFn}); |
|
160 |
|
161 /** |
|
162 * @event request |
|
163 * @description Fired when an entry is requested from the cache. |
|
164 * @param e {EventFacade} Event Facade with the following properties: |
|
165 * <dl> |
|
166 * <dt>request (Object)</dt> <dd>The request object.</dd> |
|
167 * </dl> |
|
168 */ |
|
169 |
|
170 /** |
|
171 * @event retrieve |
|
172 * @description Fired when an entry is retrieved from the cache. |
|
173 * @param e {EventFacade} Event Facade with the following properties: |
|
174 * <dl> |
|
175 * <dt>entry (Object)</dt> <dd>The retrieved entry.</dd> |
|
176 * </dl> |
|
177 */ |
|
178 |
|
179 // Initialize internal values |
|
180 this._entries = []; |
|
181 Y.log("Cache initialized", "info", "cache"); |
|
182 }, |
|
183 |
|
184 /** |
|
185 * @method destructor |
|
186 * @description Internal destroy() handler. |
|
187 * @private |
|
188 */ |
|
189 destructor: function() { |
|
190 this._entries = []; |
|
191 Y.log("Cache destroyed", "info", "cache"); |
|
192 }, |
|
193 |
|
194 ///////////////////////////////////////////////////////////////////////////// |
|
195 // |
|
196 // Cache protected methods |
|
197 // |
|
198 ///////////////////////////////////////////////////////////////////////////// |
|
199 |
|
200 /** |
|
201 * Sets max. |
|
202 * |
|
203 * @method _setMax |
|
204 * @protected |
|
205 */ |
|
206 _setMax: function(value) { |
|
207 // If the cache is full, make room by removing stalest element (index=0) |
|
208 var entries = this._entries; |
|
209 if(value > 0) { |
|
210 if(entries) { |
|
211 while(entries.length > value) { |
|
212 entries.shift(); |
|
213 } |
|
214 } |
|
215 } |
|
216 else { |
|
217 value = 0; |
|
218 this._entries = []; |
|
219 } |
|
220 return value; |
|
221 }, |
|
222 |
|
223 /** |
|
224 * Gets size. |
|
225 * |
|
226 * @method _getSize |
|
227 * @protected |
|
228 */ |
|
229 _getSize: function() { |
|
230 return this._entries.length; |
|
231 }, |
|
232 |
|
233 /** |
|
234 * Gets all entries. |
|
235 * |
|
236 * @method _getEntries |
|
237 * @protected |
|
238 */ |
|
239 _getEntries: function() { |
|
240 return this._entries; |
|
241 }, |
|
242 |
|
243 |
|
244 /** |
|
245 * Adds entry to cache. |
|
246 * |
|
247 * @method _defAddFn |
|
248 * @param e {EventFacade} Event Facade with the following properties: |
|
249 * <dl> |
|
250 * <dt>entry (Object)</dt> <dd>The cached entry.</dd> |
|
251 * </dl> |
|
252 * @protected |
|
253 */ |
|
254 _defAddFn: function(e) { |
|
255 var entries = this._entries, |
|
256 entry = e.entry, |
|
257 max = this.get("max"), |
|
258 pos; |
|
259 |
|
260 // If uniqueKeys is true and item exists with this key, then remove it. |
|
261 if (this.get("uniqueKeys")) { |
|
262 pos = this._position(e.entry.request); |
|
263 if (LANG.isValue(pos)) { |
|
264 entries.splice(pos, 1); |
|
265 } |
|
266 } |
|
267 |
|
268 // If the cache at or over capacity, make room by removing stalest |
|
269 // element(s) starting at index-0. |
|
270 while (max && entries.length >= max) { |
|
271 entries.shift(); |
|
272 } |
|
273 |
|
274 // Add entry to cache in the newest position, at the end of the array |
|
275 entries[entries.length] = entry; |
|
276 Y.log("Cached entry: " + Y.dump(entry), "info", "cache"); |
|
277 }, |
|
278 |
|
279 /** |
|
280 * Flushes cache. |
|
281 * |
|
282 * @method _defFlushFn |
|
283 * @param e {EventFacade} Event Facade object. |
|
284 * @protected |
|
285 */ |
|
286 _defFlushFn: function(e) { |
|
287 var entries = this._entries, |
|
288 details = e.details[0], |
|
289 pos; |
|
290 |
|
291 //passed an item, flush only that |
|
292 if(details && LANG.isValue(details.request)) { |
|
293 pos = this._position(details.request); |
|
294 |
|
295 if(LANG.isValue(pos)) { |
|
296 entries.splice(pos,1); |
|
297 |
|
298 Y.log("Flushed cache item " + Y.dump(details.request), "info", "cache"); |
|
299 } |
|
300 } |
|
301 //no item, flush everything |
|
302 else { |
|
303 this._entries = []; |
|
304 Y.log("Cache flushed", "info", "cache"); |
|
305 } |
|
306 }, |
|
307 |
|
308 /** |
|
309 * Default overridable method compares current request with given cache entry. |
|
310 * Returns true if current request matches the cached request, otherwise |
|
311 * false. Implementers should override this method to customize the |
|
312 * cache-matching algorithm. |
|
313 * |
|
314 * @method _isMatch |
|
315 * @param request {Object} Request object. |
|
316 * @param entry {Object} Cached entry. |
|
317 * @return {Boolean} True if current request matches given cached request, false otherwise. |
|
318 * @protected |
|
319 */ |
|
320 _isMatch: function(request, entry) { |
|
321 if(!entry.expires || new Date() < entry.expires) { |
|
322 return (request === entry.request); |
|
323 } |
|
324 return false; |
|
325 }, |
|
326 |
|
327 /** |
|
328 * Returns position of a request in the entries array, otherwise null. |
|
329 * |
|
330 * @method _position |
|
331 * @param request {Object} Request object. |
|
332 * @return {Number} Array position if found, null otherwise. |
|
333 * @protected |
|
334 */ |
|
335 _position: function(request) { |
|
336 // If cache is enabled... |
|
337 var entries = this._entries, |
|
338 length = entries.length, |
|
339 i = length-1; |
|
340 |
|
341 if((this.get("max") === null) || this.get("max") > 0) { |
|
342 // Loop through each cached entry starting from the newest |
|
343 for(; i >= 0; i--) { |
|
344 // Execute matching function |
|
345 if(this._isMatch(request, entries[i])) { |
|
346 return i; |
|
347 } |
|
348 } |
|
349 } |
|
350 |
|
351 return null; |
|
352 }, |
|
353 |
|
354 ///////////////////////////////////////////////////////////////////////////// |
|
355 // |
|
356 // Cache public methods |
|
357 // |
|
358 ///////////////////////////////////////////////////////////////////////////// |
|
359 |
|
360 /** |
|
361 * Adds a new entry to the cache of the format |
|
362 * {request:request, response:response, cached:cached, expires:expires}. |
|
363 * If cache is full, evicts the stalest entry before adding the new one. |
|
364 * |
|
365 * @method add |
|
366 * @param request {Object} Request value. |
|
367 * @param response {Object} Response value. |
|
368 */ |
|
369 add: function(request, response) { |
|
370 var expires = this.get("expires"); |
|
371 if(this.get("initialized") && ((this.get("max") === null) || this.get("max") > 0) && |
|
372 (LANG.isValue(request) || LANG.isNull(request) || LANG.isUndefined(request))) { |
|
373 this.fire("add", {entry: { |
|
374 request:request, |
|
375 response:response, |
|
376 cached: new Date(), |
|
377 expires: isDate(expires) ? expires : |
|
378 (expires ? new Date(new Date().getTime() + this.get("expires")) : null) |
|
379 }}); |
|
380 } |
|
381 else { |
|
382 Y.log("Could not add " + Y.dump(response) + " to cache for " + Y.dump(request), "info", "cache"); |
|
383 } |
|
384 }, |
|
385 |
|
386 /** |
|
387 * Flushes cache. |
|
388 * |
|
389 * @method flush |
|
390 */ |
|
391 flush: function(request) { |
|
392 this.fire("flush", { request: (LANG.isValue(request) ? request : null) }); |
|
393 }, |
|
394 |
|
395 /** |
|
396 * Retrieves cached object for given request, if available, and refreshes |
|
397 * entry in the cache. Returns null if there is no cache match. |
|
398 * |
|
399 * @method retrieve |
|
400 * @param request {Object} Request object. |
|
401 * @return {Object} Cached object with the properties request and response, or null. |
|
402 */ |
|
403 retrieve: function(request) { |
|
404 // If cache is enabled... |
|
405 var entries = this._entries, |
|
406 length = entries.length, |
|
407 entry = null, |
|
408 pos; |
|
409 |
|
410 if((length > 0) && ((this.get("max") === null) || (this.get("max") > 0))) { |
|
411 this.fire("request", {request: request}); |
|
412 |
|
413 pos = this._position(request); |
|
414 |
|
415 if(LANG.isValue(pos)) { |
|
416 entry = entries[pos]; |
|
417 |
|
418 this.fire("retrieve", {entry: entry}); |
|
419 |
|
420 // Refresh the position of the cache hit |
|
421 if(pos < length-1) { |
|
422 // Remove element from its original location |
|
423 entries.splice(pos,1); |
|
424 // Add as newest |
|
425 entries[entries.length] = entry; |
|
426 Y.log("Refreshed cache entry: " + Y.dump(entry) + |
|
427 " for request: " + Y.dump(request), "info", "cache"); |
|
428 } |
|
429 |
|
430 Y.log("Retrieved cached response: " + Y.dump(entry) + |
|
431 " for request: " + Y.dump(request), "info", "cache"); |
|
432 return entry; |
|
433 } |
|
434 } |
|
435 return null; |
|
436 } |
|
437 }); |
|
438 |
|
439 Y.Cache = Cache; |
|
440 |
|
441 |
|
442 }, '@VERSION@', {"requires": ["base"]}); |