@itentialopensource/adapter-utils 4.44.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +3 -0
- package/.eslintrc.js +18 -0
- package/.jshintrc +3 -0
- package/CHANGELOG.md +1398 -0
- package/CODE_OF_CONDUCT.md +48 -0
- package/CONTRIBUTING.md +173 -0
- package/LICENSE +201 -0
- package/README.md +12 -0
- package/actionSchema.json +186 -0
- package/error.json +148 -0
- package/index.js +7 -0
- package/lib/connectorRest.js +4083 -0
- package/lib/dbUtil.js +1300 -0
- package/lib/propertyUtil.js +1012 -0
- package/lib/requestHandler.js +1175 -0
- package/lib/restHandler.js +1309 -0
- package/lib/throttle.js +1289 -0
- package/lib/translatorUtil.js +1137 -0
- package/package.json +61 -0
- package/propertiesSchema.json +840 -0
- package/utils/pre-commit.sh +26 -0
- package/utils/setup.js +32 -0
- package/utils/testRunner.js +259 -0
package/lib/dbUtil.js
ADDED
|
@@ -0,0 +1,1300 @@
|
|
|
1
|
+
/* Required libraries. */
|
|
2
|
+
/* global g_redis log */
|
|
3
|
+
/* eslint consistent-return:warn */
|
|
4
|
+
/* eslint no-underscore-dangle: [2, { "allow": ["_id"] }] */
|
|
5
|
+
/* eslint no-unused-vars:warn */
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const uuid = require('uuid');
|
|
9
|
+
|
|
10
|
+
/* Fetch in the other needed components for the this Adaptor */
|
|
11
|
+
const { MongoClient } = require('mongodb');
|
|
12
|
+
|
|
13
|
+
// Other global variables
|
|
14
|
+
let adapterDir = '';
|
|
15
|
+
let saveFS = false;
|
|
16
|
+
let storDir = `${adapterDir}/storage`;
|
|
17
|
+
let id = null;
|
|
18
|
+
|
|
19
|
+
const Storage = {
|
|
20
|
+
UNDEFINED: 0,
|
|
21
|
+
DBINFO: 1,
|
|
22
|
+
ADAPTERDB: 2,
|
|
23
|
+
FILESYSTEM: 3,
|
|
24
|
+
IAPDB: 4
|
|
25
|
+
};
|
|
26
|
+
Object.freeze(Storage);
|
|
27
|
+
|
|
28
|
+
/* DB UTILS INTERNAL FUNCTIONS */
|
|
29
|
+
/** getFromJson
|
|
30
|
+
* @summary returns information from a json file on the file system
|
|
31
|
+
*/
|
|
32
|
+
function getFromJson(fileName, filter) {
|
|
33
|
+
const origin = `${id}-dbUtil-getFromJson`;
|
|
34
|
+
log.trace(origin);
|
|
35
|
+
|
|
36
|
+
// verify the required data has been provided
|
|
37
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
38
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// get the entity and the action if this is a metric
|
|
43
|
+
let ent = null;
|
|
44
|
+
let act = null;
|
|
45
|
+
let metric = false;
|
|
46
|
+
const useFilter = filter;
|
|
47
|
+
if (useFilter && useFilter.metric) {
|
|
48
|
+
metric = true;
|
|
49
|
+
if (useFilter.metric.entity) {
|
|
50
|
+
ent = useFilter.metric.entity;
|
|
51
|
+
}
|
|
52
|
+
if (useFilter.metric.action) {
|
|
53
|
+
act = useFilter.metric.action;
|
|
54
|
+
}
|
|
55
|
+
if (!ent || !act) {
|
|
56
|
+
log.warn(`${origin}: metric provided with one of entity/action, require both`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
delete useFilter.metric;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// make sure what we need exists and has been provided
|
|
64
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
65
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to retrieve`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// determine if the file exists so we retrieve the data
|
|
70
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
71
|
+
const content = JSON.parse(fs.readFileSync(`${storDir}/${fileName}.json`, 'utf-8'));
|
|
72
|
+
const toReturn = [];
|
|
73
|
+
|
|
74
|
+
if (metric) {
|
|
75
|
+
// if metric need to match the entity and action
|
|
76
|
+
content.table.forEach((item) => {
|
|
77
|
+
if (ent === item.entity && act === item.action) {
|
|
78
|
+
toReturn.push(item);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
// if not metric must match the items in the filter
|
|
83
|
+
const key = Object.keys(useFilter);
|
|
84
|
+
content.table.forEach((item) => {
|
|
85
|
+
let push = true;
|
|
86
|
+
key.forEach((fil) => {
|
|
87
|
+
if (useFilter[fil] !== item[fil]) push = false;
|
|
88
|
+
});
|
|
89
|
+
if (push) toReturn.push(item);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const filtered = content.table.filter((el) => {
|
|
93
|
+
Object.keys(useFilter).forEach((obj) => {
|
|
94
|
+
if (el[obj] !== useFilter[obj]) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
100
|
+
return toReturn;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// if no file, nothing to return
|
|
104
|
+
return null;
|
|
105
|
+
} catch (ex) {
|
|
106
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** countJSON
|
|
112
|
+
* @summary returns a count from a json storage file
|
|
113
|
+
*/
|
|
114
|
+
function countJSON(fileName, filter) {
|
|
115
|
+
const origin = `${id}-dbUtil-countJSON`;
|
|
116
|
+
log.trace(origin);
|
|
117
|
+
|
|
118
|
+
// verify the required data has been provided
|
|
119
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
120
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// make sure what we need exists and has been provided
|
|
126
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
127
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to count`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// determine if the file exists so we can count data from it
|
|
132
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
133
|
+
const data = getFromJson(fileName, filter);
|
|
134
|
+
if (data) {
|
|
135
|
+
return data.length;
|
|
136
|
+
}
|
|
137
|
+
return -1;
|
|
138
|
+
}
|
|
139
|
+
} catch (ex) {
|
|
140
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** saveAsJson
|
|
146
|
+
* @summary saves information into a json storage file
|
|
147
|
+
*/
|
|
148
|
+
function saveAsJson(fileName, data) {
|
|
149
|
+
const origin = `${id}-dbUtil-saveAsJson`;
|
|
150
|
+
log.trace(origin);
|
|
151
|
+
|
|
152
|
+
// verify the required data has been provided
|
|
153
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
154
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// get the entity and the action if this is a metric
|
|
159
|
+
let ent = null;
|
|
160
|
+
let act = null;
|
|
161
|
+
let metric = false;
|
|
162
|
+
const useData = data;
|
|
163
|
+
if (useData && useData.metric) {
|
|
164
|
+
metric = true;
|
|
165
|
+
if (useData.metric.entity) {
|
|
166
|
+
ent = useData.metric.entity;
|
|
167
|
+
}
|
|
168
|
+
if (useData.metric.action) {
|
|
169
|
+
act = useData.metric.action;
|
|
170
|
+
}
|
|
171
|
+
if (!ent || !act) {
|
|
172
|
+
log.warn(`${origin}: metric provided with one of entity/action, require both`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
delete useData.metric;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// make sure what we need exists and has been provided
|
|
180
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
181
|
+
// need to make the storage directory if it does not exist
|
|
182
|
+
fs.mkdirSync(`${storDir}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// determine if the file exists so we add data to it
|
|
186
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
187
|
+
// have to read, append & save
|
|
188
|
+
const content = JSON.parse(fs.readFileSync(`${storDir}/${fileName}.json`, 'utf-8'));
|
|
189
|
+
let exists = false;
|
|
190
|
+
|
|
191
|
+
content.table.forEach((item) => {
|
|
192
|
+
const toPush = item;
|
|
193
|
+
if (metric) {
|
|
194
|
+
// if it is a metric and we already have entity and action need to edit the data
|
|
195
|
+
if (ent === item.entity && act === item.action) {
|
|
196
|
+
exists = true;
|
|
197
|
+
Object.keys(useData).forEach((key) => {
|
|
198
|
+
if (key === '$inc') {
|
|
199
|
+
Object.keys(useData[key]).forEach((inc) => {
|
|
200
|
+
if (!toPush[inc]) {
|
|
201
|
+
toPush[inc] = useData[key][inc];
|
|
202
|
+
}
|
|
203
|
+
toPush[inc] += useData[key][inc];
|
|
204
|
+
});
|
|
205
|
+
} else if (key === '$set') {
|
|
206
|
+
Object.keys(useData[key]).forEach((set) => {
|
|
207
|
+
toPush[set] = useData[key][set];
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
if (!exists) {
|
|
215
|
+
// push new thing to table.
|
|
216
|
+
const toPush = {};
|
|
217
|
+
const keysArray = Object.keys(useData);
|
|
218
|
+
keysArray.forEach((i) => {
|
|
219
|
+
const newKeys = Object.keys(useData[i]);
|
|
220
|
+
newKeys.forEach((j) => {
|
|
221
|
+
toPush[j] = useData[i][j];
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
content.table.push(toPush);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// now that is updated, write the file back out
|
|
228
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(content, null, 2));
|
|
229
|
+
return useData;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// if the file has not been created yet
|
|
233
|
+
const obj = { table: [] };
|
|
234
|
+
const toPush = {};
|
|
235
|
+
const keysArray = Object.keys(data);
|
|
236
|
+
keysArray.forEach((outer) => {
|
|
237
|
+
const newKeys = Object.keys(data[outer]);
|
|
238
|
+
newKeys.forEach((inner) => {
|
|
239
|
+
toPush[inner] = data[outer][inner];
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
obj.table.push(toPush);
|
|
243
|
+
|
|
244
|
+
// write the file out to the file system
|
|
245
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(obj, null, 2));
|
|
246
|
+
return data;
|
|
247
|
+
} catch (ex) {
|
|
248
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** removeFromJSON
|
|
254
|
+
* @summary removes information from a json storage file
|
|
255
|
+
*/
|
|
256
|
+
function removeFromJSON(fileName, filter, multiple) {
|
|
257
|
+
const origin = `${id}-dbUtil-removeFromJSON`;
|
|
258
|
+
log.trace(origin);
|
|
259
|
+
|
|
260
|
+
// verify the required data has been provided
|
|
261
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
262
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// get the entity and the action if this is a metric
|
|
267
|
+
let ent = null;
|
|
268
|
+
let act = null;
|
|
269
|
+
let metric = false;
|
|
270
|
+
const useFilter = filter;
|
|
271
|
+
if (useFilter && useFilter.metric) {
|
|
272
|
+
metric = true;
|
|
273
|
+
if (useFilter.metric.entity) {
|
|
274
|
+
ent = useFilter.metric.entity;
|
|
275
|
+
}
|
|
276
|
+
if (useFilter.metric.action) {
|
|
277
|
+
act = useFilter.metric.action;
|
|
278
|
+
}
|
|
279
|
+
if (!ent && !act) {
|
|
280
|
+
log.warn(`${origin}: metric provided with one of entity/action, require both`);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
delete useFilter.metric;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
// make sure what we need exists and has been provided
|
|
288
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
289
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to remove`);
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// determine if the file exists so we remove data from it
|
|
294
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
295
|
+
const content = JSON.parse(fs.readFileSync(`${storDir}/${fileName}.json`, 'utf-8'));
|
|
296
|
+
const toReturn = [];
|
|
297
|
+
|
|
298
|
+
// if this is a metric make sure the entity and action match
|
|
299
|
+
if (metric) {
|
|
300
|
+
content.table = content.table.filter((item) => {
|
|
301
|
+
if (ent === item.entity && act === item.action) {
|
|
302
|
+
toReturn.push(item);
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
return true;
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
// go through content to determine if item matches the filter
|
|
309
|
+
let ctr = 0;
|
|
310
|
+
// create the contents that are being removed
|
|
311
|
+
const removed = content.table.filter((el) => {
|
|
312
|
+
Object.keys(useFilter).forEach((obj) => {
|
|
313
|
+
if (el[obj] !== useFilter[obj]) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
ctr += 1;
|
|
318
|
+
if (!multiple && ctr > 1) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
});
|
|
323
|
+
let ctr1 = 0;
|
|
324
|
+
// remove the items from the contents
|
|
325
|
+
content.table = content.table.filter((el, i) => {
|
|
326
|
+
Object.keys(useFilter).forEach((obj) => {
|
|
327
|
+
if (el[obj] !== useFilter[obj]) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
ctr1 += 1;
|
|
332
|
+
if (!multiple && ctr1 > 1) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// write the contents back to the file
|
|
339
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(content, null, 2));
|
|
340
|
+
return removed;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// write the contents back to the file
|
|
344
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(content, null, 2));
|
|
345
|
+
return toReturn;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
log.error(`${origin}: Collection ${fileName} does not exist`);
|
|
349
|
+
return null;
|
|
350
|
+
} catch (ex) {
|
|
351
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** deleteJSON
|
|
357
|
+
* @summary deletes a json storage file
|
|
358
|
+
*/
|
|
359
|
+
function deleteJSON(fileName) {
|
|
360
|
+
const origin = `${id}-dbUtil-deleteJSON`;
|
|
361
|
+
log.trace(origin);
|
|
362
|
+
|
|
363
|
+
// verify the required data has been provided
|
|
364
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
365
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
// make sure what we need exists and has been provided
|
|
371
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
372
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to delete`);
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// determine if the file exists so we remove - also assume we add a .json suffix
|
|
377
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
378
|
+
fs.remove(`${storDir}/${fileName}.json`).catch((some) => {
|
|
379
|
+
log.info(`${origin}: ${some}`);
|
|
380
|
+
fs.rmdirSync(`${storDir}/${fileName}.json`);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// successful -- return the fileName
|
|
385
|
+
return fileName;
|
|
386
|
+
} catch (ex) {
|
|
387
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class DBUtil {
|
|
393
|
+
/**
|
|
394
|
+
* These are database utilities that can be used by the adapter to interact with a
|
|
395
|
+
* mongo database
|
|
396
|
+
* @constructor
|
|
397
|
+
*/
|
|
398
|
+
constructor(prongId, properties, directory) {
|
|
399
|
+
this.myid = prongId;
|
|
400
|
+
id = prongId;
|
|
401
|
+
this.baseDir = directory;
|
|
402
|
+
adapterDir = this.baseDir;
|
|
403
|
+
storDir = `${adapterDir}/storage`;
|
|
404
|
+
this.props = properties;
|
|
405
|
+
this.adapterMongoClient = null;
|
|
406
|
+
|
|
407
|
+
// set up the properties I care about
|
|
408
|
+
this.refreshProperties(properties);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* refreshProperties is used to set up all of the properties for the db utils.
|
|
413
|
+
* It allows properties to be changed later by simply calling refreshProperties rather
|
|
414
|
+
* than having to restart the db utils.
|
|
415
|
+
*
|
|
416
|
+
* @function refreshProperties
|
|
417
|
+
* @param {Object} properties - an object containing all of the properties
|
|
418
|
+
*/
|
|
419
|
+
refreshProperties(properties) {
|
|
420
|
+
const origin = `${this.myid}-dbUtil-refreshProperties`;
|
|
421
|
+
log.trace(origin);
|
|
422
|
+
this.dburl = null;
|
|
423
|
+
this.dboptions = {};
|
|
424
|
+
this.database = this.myid;
|
|
425
|
+
|
|
426
|
+
// verify the necessary information was received
|
|
427
|
+
if (!properties) {
|
|
428
|
+
log.error(`${origin}: DB Utils received no properties!`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!properties.mongo || !properties.mongo.host) {
|
|
432
|
+
log.info(`${origin}: No default adapter database configured!`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// set the database port
|
|
437
|
+
let port = 27017;
|
|
438
|
+
if (properties.mongo.port) {
|
|
439
|
+
port = properties.mongo.port;
|
|
440
|
+
}
|
|
441
|
+
// set the database
|
|
442
|
+
if (properties.mongo.database) {
|
|
443
|
+
this.database = properties.mongo.database;
|
|
444
|
+
}
|
|
445
|
+
// set the user
|
|
446
|
+
let username = null;
|
|
447
|
+
if (properties.mongo.username) {
|
|
448
|
+
username = properties.mongo.username;
|
|
449
|
+
}
|
|
450
|
+
// set the password
|
|
451
|
+
let password = null;
|
|
452
|
+
if (properties.mongo.password) {
|
|
453
|
+
password = properties.mongo.password;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// format the database url
|
|
457
|
+
this.dburl = 'mongodb://';
|
|
458
|
+
log.info(`${origin}: Default adapter database at: ${properties.mongo.host}`);
|
|
459
|
+
|
|
460
|
+
if (username) {
|
|
461
|
+
this.dburl += `${encodeURIComponent(username)}:${encodeURIComponent(password)}@`;
|
|
462
|
+
log.info(`${origin}: Default adapter database will use authentication.`);
|
|
463
|
+
}
|
|
464
|
+
this.dburl += `${encodeURIComponent(properties.mongo.host)}:${encodeURIComponent(port)}/${encodeURIComponent(this.database)}`;
|
|
465
|
+
|
|
466
|
+
// are we using a replication set need to add it to the url
|
|
467
|
+
if (properties.mongo.replSet) {
|
|
468
|
+
this.dburl += `?${properties.mongo.replSet}`;
|
|
469
|
+
log.info(`${origin}: Default adapter database will use replica set.`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Do we need SSL to connect to the database
|
|
473
|
+
if (properties.mongo.db_ssl && properties.mongo.db_ssl.enabled === true) {
|
|
474
|
+
log.info(`${origin}: Default adapter database will use SSL.`);
|
|
475
|
+
this.dboptions.ssl = true;
|
|
476
|
+
|
|
477
|
+
// validate the server's certificate against a known certificate authority?
|
|
478
|
+
if (properties.mongo.db_ssl.accept_invalid_cert === false) {
|
|
479
|
+
this.dboptions.sslValidate = true;
|
|
480
|
+
log.info(`${origin}: Default adapter database will use Certificate based SSL.`);
|
|
481
|
+
// if validation is enabled, we need to read the CA file
|
|
482
|
+
if (properties.mongo.db_ssl.ca_file) {
|
|
483
|
+
try {
|
|
484
|
+
this.dboptions.sslCA = [fs.readFileSync(properties.mongo.db_ssl.ca_file)];
|
|
485
|
+
} catch (err) {
|
|
486
|
+
log.error(`${origin}: Error: unable to load Mongo CA file: ${err}`);
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
log.error(`${origin}: Error: Certificate validation is enabled but a CA is not specified.`);
|
|
490
|
+
}
|
|
491
|
+
if (properties.mongo.db_ssl.key_file) {
|
|
492
|
+
try {
|
|
493
|
+
this.dboptions.sslKey = [fs.readFileSync(properties.mongo.db_ssl.key_file)];
|
|
494
|
+
} catch (err) {
|
|
495
|
+
log.error(`${origin}: Error: Unable to load Mongo Key file: ${err}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (properties.mongo.db_ssl.cert_file) {
|
|
499
|
+
try {
|
|
500
|
+
this.dboptions.sslCert = [fs.readFileSync(properties.mongo.db_ssl.cert_file)];
|
|
501
|
+
} catch (err) {
|
|
502
|
+
log.error(`${origin}: Error: Unable to load Mongo Certificate file: ${err}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
this.dboptions.sslValidate = false;
|
|
507
|
+
log.info(`${origin}: Default adapter database not using Certificate based SSL.`);
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
log.info(`${origin}: Default adapter database not using SSL.`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// if we were provided with a path to save metrics - instead of just a boolean
|
|
514
|
+
// will assume this is a place where all adapter stuff can go!
|
|
515
|
+
saveFS = this.props.save_metric || false;
|
|
516
|
+
if (saveFS && typeof saveFS === 'string' && saveFS !== '') {
|
|
517
|
+
storDir = saveFS;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Call to determine the storage target. If the storage is a Mongo database, then
|
|
523
|
+
* the client connection and database is returned.
|
|
524
|
+
*
|
|
525
|
+
* @function determineStorage
|
|
526
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
527
|
+
* @param {callback} callback - the error, the storage, the Mongo client connection, the Mongo database
|
|
528
|
+
*/
|
|
529
|
+
determineStorage(dbInfo, callback) {
|
|
530
|
+
const origin = `${this.myid}-dbUtil-determineStorage`;
|
|
531
|
+
|
|
532
|
+
if (dbInfo) {
|
|
533
|
+
// priority 1 - use the dbInfo passed in
|
|
534
|
+
if (dbInfo.dburl && dbInfo.database) {
|
|
535
|
+
MongoClient.connect(dbInfo.dburl, dbInfo.dboptions, (err, mongoClient) => {
|
|
536
|
+
if (err) {
|
|
537
|
+
log.error(`${origin}: Error! Failed to connect to database: ${err}`);
|
|
538
|
+
return callback(err, null, null, null);
|
|
539
|
+
}
|
|
540
|
+
log.debug('using dbinfo');
|
|
541
|
+
return callback(null, Storage.DBINFO, mongoClient, dbInfo.database);
|
|
542
|
+
});
|
|
543
|
+
} else {
|
|
544
|
+
const err = 'Error! Marlformed dbInfo';
|
|
545
|
+
return callback(err, null, null, Storage.DBINFO);
|
|
546
|
+
}
|
|
547
|
+
} else if (this.adapterMongoClient) {
|
|
548
|
+
// priority 2 - use the adapter database
|
|
549
|
+
log.debug('using adapter db');
|
|
550
|
+
return callback(null, Storage.ADAPTERDB, this.adapterMongoClient, this.database);
|
|
551
|
+
} else if (this.dburl === null && dbInfo === null) {
|
|
552
|
+
// priority 3 - use the filesystem
|
|
553
|
+
log.debug('using filesystem');
|
|
554
|
+
return callback(null, Storage.FILESYSTEM, null, null);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Call to connect and authenticate to the adapter database
|
|
560
|
+
*
|
|
561
|
+
* @function connect
|
|
562
|
+
*/
|
|
563
|
+
connect(callback) {
|
|
564
|
+
// callback format: (this.alive, mongoClient); generally mongoClient should only be used if this.alive is true.
|
|
565
|
+
const origin = `${this.myid}-dbUtil-connect`;
|
|
566
|
+
log.trace(origin);
|
|
567
|
+
|
|
568
|
+
// const options = (replSetEnabled === true) ? { replSet: opts } : { server: opts };
|
|
569
|
+
log.debug(`${origin}: Connecting to MongoDB with options ${JSON.stringify(this.dboptions)}`);
|
|
570
|
+
|
|
571
|
+
// Now we will start the process of connecting to mongo db
|
|
572
|
+
return MongoClient.connect(this.dburl, this.dboptions, (err, mongoClient) => {
|
|
573
|
+
if (!mongoClient) {
|
|
574
|
+
log.error(`${origin}: Error! Exiting... Must start MongoDB first ${err}`);
|
|
575
|
+
this.alive = false;
|
|
576
|
+
return callback(this.alive);
|
|
577
|
+
}
|
|
578
|
+
mongoClient.on('close', () => {
|
|
579
|
+
this.alive = false;
|
|
580
|
+
log.error(`${origin}: MONGO CONNECTION LOST...`);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
mongoClient.on('reconnect', () => {
|
|
584
|
+
// we still need to check if we are properly authenticated
|
|
585
|
+
// so we just list collections to test it.
|
|
586
|
+
this.clientDB.listCollections().toArray((error) => {
|
|
587
|
+
if (error) {
|
|
588
|
+
log.error(`${origin}: ${error}`);
|
|
589
|
+
this.alive = false;
|
|
590
|
+
} else {
|
|
591
|
+
log.info(`${origin}: MONGO CONNECTION BACK...`);
|
|
592
|
+
this.alive = true;
|
|
593
|
+
this.adapterMongoClient = mongoClient;
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
mongoClient.db(this.database).serverConfig.on('left', (type) => {
|
|
599
|
+
if (type === 'primary') {
|
|
600
|
+
this.alive = false;
|
|
601
|
+
log.info(`${origin}: MONGO PRIMARY CONNECTION LOST...`);
|
|
602
|
+
} else if (type === 'secondary') {
|
|
603
|
+
log.info(`${origin}: MONGO SECONDARY CONNECTION LOST...`);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
mongoClient.db(this.database).serverConfig.on('joined', (type) => {
|
|
608
|
+
if (type === 'primary') {
|
|
609
|
+
log.info(`${origin}: MONGO PRIMARY CONNECTION BACK...`);
|
|
610
|
+
} else if (type === 'secondary') {
|
|
611
|
+
log.info(`${origin}: MONGO SECONDARY CONNECTION BACK...`);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
log.info(`${origin}: mongo running @${this.dburl}/${this.database}`);
|
|
616
|
+
this.clientDB = mongoClient.db(this.database);
|
|
617
|
+
this.adapterMongoClient = mongoClient;
|
|
618
|
+
|
|
619
|
+
// we don't have authentication defined but we still need to check if Mongo does not
|
|
620
|
+
// require one, so we just list collections to test if it's doable.
|
|
621
|
+
return this.clientDB.listCollections().toArray((error) => {
|
|
622
|
+
if (error) {
|
|
623
|
+
log.error(`${origin}: ${error}`);
|
|
624
|
+
this.alive = false;
|
|
625
|
+
} else {
|
|
626
|
+
log.info(`${origin}: MongoDB connection has been established`);
|
|
627
|
+
this.alive = true;
|
|
628
|
+
this.adapterMongoClient = mongoClient;
|
|
629
|
+
}
|
|
630
|
+
return callback(this.alive, mongoClient);
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Call to disconnect from the adapter database
|
|
637
|
+
*
|
|
638
|
+
* @function disconnect
|
|
639
|
+
*/
|
|
640
|
+
disconnect() {
|
|
641
|
+
if (this.adapterMongoClient) {
|
|
642
|
+
this.adapterMongoClient.close();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* createCollection creates the provided collection in the file system or database.
|
|
648
|
+
*
|
|
649
|
+
* @function createCollection
|
|
650
|
+
* @param {string} collectionName - the name of the collection to create
|
|
651
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
652
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
653
|
+
*/
|
|
654
|
+
createCollection(collectionName, dbInfo, fsWrite, callback) {
|
|
655
|
+
const origin = `${this.myid}-dbUtil-createCollection`;
|
|
656
|
+
log.trace(origin);
|
|
657
|
+
|
|
658
|
+
try {
|
|
659
|
+
// verify the required data has been provided
|
|
660
|
+
if (!collectionName || (typeof collectionName !== 'string')) {
|
|
661
|
+
log.warn(`${origin}: Missing Collection Name or not string`);
|
|
662
|
+
return callback(`${origin}: Missing Collection Name or not string`, null);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
666
|
+
if (storageError) {
|
|
667
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// if using file storage
|
|
671
|
+
if (storage === Storage.FILESYSTEM) {
|
|
672
|
+
// if there is no adapter directory - can not do anything so error
|
|
673
|
+
if (!fs.existsSync(`${this.baseDir}`)) {
|
|
674
|
+
log.warn(`${origin}: Not able to create storage - missing base directory!`);
|
|
675
|
+
return callback(`${origin}: Not able to create storage - missing base directory!`, null);
|
|
676
|
+
}
|
|
677
|
+
// if there is no storage directory - create it
|
|
678
|
+
if (!fs.existsSync(`${this.baseDir}/storage`)) {
|
|
679
|
+
fs.mkdirSync(`${this.baseDir}/storage`);
|
|
680
|
+
}
|
|
681
|
+
// if the collection already exists - no need to create it
|
|
682
|
+
if (fs.existsSync(`${this.baseDir}/storage/${collectionName}`)) {
|
|
683
|
+
log.debug(`${origin}: storage file collection already exists`);
|
|
684
|
+
return callback(null, collectionName);
|
|
685
|
+
}
|
|
686
|
+
// create the new collection on the file system
|
|
687
|
+
fs.mkdirSync(`${this.baseDir}/storage/${collectionName}`);
|
|
688
|
+
log.debug(`${origin}: storage file collection ${collectionName} created`);
|
|
689
|
+
return callback(null, collectionName);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// if using MongoDB storage
|
|
693
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
694
|
+
return mongoClient.db(database).createCollection(collectionName, (err, res) => {
|
|
695
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
696
|
+
if (err) {
|
|
697
|
+
// error we get back if the collection already existed - not a true error
|
|
698
|
+
if (err.codeName === 'NamespaceExists') {
|
|
699
|
+
log.debug(`${origin}: database collection already exists`);
|
|
700
|
+
return callback(null, collectionName);
|
|
701
|
+
}
|
|
702
|
+
log.warn(`${origin}: Error creating collection ${err}`);
|
|
703
|
+
return callback(`${origin}: Error creating collection ${err}`, null);
|
|
704
|
+
}
|
|
705
|
+
log.spam(`${origin}: db response ${res}`);
|
|
706
|
+
log.debug(`${origin}: database collection ${collectionName} created`);
|
|
707
|
+
return callback(null, collectionName);
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
} catch (ex) {
|
|
712
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
713
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* removeCollection removes the provided collection from the file system or database.
|
|
719
|
+
*
|
|
720
|
+
* @function removeCollection
|
|
721
|
+
* @param {string} collectionName - the name of the collection to remove
|
|
722
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
723
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
724
|
+
*/
|
|
725
|
+
removeCollection(collectionName, dbInfo, fsWrite, callback) {
|
|
726
|
+
const origin = `${this.myid}-dbUtil-removeCollection`;
|
|
727
|
+
log.trace(origin);
|
|
728
|
+
|
|
729
|
+
try {
|
|
730
|
+
// verify the required data has been provided
|
|
731
|
+
if (!collectionName || (typeof collectionName !== 'string')) {
|
|
732
|
+
log.warn(`${origin}: Missing Collection Name or not string`);
|
|
733
|
+
return callback(`${origin}: Missing Collection Name or not string`, null);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
737
|
+
if (storageError) {
|
|
738
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// if using file storage
|
|
742
|
+
if (storage === Storage.FILESYSTEM) {
|
|
743
|
+
const deld = deleteJSON(collectionName);
|
|
744
|
+
if (deld) {
|
|
745
|
+
log.debug(`${origin}: storage file collection ${collectionName} removed`);
|
|
746
|
+
return callback(null, deld);
|
|
747
|
+
}
|
|
748
|
+
log.debug(`${origin}: could not remove storage file collection`);
|
|
749
|
+
return callback(`${origin}: could not remove storage file collection`, null);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// if using MongoDB storage
|
|
753
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
754
|
+
// get the list of collections from the database - can we just drop?
|
|
755
|
+
return mongoClient.db(database).listCollections().toArray((err, result) => {
|
|
756
|
+
if (err) {
|
|
757
|
+
log.warn(`${origin}: Failed to get collections ${err}`);
|
|
758
|
+
return callback(`${origin}: Failed to get collections ${err}`, null);
|
|
759
|
+
}
|
|
760
|
+
// go through the collections to get the correct one for removal
|
|
761
|
+
for (let e = 0; e < result.length; e += 1) {
|
|
762
|
+
if (result[e].name === collectionName) {
|
|
763
|
+
// now that we found it, remove it
|
|
764
|
+
return mongoClient.db(database).collection(collectionName).drop({}, (err1, res) => {
|
|
765
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
766
|
+
if (err1) {
|
|
767
|
+
log.warn(`${origin}: Failed to remove collection ${err1}`);
|
|
768
|
+
return callback(`${origin}: Failed to remove collection ${err1}`, null);
|
|
769
|
+
}
|
|
770
|
+
log.spam(`${origin}: db response ${res}`);
|
|
771
|
+
log.debug(`${origin}: database collection ${collectionName} removed`);
|
|
772
|
+
return callback(null, collectionName);
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return callback(null, collectionName);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
} catch (ex) {
|
|
781
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
782
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Call to create an item in the database
|
|
788
|
+
*
|
|
789
|
+
* @function create
|
|
790
|
+
* @param {string} collectionName - the collection to save the item in. (required)
|
|
791
|
+
* @param {string} data - the modification to make. (required)
|
|
792
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
793
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
794
|
+
*/
|
|
795
|
+
create(collectionName, data, dbInfo, fsWrite, callback) {
|
|
796
|
+
const origin = `${this.myid}-dbUtil-create`;
|
|
797
|
+
log.trace(origin);
|
|
798
|
+
|
|
799
|
+
try {
|
|
800
|
+
// verify the required data has been provided
|
|
801
|
+
if (!collectionName) {
|
|
802
|
+
log.warn(`${origin}: Missing Collection Name`);
|
|
803
|
+
return callback(`${origin}: Missing Collection Name`, null);
|
|
804
|
+
}
|
|
805
|
+
if (!data) {
|
|
806
|
+
log.warn(`${origin}: Missing data to add`);
|
|
807
|
+
return callback(`${origin}: Missing data to add`, null);
|
|
808
|
+
}
|
|
809
|
+
if ((data.entity && !data.action) || (!data.entity && data.action)) {
|
|
810
|
+
log.warn(`${origin}: Inconsistent entity/action set`);
|
|
811
|
+
return callback(`${origin}: Inconsistent entity/action set`, null);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// create the unique identifier (should we let mongo do this?)
|
|
815
|
+
const dataInfo = data;
|
|
816
|
+
if (!{}.hasOwnProperty.call(dataInfo, '_id')) {
|
|
817
|
+
dataInfo._id = uuid.v4();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
821
|
+
if (storageError) {
|
|
822
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// if using file storage
|
|
826
|
+
if (storage === Storage.FILESYSTEM) {
|
|
827
|
+
// save it to file in the adapter storage directory
|
|
828
|
+
const saved = saveAsJson(collectionName, data);
|
|
829
|
+
if (!saved) {
|
|
830
|
+
log.warn(`${origin}: Data has not been saved to file storage`);
|
|
831
|
+
return callback(`${origin}: Data has not been saved to file storage`, null);
|
|
832
|
+
}
|
|
833
|
+
log.debug(`${origin}: Data saved in file storage`);
|
|
834
|
+
return callback(null, saved);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// if using MongoDB storage
|
|
838
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
839
|
+
// Add the data to the database
|
|
840
|
+
// insertOne has only 2 parameters: the data to be added & callback. Not an identifier.
|
|
841
|
+
return mongoClient.db(database).collection(collectionName).insertOne(dataInfo, (err, result) => {
|
|
842
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
843
|
+
if (err) {
|
|
844
|
+
log.warn(`${origin}: Failed to insert data in collection ${err}`);
|
|
845
|
+
return callback(`${origin}: Failed to insert data in collection ${err}`, null);
|
|
846
|
+
}
|
|
847
|
+
log.spam(`${origin}: db response ${result}`);
|
|
848
|
+
log.debug(`${origin}: Data saved in database`);
|
|
849
|
+
return callback(null, data);
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
} catch (ex) {
|
|
854
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
855
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Call to create an index in the database
|
|
861
|
+
*
|
|
862
|
+
* @function createIndex
|
|
863
|
+
* @param {string} collectionName - the collection to index. (required)
|
|
864
|
+
* @param {string} fieldOrSpec - what to index. (required)
|
|
865
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
866
|
+
*/
|
|
867
|
+
createIndex(collectionName, fieldOrSpec, options, dbInfo, callback) {
|
|
868
|
+
const origin = `${this.myid}-dbUtil-createIndex`;
|
|
869
|
+
log.trace(origin);
|
|
870
|
+
|
|
871
|
+
try {
|
|
872
|
+
// verify the required data has been provided
|
|
873
|
+
if (!collectionName) {
|
|
874
|
+
log.warn(`${origin}: Missing Collection Name`);
|
|
875
|
+
return callback(`${origin}: Missing Collection Name`, null);
|
|
876
|
+
}
|
|
877
|
+
if (!fieldOrSpec) {
|
|
878
|
+
log.warn(`${origin}: Missing Specs`);
|
|
879
|
+
return callback(`${origin}: Missing Specs`, null);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
883
|
+
if (storageError) {
|
|
884
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// if using file storage
|
|
888
|
+
if (storage === Storage.FILESYSTEM) {
|
|
889
|
+
// no database - no index
|
|
890
|
+
log.warn(`${origin}: No database - no index`);
|
|
891
|
+
return callback(`${origin}: No database - no index`, null);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// if using MongoDB storage
|
|
895
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
896
|
+
// create the index on the collection
|
|
897
|
+
return mongoClient.db(database).collection(collectionName).createIndex(fieldOrSpec, options || {}, (err, res) => {
|
|
898
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
899
|
+
if (err) {
|
|
900
|
+
log.warn(`${origin}: Failed to index data in collection ${err}`);
|
|
901
|
+
return callback(`${origin}: Failed to index data in collection ${err}`, null);
|
|
902
|
+
}
|
|
903
|
+
log.debug(`${origin}: Data in collection indexed`);
|
|
904
|
+
return callback(null, res);
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
} catch (ex) {
|
|
909
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
910
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Call to count the documents in a collection
|
|
916
|
+
*
|
|
917
|
+
* @function countDocuments
|
|
918
|
+
* @param {string} collectionName - the collection to count documents in. (required)
|
|
919
|
+
* @param {object} query - the query to minimize documents you count. (required)
|
|
920
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
921
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
922
|
+
*/
|
|
923
|
+
countDocuments(collectionName, query, options, dbInfo, fsWrite, callback) {
|
|
924
|
+
const origin = `${this.myid}-dbUtil-countDocuments`;
|
|
925
|
+
log.trace(origin);
|
|
926
|
+
|
|
927
|
+
try {
|
|
928
|
+
// verify the required data has been provided
|
|
929
|
+
if (!collectionName) {
|
|
930
|
+
log.warn(`${origin}: Missing Collection Name`);
|
|
931
|
+
return callback(`${origin}: Missing Collection Name`, null);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
935
|
+
if (storageError) {
|
|
936
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// if using file storage
|
|
940
|
+
if (storage === Storage.FILESYSTEM) {
|
|
941
|
+
// get a count from the JSON
|
|
942
|
+
const data = countJSON(collectionName, query);
|
|
943
|
+
if (!data || data === -1) {
|
|
944
|
+
log.warn(`${origin}: Could not count data from file storage`);
|
|
945
|
+
return callback(`${origin}: Could not count data from file storage`, null);
|
|
946
|
+
}
|
|
947
|
+
log.debug(`${origin}: Count from file storage ${data}`);
|
|
948
|
+
return callback(null, data);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// if using MongoDB storage
|
|
952
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
953
|
+
// get the count from mongo
|
|
954
|
+
return mongoClient.db(database).collection(collectionName).count(query, options, (err, res) => {
|
|
955
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
956
|
+
if (err) {
|
|
957
|
+
log.warn(`${origin}: Failed to count collection ${err}`);
|
|
958
|
+
return callback(`${origin}: Failed to count collection ${err}`, null);
|
|
959
|
+
}
|
|
960
|
+
log.debug(`${origin}: Count from database ${res}`);
|
|
961
|
+
return callback(null, res);
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
} catch (ex) {
|
|
966
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
967
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Delete items from a collection
|
|
973
|
+
*
|
|
974
|
+
* @function delete
|
|
975
|
+
* @param {string} collectionName - the collection to remove document from. (required)
|
|
976
|
+
* @param {object} filter - the filter for the document(s) to remove. (required)
|
|
977
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
978
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
979
|
+
*/
|
|
980
|
+
delete(collectionName, filter, options, multiple, dbInfo, fsWrite, callback) {
|
|
981
|
+
const origin = `${this.myid}-dbUtil-delete`;
|
|
982
|
+
log.trace(origin);
|
|
983
|
+
|
|
984
|
+
try {
|
|
985
|
+
// verify the required data has been provided
|
|
986
|
+
if (!collectionName) {
|
|
987
|
+
log.warn(`${origin}: Missing Collection Name`);
|
|
988
|
+
return callback(`${origin}: Missing Collection Name`, null);
|
|
989
|
+
}
|
|
990
|
+
if (!filter) {
|
|
991
|
+
log.warn(`${origin}: Missing Filter`);
|
|
992
|
+
return callback(`${origin}: Missing Filter`, null);
|
|
993
|
+
}
|
|
994
|
+
if (multiple === undefined || multiple === null) {
|
|
995
|
+
log.warn(`${origin}: Missing Multiple flag`);
|
|
996
|
+
return callback(`${origin}: Missing Multiple flag`, null);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
1000
|
+
if (storageError) {
|
|
1001
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// if using file storage
|
|
1005
|
+
if (storage === Storage.FILESYSTEM) {
|
|
1006
|
+
// verify the collection exists
|
|
1007
|
+
if (!fs.existsSync(`${adapterDir}/storage/${collectionName}.json`)) {
|
|
1008
|
+
log.warn(`${origin}: Collection ${collectionName} does not exist`);
|
|
1009
|
+
return callback(null, `${origin}: Collection ${collectionName} does not exist`);
|
|
1010
|
+
}
|
|
1011
|
+
// remove the item from the collection
|
|
1012
|
+
const deld = removeFromJSON(collectionName, filter, multiple);
|
|
1013
|
+
if (!deld) {
|
|
1014
|
+
log.warn(`${origin}: Data has not been deleted from file storage`);
|
|
1015
|
+
return callback(`${origin}: Data has not been deleted from file storage`, null);
|
|
1016
|
+
}
|
|
1017
|
+
log.debug(`${origin}: Data has been deleted from file storage`);
|
|
1018
|
+
return callback(null, deld);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// if using MongoDB storage
|
|
1022
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
1023
|
+
if (!multiple) {
|
|
1024
|
+
// delete the single item from mongo
|
|
1025
|
+
return mongoClient.db(database).collection(collectionName).deleteOne(filter, options, (err, res) => {
|
|
1026
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
1027
|
+
if (err) {
|
|
1028
|
+
log.warn(`${origin}: Failed to delete data from database ${err}`);
|
|
1029
|
+
return callback(`${origin}: Failed delete data from database ${err}`, null);
|
|
1030
|
+
}
|
|
1031
|
+
log.debug(`${origin}: Data has been deleted from database`);
|
|
1032
|
+
return callback(null, res);
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// delete the multiple items from mongo
|
|
1037
|
+
return mongoClient.db(database).collection(collectionName).deleteMany(filter, options, (err, res) => {
|
|
1038
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
1039
|
+
if (err) {
|
|
1040
|
+
log.warn(`${origin}: Failed to delete data from database ${err}`);
|
|
1041
|
+
return callback(`${origin}: Failed delete data from database ${err}`, null);
|
|
1042
|
+
}
|
|
1043
|
+
log.debug(`${origin}: Data has been deleted from database`);
|
|
1044
|
+
return callback(null, res);
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
} catch (ex) {
|
|
1049
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
1050
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Replace an item in a collection
|
|
1056
|
+
*
|
|
1057
|
+
* @function replaceOne
|
|
1058
|
+
* @param {string} collectionName - the collection to replace document in. (required)
|
|
1059
|
+
* @param {object} filter - the filter for the document(s) to replace. (required)
|
|
1060
|
+
* @param {object} doc - the filter for the document(s) to replace. (required)
|
|
1061
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
1062
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
1063
|
+
*/
|
|
1064
|
+
replaceOne(collectionName, filter, doc, options, dbInfo, fsWrite, callback) {
|
|
1065
|
+
const origin = `${this.myid}-dbUtil-replaceOne`;
|
|
1066
|
+
log.trace(origin);
|
|
1067
|
+
|
|
1068
|
+
try {
|
|
1069
|
+
// verify the required data has been provided
|
|
1070
|
+
if (!collectionName) {
|
|
1071
|
+
log.warn(`${origin}: Missing Collection Name`);
|
|
1072
|
+
return callback(`${origin}: Missing Collection Name`, null);
|
|
1073
|
+
}
|
|
1074
|
+
if (!filter) {
|
|
1075
|
+
log.warn(`${origin}: Missing Filter`);
|
|
1076
|
+
return callback(`${origin}: Missing Filter`, null);
|
|
1077
|
+
}
|
|
1078
|
+
if (!doc) {
|
|
1079
|
+
log.warn(`${origin}: Missing Document`);
|
|
1080
|
+
return callback(`${origin}: Missing Document`, null);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
1084
|
+
if (storageError) {
|
|
1085
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// if using file storage
|
|
1089
|
+
if (storage === Storage.FILESYSTEM) {
|
|
1090
|
+
// remove the data from the collection
|
|
1091
|
+
const rem = removeFromJSON(collectionName, filter, false);
|
|
1092
|
+
if (rem) {
|
|
1093
|
+
// add the data into the collection
|
|
1094
|
+
const sav = saveAsJson(collectionName, doc);
|
|
1095
|
+
if (sav) {
|
|
1096
|
+
log.debug(`${origin}: Data replaced in file storage`);
|
|
1097
|
+
return callback(null, sav);
|
|
1098
|
+
}
|
|
1099
|
+
log.warn(`${origin}: Could not save doc into file storage`);
|
|
1100
|
+
return callback(`${origin}: Could not save doc into file storage`, null);
|
|
1101
|
+
}
|
|
1102
|
+
log.warn(`${origin}: Could not delete from file storage`);
|
|
1103
|
+
return callback(`${origin}: Could not delete from file storage`, null);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// if using MongoDB storage
|
|
1107
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
1108
|
+
// replace an items in mongo
|
|
1109
|
+
return mongoClient.db(database).collection(collectionName).replaceOne(filter, doc, options, (err, res) => {
|
|
1110
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
1111
|
+
if (err) {
|
|
1112
|
+
log.warn(`${origin}: Failed to replace data in database ${err}`);
|
|
1113
|
+
return callback(`${origin}: Failed replace data in database ${err}`, null);
|
|
1114
|
+
}
|
|
1115
|
+
log.debug(`${origin}: Data replaced in file storage`);
|
|
1116
|
+
return callback(null, res);
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
} catch (ex) {
|
|
1121
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
1122
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Call to find items in the database
|
|
1128
|
+
*
|
|
1129
|
+
* @function find
|
|
1130
|
+
* @param {string} collectionName - the collection name to search in. (required)
|
|
1131
|
+
* @param {object} options - the options to use to find data.
|
|
1132
|
+
* options = {
|
|
1133
|
+
* filter : <filter Obj>,
|
|
1134
|
+
* sort : <sort Obj>,
|
|
1135
|
+
* start : <start position>,
|
|
1136
|
+
* limit : <limit of results>,
|
|
1137
|
+
* }
|
|
1138
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
1139
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
1140
|
+
*/
|
|
1141
|
+
find(collectionName, options, dbInfo, fsWrite, callback) {
|
|
1142
|
+
const origin = `${this.myid}-dbUtil-find`;
|
|
1143
|
+
log.trace(origin);
|
|
1144
|
+
|
|
1145
|
+
try {
|
|
1146
|
+
// verify the required data has been provided
|
|
1147
|
+
if (!collectionName) {
|
|
1148
|
+
log.warn(`${origin}: Missing Collection Name`);
|
|
1149
|
+
return callback(`${origin}: Missing Collection Name`, null);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// get the collection so we can run the remove on the collection
|
|
1153
|
+
let filter = {};
|
|
1154
|
+
let start = 0;
|
|
1155
|
+
let sort = {};
|
|
1156
|
+
let limit = 10;
|
|
1157
|
+
if (options) {
|
|
1158
|
+
filter = options.filter || {};
|
|
1159
|
+
start = options.start || 0;
|
|
1160
|
+
sort = options.sort || {};
|
|
1161
|
+
if (Object.hasOwnProperty.call(options, 'limit')) {
|
|
1162
|
+
({ limit } = options);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// If limit is not specified, default to 10.
|
|
1167
|
+
// Note: limit may be 0, which is equivalent to setting no limit.
|
|
1168
|
+
|
|
1169
|
+
// Replace filter with regex to allow for substring lookup
|
|
1170
|
+
// TODO: Need to create a new filter object instead of mutating the exsisting one
|
|
1171
|
+
const filterKeys = Object.keys(filter).filter((key) => (key[0] !== '$' && typeof filter[key] === 'string'));
|
|
1172
|
+
filterKeys.map((key) => {
|
|
1173
|
+
try {
|
|
1174
|
+
const escapedFilter = filter[key].replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
|
|
1175
|
+
const regexedFilter = new RegExp(`.*${escapedFilter}.*`, 'i');
|
|
1176
|
+
filter[key] = {
|
|
1177
|
+
$regex: regexedFilter
|
|
1178
|
+
};
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
delete filter[key];
|
|
1181
|
+
}
|
|
1182
|
+
return key;
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
1186
|
+
if (storageError) {
|
|
1187
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// if using file storage
|
|
1191
|
+
if (storage === Storage.FILESYSTEM) {
|
|
1192
|
+
// Find it from file in the adapter
|
|
1193
|
+
let toReturn = getFromJson(collectionName, filter);
|
|
1194
|
+
if (toReturn && toReturn.length > limit) {
|
|
1195
|
+
let curEnd = start + limit;
|
|
1196
|
+
if (curEnd < toReturn.length) {
|
|
1197
|
+
curEnd = toReturn.length;
|
|
1198
|
+
}
|
|
1199
|
+
toReturn = toReturn.slice(start, curEnd);
|
|
1200
|
+
}
|
|
1201
|
+
log.trace(`${origin}: Data retrieved from file storage`);
|
|
1202
|
+
return callback(null, toReturn);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// if using MongoDB storage
|
|
1206
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
1207
|
+
// Find the data in the database
|
|
1208
|
+
return mongoClient.db(database).collection(collectionName).find(filter).sort(sort)
|
|
1209
|
+
.skip(start)
|
|
1210
|
+
.limit(limit)
|
|
1211
|
+
.toArray()
|
|
1212
|
+
.then((value) => {
|
|
1213
|
+
log.debug(`${origin}: Data retrieved from database`);
|
|
1214
|
+
return callback(null, value);
|
|
1215
|
+
})
|
|
1216
|
+
.finally(() => {
|
|
1217
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
} catch (ex) {
|
|
1222
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
1223
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Call to find an item in a collection and modify it.
|
|
1229
|
+
*
|
|
1230
|
+
* @function findAndModify
|
|
1231
|
+
* @param {string} collectionName - the collection to find things from. (required)
|
|
1232
|
+
* @param {object} filter - the filter used to find objects. (optional)
|
|
1233
|
+
* @param {array} sort - how to sort the items (first one in order will be modified). (optional)
|
|
1234
|
+
* @param {object} data - the modification to make. (required)
|
|
1235
|
+
* @param {boolean} upsert - option for the whether to insert new objects. (optional)
|
|
1236
|
+
* @param {object} dbInfo - the url for the database to connect to (dburl, dboptions, database) (optional)
|
|
1237
|
+
* @param {boolean} fsWrite - turn on write to the file system (optional)
|
|
1238
|
+
* @param {updateCallback} callback - a callback function to return a result
|
|
1239
|
+
* (the new object) or the error
|
|
1240
|
+
*/
|
|
1241
|
+
findAndModify(collectionName, filter, sort, data, upsert, dbInfo, fsWrite, callback) {
|
|
1242
|
+
const origin = `${this.myid}-dbUtil-findAndModify`;
|
|
1243
|
+
log.trace(origin);
|
|
1244
|
+
|
|
1245
|
+
try {
|
|
1246
|
+
// verify the required data has been provided
|
|
1247
|
+
if (collectionName === undefined || collectionName === null || collectionName === '') {
|
|
1248
|
+
log.warn(`${origin}: Missing Collection Name`);
|
|
1249
|
+
return callback(`${origin}: Missing Collection Name`, null);
|
|
1250
|
+
}
|
|
1251
|
+
if (data === undefined || data === null || typeof data !== 'object' || Object.keys(data).length === 0) {
|
|
1252
|
+
log.warn(`${origin}: Missing data for modification`);
|
|
1253
|
+
return callback(`${origin}: Missing data for modification`, null);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
const options = {
|
|
1257
|
+
sort,
|
|
1258
|
+
upsert,
|
|
1259
|
+
returnOriginal: false
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
return this.determineStorage(dbInfo, (storageError, storage, mongoClient, database) => {
|
|
1263
|
+
if (storageError) {
|
|
1264
|
+
return callback(`${origin}: ${storageError}`, null);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// if using file storage
|
|
1268
|
+
if (storage === Storage.FILESYSTEM) {
|
|
1269
|
+
// save it to file in the adapter storage directory
|
|
1270
|
+
const saved = saveAsJson(collectionName, data);
|
|
1271
|
+
if (!saved) {
|
|
1272
|
+
log.error(`${origin}: Data has not been saved`);
|
|
1273
|
+
return callback(`${origin}: Data has not been saved`, null);
|
|
1274
|
+
}
|
|
1275
|
+
log.debug(`${origin}: Data modified in file storage`);
|
|
1276
|
+
return callback(null, saved);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// if using MongoDB storage
|
|
1280
|
+
if (storage === Storage.DBINFO || storage === Storage.ADAPTERDB) {
|
|
1281
|
+
// find and modify the data in the database
|
|
1282
|
+
return mongoClient.db(database).collection(collectionName).findOneAndUpdate((filter || {}), data, options, (err, result) => {
|
|
1283
|
+
if (storage === Storage.DBINFO && mongoClient) mongoClient.close();
|
|
1284
|
+
if (err) {
|
|
1285
|
+
log.warn(`${origin}: Failed to modified data in database ${err}`);
|
|
1286
|
+
return callback(`${origin}: Failed modified data in database ${err}`, null);
|
|
1287
|
+
}
|
|
1288
|
+
log.debug(`${origin}: Data modified in database`);
|
|
1289
|
+
return callback(null, result);
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
} catch (ex) {
|
|
1294
|
+
log.warn(`${origin}: Caught Exception - ${ex}`);
|
|
1295
|
+
return callback(`${origin}: Caught Exception - ${ex}`, null);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
module.exports = DBUtil;
|