-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmock_cache_handler.js
More file actions
296 lines (283 loc) · 8.45 KB
/
mock_cache_handler.js
File metadata and controls
296 lines (283 loc) · 8.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/*
* Mock Cache Handler for Node.js applications using Application Container Cloud Service
* to allow for offline testing. Manages the cached objects in a local hashmap instead of
* in the online cache.
* This is designed to provide the same interfaces as the Cache object, but without worrying
* about the caching infrastructure.
* By: Callan Howell-Pavia (callan.howell-pavia@oracle.com)
*
* This implementation is designed to provide the a simple caching interface, in
* typical node style, using callbacks.
*
* Internal structure is slightly different, but should appear the same to clients
* Internally, to handle TTL, objects have a 'value' and 'timeout' attribute.
*/
var _globalMockCache = {};
function MockCache(cacheName){
//URI Encode the cacheName???
this._cacheName = encodeURIComponent(cacheName);
if(!_globalMockCache[this._cacheName]){
_globalMockCache[this._cacheName] = { cache: {}, count: 0};
}
this._cache = _globalMockCache[this._cacheName].cache;
this._count = _globalMockCache[this._cacheName].count;
}
MockCache.prototype.get = function(key, objType, callback){
if(key == undefined || typeof key == 'function'){
throw new SyntaxError("key is a required parameter for get!");
}
if(typeof objType == 'function'){
callback = objType;
objType = null;
}
if(typeof key == 'number'){
key = String(key);
}
if(typeof key != 'string'){
throw new TypeError("Caching keys must be strings or numbers!");
}
if(objType && typeof objType != 'string'){
throw new TypeError("Object hints for getting a cache entry must be a valid javascript object type, e.g. 'string'");
}
//Retrieve the entry from the cache
var obj = this._cache[key];
if(!obj){
//respond with a null object
callback(null, null);
return;
}
//Coerce the response into appropriate format
try{
var ret = _coerceObjectType(obj.value, objType);
}catch(ex){
callback(ex, null);
return;
}
callback(null, ret);
};
//Add an entry to the cache
MockCache.prototype.put = function(key, val, ttl, isBuffer, callback) {
if(key == undefined || val == undefined || typeof key == 'function' || typeof val == 'function'){
throw new SyntaxError("key and val are required parameters for put!");
}
if(typeof ttl == 'function'){
callback = ttl;
ttl = null;
isBuffer = false;
}
if(typeof ttl == 'boolean'){
if(typeof isBuffer == 'function'){
callback = isBuffer;
}
isBuffer = ttl;
ttl = null;
}
if(typeof isBuffer == 'function'){
callback = isBuffer;
isBuffer = false;
}
if(typeof key == 'number'){
key = String(key);
}
if(typeof key != 'string'){
throw new TypeError("Caching keys must be strings or numbers!");
}
if(ttl && typeof ttl != 'number'){
throw new TypeError("Time to live for cache entries must a positive number!");
}
if(!this._cache[key]){
this._count++;
}
if(this._cache[key] && this._cache[key].timeout){
clearTimeout(this._cache[key].timeout);
}
this._cache[key] = {};
if(!isBuffer){
this._cache[key].value = val;
}else{
this._cache[key].value = val.toString('utf8');
}
//Handle ttl
if(ttl && ttl > 0){
var self = this;
this._cache[key].timeout = setTimeout(function(){
if(self._cache[key]){
self._cache[key] = null;
self._count--;
}
}, ttl);
}
callback(null);
};
//Add an entry to the cache if the key is available, otherwise reject.
MockCache.prototype.putIfAbsent = function(key, val, ttl, isBuffer, callback) {
if(key == undefined || val == undefined || typeof key == 'function' || typeof val == 'function'){
throw new SyntaxError("key and val are required parameters for putIfAbsent!");
}
if(typeof ttl == 'function'){
callback = ttl;
ttl = null;
isBuffer = false;
}
if(typeof ttl == 'boolean'){
if(typeof isBuffer == 'function'){
callback = isBuffer;
}
isBuffer = ttl;
ttl = null;
}
if(typeof isBuffer == 'function'){
callback = isBuffer;
isBuffer = false;
}
if(typeof key == 'number'){
key = String(key);
}
if(typeof key != 'string'){
throw new TypeError("Caching keys must be strings or numbers!");
}
if(ttl && typeof ttl != 'number'){
throw new TypeError("Time to live for cache entries must a positive number!");
}
if(this._cache[key]){
callback(new Error('Did not insert entry, key already exists.'));
return;
}
this._cache[key] = {};
if(!isBuffer){
this._cache[key].value = val;
}else{
this._cache[key].value = val.toString('utf8');
}
this._count++;
//Handle ttl
if(ttl && ttl > 0){
this._cache[key].timeout = setTimeout(function(){
if(self._cache[key]){
self._cache[key] = null;
self._count--;
}
}, ttl);
}
callback(null);
};
//Add an entry to the cache if the current value equals an old value.
MockCache.prototype.replace = function(key, val, oldVal, ttl, callback) {
if(key == undefined || val == undefined || oldVal == undefined
|| typeof key == 'function' || typeof val == 'function' || typeof oldVal == 'function'){
throw new SyntaxError("key, val and oldVal are required parameters for replace!");
}
if(typeof ttl == 'function'){
callback = ttl;
ttl = null;
}
if(typeof key == 'number'){
key = String(key);
}
if(typeof key != 'string'){
throw new TypeError("Caching keys must be strings or numbers!");
}
if(ttl && typeof ttl != 'number'){
throw new TypeError("Time to live for cache entries must a positive number!");
}
if(!this._cache[key] || this._cache[key].value != oldVal){
callback(new Error('Did not insert entry, cached value does not equal oldVal supplied.'));
return;
}
if(this._cache[key].timeout){
clearTimeout(this._cache[key].timeout);
}
this._cache[key] = {};
if(Buffer.isBuffer(val)){
this._cache[key].value = val.toString('utf8');
}else{
this._cache[key].value = val;
}
//Handle ttl
if(ttl && ttl > 0){
this._cache[key].timeout = setTimeout(function(){
if(self._cache[key]){
self._cache[key] = null;
self._count--;
}
}, ttl);
}
callback(null);
};
//Delete an entry from the cache
MockCache.prototype.delete = function(key, callback) {
if(key == undefined || typeof key == 'function'){
throw new SyntaxError("key is a required parameter for delete!");
}
if(typeof key == 'number'){
key = String(key);
}
if(typeof key != 'string'){
throw new TypeError("Caching keys must be strings or numbers!");
}
if(this._cache[key] && this._cache[key].timeout){
clearTimeout(this._cache[key].timeout);
}
if(this._cache[key]){
this._count--;
}
this._cache[key] = null;
callback(null);
};
//Clear the whole cache
MockCache.prototype.clear = function(callback) {
this._cache = {};
this._count = 0;
callback();
};
/*Get the cache stats
*Response object has the following fields:
*'cache' - cache name
*'count' - number of objects in the cache
*'size' - size of the cache (in bytes?)
*/
MockCache.prototype.stats = function(callback){
//Could calculate the size, but seems time intensive for no real gain
//For now it is always count*4. Sorry folks.
var cacheInfo = { cache: this._cacheName,
count: this._count,
size: this._count*4
};
callback(null, cacheInfo);
};
/*
* Internal method for coercing the response back into the expected format.
* Takes an 'object hint' which defines the expected response format,
* but if one is not provided, responds with the plain object. Should be fine,
* since no serialization happens in the mock cache.
*/
function _coerceObjectType(obj, objHint){
switch(objHint){
case 'string':
//Response that comes back is application/octet-stream, which we will return raw
return obj;
case 'buffer':
return Buffer.from(obj, 'utf8');
case 'number':
var num = Number(obj);
if(isNaN(num)){
throw new TypeError("'number was requested as a type, but result is NaN!");
}
return num;
case 'array':
if(obj[0] == undefined){
//Is an object, not an array, so we will construct an array of just this object
return new Array(pObj);
}
return obj;
case 'boolean':
//Today's javascript fun-fact: 'false' is truthy!
if(obj == 'false'){
return false;
}
return Boolean(obj);
default:
return obj;
}
}
module.exports = MockCache;