@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
|
@@ -0,0 +1,1175 @@
|
|
|
1
|
+
/* @copyright Itential, LLC 2018 */
|
|
2
|
+
|
|
3
|
+
// Set globals
|
|
4
|
+
/* global log g_redis */
|
|
5
|
+
/* eslint consistent-return: warn */
|
|
6
|
+
/* eslint global-require: warn */
|
|
7
|
+
/* eslint import/no-dynamic-require: warn */
|
|
8
|
+
|
|
9
|
+
/* NodeJS internal utilities */
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const AsyncLockCl = require('async-lock');
|
|
13
|
+
|
|
14
|
+
// The schema validator
|
|
15
|
+
const AjvCl = require('ajv');
|
|
16
|
+
|
|
17
|
+
/* Fetch in the other needed components for the this Class */
|
|
18
|
+
const RestHandlerCl = require(path.join(__dirname, '/restHandler.js'));
|
|
19
|
+
const PropUtilCl = require(path.join(__dirname, '/propertyUtil.js'));
|
|
20
|
+
const ConnectorCl = require(path.join(__dirname, '/connectorRest.js'));
|
|
21
|
+
const TransUtilCl = require(path.join(__dirname, '/translatorUtil.js'));
|
|
22
|
+
const DBUtilCl = require(path.join(__dirname, '/dbUtil.js'));
|
|
23
|
+
|
|
24
|
+
let id = null;
|
|
25
|
+
const allowFailover = 'AD.300';
|
|
26
|
+
const noFailover = 'AD.500';
|
|
27
|
+
let dbUtilInst = null;
|
|
28
|
+
let propUtilInst = null;
|
|
29
|
+
let transUtilInst = null;
|
|
30
|
+
const NS_PER_SEC = 1e9;
|
|
31
|
+
let username = null;
|
|
32
|
+
|
|
33
|
+
// used for local cache or a temp if using redis
|
|
34
|
+
let cache = {};
|
|
35
|
+
const cachelock = 0;
|
|
36
|
+
let clock = null;
|
|
37
|
+
|
|
38
|
+
// INTERNAL FUNCTIONS
|
|
39
|
+
/**
|
|
40
|
+
* @summary Validate the properties have been provided for the libraries
|
|
41
|
+
*
|
|
42
|
+
* @function validateProperties
|
|
43
|
+
* @param {String} entityName - the name of the entity (required)
|
|
44
|
+
* @param {String} actionName - the name of the action to take (required)
|
|
45
|
+
*
|
|
46
|
+
* @return {Object} entitySchema - the entity schema object
|
|
47
|
+
*/
|
|
48
|
+
function validateProperties(properties) {
|
|
49
|
+
const origin = `${id}-requestHandler-validateProperties`;
|
|
50
|
+
log.trace(origin);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// get the path for the specific action file
|
|
54
|
+
const propertyFile = path.join(__dirname, '/../propertiesSchema.json');
|
|
55
|
+
|
|
56
|
+
// Read the action from the file system
|
|
57
|
+
const propertySchema = JSON.parse(fs.readFileSync(propertyFile, 'utf-8'));
|
|
58
|
+
|
|
59
|
+
// add any defaults to the data
|
|
60
|
+
const combinedProps = propUtilInst.mergeProperties(properties, propUtilInst.setDefaults(propertySchema));
|
|
61
|
+
|
|
62
|
+
// validate the entity against the schema
|
|
63
|
+
const ajvInst = new AjvCl();
|
|
64
|
+
const validate = ajvInst.compile(propertySchema);
|
|
65
|
+
const result = validate(combinedProps);
|
|
66
|
+
|
|
67
|
+
// if invalid properties throw an error
|
|
68
|
+
if (!result) {
|
|
69
|
+
// create the generic part of an error object
|
|
70
|
+
const errorObj = {
|
|
71
|
+
origin,
|
|
72
|
+
type: 'Schema Validation Failure',
|
|
73
|
+
vars: [validate.errors[0].message]
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// log and throw the error
|
|
77
|
+
log.trace(`${origin}: Schema validation failure ${validate.errors[0].message}`);
|
|
78
|
+
throw new Error(JSON.stringify(errorObj));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// need to decode/decrypt the static token if it is encoded/encrypted
|
|
82
|
+
if (combinedProps.authentication && combinedProps.authentication.token
|
|
83
|
+
&& (combinedProps.authentication.token.indexOf('{code}') === 0
|
|
84
|
+
|| combinedProps.authentication.token.indexOf('{crypt}') === 0)) {
|
|
85
|
+
combinedProps.authentication.token = propUtilInst.decryptProperty(combinedProps.authentication.token);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// need to decode/decrypt the password if it is encoded/encrypted
|
|
89
|
+
if (combinedProps.authentication && combinedProps.authentication.password
|
|
90
|
+
&& (combinedProps.authentication.password.indexOf('{code}') === 0
|
|
91
|
+
|| combinedProps.authentication.password.indexOf('{crypt}') === 0)) {
|
|
92
|
+
combinedProps.authentication.password = propUtilInst.decryptProperty(combinedProps.authentication.password);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// return the resulting properties --- add any necessary defaults
|
|
96
|
+
return combinedProps;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return transUtilInst.checkAndThrow(e, origin, 'Issue validating properties');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @summary Walk through the entities and make sure they have an action file
|
|
104
|
+
* and that file format is validated against actionSchema.json
|
|
105
|
+
*
|
|
106
|
+
* @function walkThroughActionFiles
|
|
107
|
+
* @param {String} directory - the directory for the adapter (required)
|
|
108
|
+
*/
|
|
109
|
+
function walkThroughActionFiles(directory) {
|
|
110
|
+
const origin = `${id}-requestHandler-walkThroughActionFiles`;
|
|
111
|
+
log.trace(origin);
|
|
112
|
+
const clean = [];
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Read the action schema from the file system
|
|
116
|
+
const actionSchemaFile = path.join(__dirname, '/../actionSchema.json');
|
|
117
|
+
const actionSchema = JSON.parse(fs.readFileSync(actionSchemaFile, 'utf-8'));
|
|
118
|
+
const entitydir = `${directory}/entities`;
|
|
119
|
+
|
|
120
|
+
// if there is an entity directory
|
|
121
|
+
if (fs.statSync(directory).isDirectory() && fs.statSync(entitydir).isDirectory()) {
|
|
122
|
+
const entities = fs.readdirSync(entitydir);
|
|
123
|
+
|
|
124
|
+
// need to go through each entity in the entities directory
|
|
125
|
+
for (let e = 0; e < entities.length; e += 1) {
|
|
126
|
+
// make sure the entity is a directory - do not care about extra files
|
|
127
|
+
// only entities (dir)
|
|
128
|
+
if (fs.statSync(`${entitydir}/${entities[e]}`).isDirectory()) {
|
|
129
|
+
// see if the action file exists in the entity
|
|
130
|
+
if (fs.existsSync(`${entitydir}/${entities[e]}/action.json`)) {
|
|
131
|
+
// Read the entity actions from the file system
|
|
132
|
+
const actions = JSON.parse(fs.readFileSync(`${entitydir}/${entities[e]}/action.json`, 'utf-8'));
|
|
133
|
+
|
|
134
|
+
// add any defaults to the data
|
|
135
|
+
const defActions = propUtilInst.setDefaults(actionSchema);
|
|
136
|
+
const allActions = propUtilInst.mergeProperties(actions, defActions);
|
|
137
|
+
|
|
138
|
+
// validate the entity against the schema
|
|
139
|
+
const ajvInst = new AjvCl();
|
|
140
|
+
const validate = ajvInst.compile(actionSchema);
|
|
141
|
+
const result = validate(allActions);
|
|
142
|
+
|
|
143
|
+
// if invalid properties throw an error
|
|
144
|
+
if (!result) {
|
|
145
|
+
// Get the action and component from the first ajv error
|
|
146
|
+
let action = 'checkErrorDetails';
|
|
147
|
+
let component = 'checkErrorDetails';
|
|
148
|
+
const temp = validate.errors[0].dataPath;
|
|
149
|
+
const actStInd = temp.indexOf('[');
|
|
150
|
+
const actEndInd = temp.indexOf(']');
|
|
151
|
+
// if we have indexes for the action we can get specifics
|
|
152
|
+
if (actStInd >= 0 && actEndInd > actStInd) {
|
|
153
|
+
const actNum = temp.substring(actStInd + 1, actEndInd);
|
|
154
|
+
// get the action name from the number
|
|
155
|
+
if (actions.actions.length >= actNum) {
|
|
156
|
+
action = actions.actions[actNum].name;
|
|
157
|
+
}
|
|
158
|
+
// get the component that failed for the action
|
|
159
|
+
if (temp.length > actEndInd + 1) {
|
|
160
|
+
component = temp.substring(actEndInd + 2);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
164
|
+
msg += `${entities[e]} - ${action} - ${component} Details: ${JSON.stringify(validate.errors)}`;
|
|
165
|
+
clean.push(msg);
|
|
166
|
+
log.warn(msg);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (let a = 0; a < actions.actions.length; a += 1) {
|
|
170
|
+
const act = actions.actions[a];
|
|
171
|
+
let reqSchema = null;
|
|
172
|
+
let respSchema = null;
|
|
173
|
+
|
|
174
|
+
// Check that the request schema file defined for the action exist
|
|
175
|
+
if (act.requestSchema) {
|
|
176
|
+
if (!fs.existsSync(`${entitydir}/${entities[e]}/${act.requestSchema}`)) {
|
|
177
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
178
|
+
msg += `${entities[e]}: ${act.name} - missing request schema file - ${act.requestSchema}`;
|
|
179
|
+
clean.push(msg);
|
|
180
|
+
log.warn(msg);
|
|
181
|
+
} else {
|
|
182
|
+
reqSchema = JSON.parse(fs.readFileSync(`${entitydir}/${entities[e]}/${act.requestSchema}`, 'utf-8'));
|
|
183
|
+
}
|
|
184
|
+
} else if (act.schema && fs.existsSync(`${entitydir}/${entities[e]}/${act.schema}`)) {
|
|
185
|
+
reqSchema = JSON.parse(fs.readFileSync(`${entitydir}/${entities[e]}/${act.schema}`, 'utf-8'));
|
|
186
|
+
} else {
|
|
187
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
188
|
+
msg += `${entities[e]}: ${act.name} - missing request schema file - ${act.schema}`;
|
|
189
|
+
clean.push(msg);
|
|
190
|
+
log.warn(msg);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check that the response schema file defined for the action exist
|
|
194
|
+
if (act.responseSchema) {
|
|
195
|
+
if (!fs.existsSync(`${entitydir}/${entities[e]}/${act.responseSchema}`)) {
|
|
196
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
197
|
+
msg += `${entities[e]}: ${act.name} - missing response schema file - ${act.responseSchema}`;
|
|
198
|
+
clean.push(msg);
|
|
199
|
+
log.warn(msg);
|
|
200
|
+
} else {
|
|
201
|
+
respSchema = JSON.parse(fs.readFileSync(`${entitydir}/${entities[e]}/${act.responseSchema}`, 'utf-8'));
|
|
202
|
+
}
|
|
203
|
+
} else if (act.schema && fs.existsSync(`${entitydir}/${entities[e]}/${act.schema}`)) {
|
|
204
|
+
respSchema = JSON.parse(fs.readFileSync(`${entitydir}/${entities[e]}/${act.schema}`, 'utf-8'));
|
|
205
|
+
} else {
|
|
206
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
207
|
+
msg += `${entities[e]}: ${act.name} - missing response schema file - ${act.schema}`;
|
|
208
|
+
clean.push(msg);
|
|
209
|
+
log.warn(msg);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// check that the action is in the schemas
|
|
213
|
+
if (!reqSchema || !reqSchema.properties || !reqSchema.properties.ph_request_type
|
|
214
|
+
|| !reqSchema.properties.ph_request_type.enum || !reqSchema.properties.ph_request_type.enum.includes(act.name)) {
|
|
215
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
216
|
+
msg += `${entities[e]}: ${act.name} - missing from ph_request_type in request schema`;
|
|
217
|
+
clean.push(msg);
|
|
218
|
+
log.warn(msg);
|
|
219
|
+
}
|
|
220
|
+
if (!respSchema || !respSchema.properties || !respSchema.properties.ph_request_type
|
|
221
|
+
|| !respSchema.properties.ph_request_type.enum || !respSchema.properties.ph_request_type.enum.includes(act.name)) {
|
|
222
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
223
|
+
msg += `${entities[e]}: ${act.name} - missing from ph_request_type in response schema`;
|
|
224
|
+
clean.push(msg);
|
|
225
|
+
log.warn(msg);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// check that the mock data files exist
|
|
229
|
+
if (act.responseObjects) {
|
|
230
|
+
for (let m = 0; m < act.responseObjects.length; m += 1) {
|
|
231
|
+
if (act.responseObjects[m].mockFile) {
|
|
232
|
+
if (!fs.existsSync(`${entitydir}/${entities[e]}/${act.responseObjects[m].mockFile}`)) {
|
|
233
|
+
let msg = `${origin}: Error on validation of actions for entity `;
|
|
234
|
+
msg += `${entities[e]}: ${act} - missing mock data file - ${act.responseObjects[m].mockFile}`;
|
|
235
|
+
clean.push(msg);
|
|
236
|
+
log.warn(msg);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
log.warn(`${origin}: Entity ${entities[e]} missing action file.`);
|
|
244
|
+
clean.push(`${origin}: Entity ${entities[e]} missing action file.`);
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
log.warn(`${origin}: Entity ${entities[e]} missing entity directory.`);
|
|
248
|
+
clean.push(`${origin}: Entity ${entities[e]} missing entity directory.`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return clean;
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return transUtilInst.checkAndThrow(e, origin, 'Issue validating actions');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @summary Method to check if the entity is in the cache
|
|
261
|
+
*
|
|
262
|
+
* @function isEntityCached
|
|
263
|
+
* @param {String} entityType - the entity type to check for
|
|
264
|
+
* @param {String/Array} entityId - the specific entity we are looking for
|
|
265
|
+
*
|
|
266
|
+
* @return {Array of Enumeration} - whether the entity was
|
|
267
|
+
* 'found' - entity was found
|
|
268
|
+
* 'notfound' - entity was not found
|
|
269
|
+
* 'needupdate' - update cache and try again
|
|
270
|
+
*/
|
|
271
|
+
function isEntityCached(entityType, entityId) {
|
|
272
|
+
const origin = `${id}-requestHandler-isEntityCached`;
|
|
273
|
+
log.trace(origin);
|
|
274
|
+
let entityIds = entityId;
|
|
275
|
+
const results = [];
|
|
276
|
+
|
|
277
|
+
// go through the cache
|
|
278
|
+
if (cache[entityType]) {
|
|
279
|
+
const now = new Date().getTime();
|
|
280
|
+
|
|
281
|
+
// see if the cache is valid
|
|
282
|
+
if ((cache[entityType].updated) && (cache[entityType].updated >= now - 300000)) {
|
|
283
|
+
// entityId is not an Array, make it one
|
|
284
|
+
if (!Array.isArray(entityIds)) {
|
|
285
|
+
entityIds = [entityId];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (let e = 0; e < entityIds.length; e += 1) {
|
|
289
|
+
// see if the device is in the cache
|
|
290
|
+
if (cache[entityType].list.includes(entityIds[e])) {
|
|
291
|
+
log.trace(`${origin}: Entity ${entityIds[e]} found in cache`);
|
|
292
|
+
results.push('found');
|
|
293
|
+
} else {
|
|
294
|
+
log.trace(`${origin}: Entity ${entityIds[e]} not found in cache`);
|
|
295
|
+
results.push('notfound');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return results;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
log.warn(`${origin}: Entity Cache out of date`);
|
|
303
|
+
return ['needupdate'];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Entity not found in cache
|
|
307
|
+
log.warn(`${origin}: Entity not in cache`);
|
|
308
|
+
return ['needupdate'];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @summary Method for metric db calls and tests
|
|
313
|
+
*
|
|
314
|
+
* @function dbCalls
|
|
315
|
+
* @param {String} entity - the entity to use. (required)
|
|
316
|
+
* @param {String} action - the action to use. (required)
|
|
317
|
+
* @param {Object} data - anything the user provides goes here. Possible tags:
|
|
318
|
+
* data = {
|
|
319
|
+
* code : <Number or String>,
|
|
320
|
+
* numRetries : <Number>,
|
|
321
|
+
* numRedirects : <Number>,
|
|
322
|
+
* isThrottling : <Boolean>,
|
|
323
|
+
* timeouts : <Number>,
|
|
324
|
+
* queueTime : <Number>,
|
|
325
|
+
* capabilityTime : <Number>,
|
|
326
|
+
* tripTime : <Number>,
|
|
327
|
+
* overallEnd : <Number>
|
|
328
|
+
* }
|
|
329
|
+
*
|
|
330
|
+
*/
|
|
331
|
+
function dbCalls(collectionName, entity, action, data, callback) {
|
|
332
|
+
try {
|
|
333
|
+
// template_entity.createEntity logs a 201 response code which we don't track so code count doesn't show up in db.
|
|
334
|
+
const filter = { entity, action }; // hypothetically each doc has unique entity+action.
|
|
335
|
+
const edits = {
|
|
336
|
+
$inc: {
|
|
337
|
+
num_called: (data.code && (data.tripTime || data.adapterTime || data.capabilityTime)) ? 1 : 0,
|
|
338
|
+
numRetries: data.retries || 0,
|
|
339
|
+
numRedirects: data.redirects || 0,
|
|
340
|
+
throttleCount: (data.queueTime) ? 1 : 0, // separate thing to keep count of # throttles. may not be necessary.
|
|
341
|
+
timeouts: data.timeouts || 0,
|
|
342
|
+
tot_queue_time: parseFloat(data.queueTime) || 0,
|
|
343
|
+
tot_rnd_trip: parseFloat(data.tripTime) || 0, // = tot_rnd_trip
|
|
344
|
+
tot_library: parseFloat(data.capabilityTime) || 0, // = tot_library, overall things
|
|
345
|
+
tot_overall: parseFloat(data.overallTime) || 0, // = tot_library, overall things
|
|
346
|
+
['results.'.concat(data.code)]: 1 // Note: this results in the JSON recording differing from DB record: JSON doesn't make a nested document, just "results.xxx".
|
|
347
|
+
// adapterTime: 0 // not accessible in this file, go to adapter.js later.tripTime
|
|
348
|
+
},
|
|
349
|
+
$set: {
|
|
350
|
+
time_units: 'ms',
|
|
351
|
+
entity,
|
|
352
|
+
action,
|
|
353
|
+
isThrottling: data.isThrottling
|
|
354
|
+
},
|
|
355
|
+
metric: {
|
|
356
|
+
entity,
|
|
357
|
+
action
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
// were are we writing? fs or db
|
|
361
|
+
return dbUtilInst.findAndModify(collectionName, filter, null, edits, true, null, true, (err, dbres) => {
|
|
362
|
+
if (err && !dbres) {
|
|
363
|
+
return callback(false);
|
|
364
|
+
}
|
|
365
|
+
return callback(true);
|
|
366
|
+
});
|
|
367
|
+
} catch (e) {
|
|
368
|
+
return callback(false);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
class RequestHandler {
|
|
373
|
+
/**
|
|
374
|
+
* Request Handler
|
|
375
|
+
* @constructor
|
|
376
|
+
*/
|
|
377
|
+
constructor(prongId, properties, directory) {
|
|
378
|
+
try {
|
|
379
|
+
this.myid = prongId;
|
|
380
|
+
id = prongId;
|
|
381
|
+
this.props = properties;
|
|
382
|
+
this.clean = [];
|
|
383
|
+
this.directory = directory;
|
|
384
|
+
this.suspend = false;
|
|
385
|
+
this.suspendInterval = 60000;
|
|
386
|
+
|
|
387
|
+
// need the db utilities before validation
|
|
388
|
+
this.dbUtil = new DBUtilCl(this.myid, properties, directory);
|
|
389
|
+
dbUtilInst = this.dbUtil;
|
|
390
|
+
|
|
391
|
+
// need the property utilities before validation
|
|
392
|
+
this.propUtil = new PropUtilCl(this.myid, directory, this.dbUtil);
|
|
393
|
+
propUtilInst = this.propUtil;
|
|
394
|
+
|
|
395
|
+
// reference to the needed classes for specific protocol handlers
|
|
396
|
+
this.transUtil = new TransUtilCl(prongId, this.propUtil);
|
|
397
|
+
transUtilInst = this.transUtil;
|
|
398
|
+
|
|
399
|
+
// validate the action files for the adapter
|
|
400
|
+
this.clean = walkThroughActionFiles(this.directory);
|
|
401
|
+
|
|
402
|
+
// save the adapter base directory
|
|
403
|
+
this.adapterBaseDir = directory;
|
|
404
|
+
this.clockInst = new AsyncLockCl();
|
|
405
|
+
clock = this.clockInst;
|
|
406
|
+
|
|
407
|
+
// set up the properties I care about
|
|
408
|
+
this.refreshProperties(properties);
|
|
409
|
+
|
|
410
|
+
// instantiate other runtime components
|
|
411
|
+
this.connector = new ConnectorCl(this.myid, this.props, this.transUtil, this.propUtil, this.dbUtil);
|
|
412
|
+
this.restHandler = new RestHandlerCl(this.myid, this.props, this.connector, this.transUtil);
|
|
413
|
+
} catch (e) {
|
|
414
|
+
// handle any exception
|
|
415
|
+
const origin = `${this.myid}-requestHandler-constructor`;
|
|
416
|
+
return this.transUtil.checkAndThrow(e, origin, 'Could not start Adapter Runtime Library');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* @callback Callback
|
|
422
|
+
* @param {Object} result - the result of the get request
|
|
423
|
+
* @param {String} error - any error that occured
|
|
424
|
+
*/
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* refreshProperties is used to set up all of the properties for the request handler.
|
|
428
|
+
* It allows properties to be changed later by simply calling refreshProperties rather
|
|
429
|
+
* than having to restart the request handler.
|
|
430
|
+
*
|
|
431
|
+
* @function refreshProperties
|
|
432
|
+
* @param {Object} properties - an object containing all of the properties
|
|
433
|
+
*/
|
|
434
|
+
refreshProperties(properties) {
|
|
435
|
+
const origin = `${this.myid}-requestHandler-refreshProperties`;
|
|
436
|
+
log.trace(origin);
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
// validate the properties that came in against library property schema
|
|
440
|
+
this.props = validateProperties(properties);
|
|
441
|
+
|
|
442
|
+
// get the list of failover codes
|
|
443
|
+
this.failoverCodes = [];
|
|
444
|
+
|
|
445
|
+
if (this.props.request && this.props.request.failover_codes
|
|
446
|
+
&& Array.isArray(this.props.request.failover_codes)) {
|
|
447
|
+
this.failoverCodes = this.props.request.failover_codes;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// get the cache location
|
|
451
|
+
this.cacheLocation = 'local';
|
|
452
|
+
|
|
453
|
+
if (this.props.cache_location) {
|
|
454
|
+
this.cacheLocation = this.props.cache_location;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
this.saveMetric = this.props.save_metric || false;
|
|
458
|
+
|
|
459
|
+
// set the username (required - default is null)
|
|
460
|
+
if (typeof this.props.authentication.username === 'string') {
|
|
461
|
+
username = this.props.authentication.username;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// if this is truly a refresh and we have a connector or rest handler, refresh them
|
|
465
|
+
if (this.connector) {
|
|
466
|
+
this.connector.refreshProperties(properties);
|
|
467
|
+
}
|
|
468
|
+
if (this.restHandler) {
|
|
469
|
+
this.restHandler.refreshProperties(properties);
|
|
470
|
+
}
|
|
471
|
+
} catch (e) {
|
|
472
|
+
// handle any exception
|
|
473
|
+
return this.transUtil.checkAndThrow(e, origin, 'Properties may not have been updated properly');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* checkActionFiles is used to update the validation of the action files.
|
|
479
|
+
*
|
|
480
|
+
* @function checkActionFiles
|
|
481
|
+
*/
|
|
482
|
+
checkActionFiles() {
|
|
483
|
+
const origin = `${this.myid}-requestHandler-checkActionFiles`;
|
|
484
|
+
log.trace(origin);
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
// validate the action files for the adapter
|
|
488
|
+
this.clean = walkThroughActionFiles(this.directory);
|
|
489
|
+
return this.clean;
|
|
490
|
+
} catch (e) {
|
|
491
|
+
return ['Exception increase log level'];
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* checkProperties is used to validate the adapter properties.
|
|
497
|
+
*
|
|
498
|
+
* @function checkProperties
|
|
499
|
+
* @param {Object} properties - an object containing all of the properties
|
|
500
|
+
*/
|
|
501
|
+
checkProperties(properties) {
|
|
502
|
+
const origin = `${this.myid}-requestHandler-checkProperties`;
|
|
503
|
+
log.trace(origin);
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
// validate the action files for the adapter
|
|
507
|
+
this.testPropResult = validateProperties(properties);
|
|
508
|
+
return this.testPropResult;
|
|
509
|
+
} catch (e) {
|
|
510
|
+
return { exception: 'Exception increase log level' };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* exposeDB is used to update the adapter metrics with the overall adapter time
|
|
516
|
+
*
|
|
517
|
+
* @function exposeDB
|
|
518
|
+
* @param {String} entity - the name of the entity for this request.
|
|
519
|
+
* (required)
|
|
520
|
+
* @param {String} action - the name of the action being executed. (required)
|
|
521
|
+
* @param {String} overallTime - the overall time in milliseconds (required)
|
|
522
|
+
*/
|
|
523
|
+
exposeDB(entity, action, overallTime) {
|
|
524
|
+
const origin = `${this.myid}-requestHandler-exposeDB`;
|
|
525
|
+
log.trace(origin);
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
// only allow the adapter.js to update the overallTime
|
|
529
|
+
const allowedData = {
|
|
530
|
+
overallTime
|
|
531
|
+
};
|
|
532
|
+
dbCalls('metrics', entity, action, allowedData, (status) => {
|
|
533
|
+
log.trace(`${origin}: ${status}`);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
return true;
|
|
537
|
+
} catch (e) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* @summary Method that identifies the actual request to be made and then
|
|
544
|
+
* makes the call through the appropriate Handler
|
|
545
|
+
*
|
|
546
|
+
* @function identifyRequest
|
|
547
|
+
* @param {String} entity - the name of the entity for this request.
|
|
548
|
+
* (required)
|
|
549
|
+
* @param {String} action - the name of the action being executed. (required)
|
|
550
|
+
* @param {Object} requestObj - an object that contains all of the possible
|
|
551
|
+
* parts of the request (payload, uriPathVars,
|
|
552
|
+
* uriQuery, uriOptions and addlHeaders
|
|
553
|
+
* (optional). Can be a stringified Object.
|
|
554
|
+
* @param {Boolean} translate - whether to translate the response. Defaults
|
|
555
|
+
* to true. If no translation will just return
|
|
556
|
+
* 'success' or an error message
|
|
557
|
+
* @param {Callback} callback - a callback function to return the result
|
|
558
|
+
* Data/Status or the Error
|
|
559
|
+
*/
|
|
560
|
+
identifyRequest(entity, action, requestObj, translate, callback) {
|
|
561
|
+
const meth = 'requestHandler-identifyRequest';
|
|
562
|
+
const origin = `${this.myid}-${meth}`;
|
|
563
|
+
log.trace(`${origin}: ${entity}-${action}`);
|
|
564
|
+
const overallTime = process.hrtime();
|
|
565
|
+
|
|
566
|
+
try {
|
|
567
|
+
// verify parameters passed are valid
|
|
568
|
+
if (entity === null || entity === '') {
|
|
569
|
+
const errorObj = this.formatErrorObject(this.myid, meth, 'Missing Data', ['entity'], null, null, null);
|
|
570
|
+
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
|
|
571
|
+
return callback(null, errorObj);
|
|
572
|
+
}
|
|
573
|
+
if (action === null || action === '') {
|
|
574
|
+
const errorObj = this.formatErrorObject(this.myid, meth, 'Missing Data', ['action'], null, null, null);
|
|
575
|
+
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
|
|
576
|
+
return callback(null, errorObj);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Get the entity schema from the file system
|
|
580
|
+
return this.propUtil.getEntitySchema(entity, action, this.dbUtil, (entitySchema, entityError) => {
|
|
581
|
+
// verify protocol for call
|
|
582
|
+
if (entityError) {
|
|
583
|
+
const errorObj = this.transUtil.checkAndReturn(entityError, origin, 'Issue identifiying request');
|
|
584
|
+
return callback(null, errorObj);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// verify protocol for call
|
|
588
|
+
if (!Object.hasOwnProperty.call(entitySchema, 'protocol')) {
|
|
589
|
+
const errorObj = this.formatErrorObject(this.myid, meth, 'Missing Data', ['action protocol'], null, null, null);
|
|
590
|
+
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
|
|
591
|
+
return callback(null, errorObj);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Determine the Protocol so the appropriate handler can be called
|
|
595
|
+
if (entitySchema.protocol.toUpperCase() === 'REST') {
|
|
596
|
+
return this.restHandler.genericRestRequest(entity, action, entitySchema, requestObj, translate, (result, error) => {
|
|
597
|
+
const overallDiff = process.hrtime(overallTime);
|
|
598
|
+
const overallEnd = `${Math.round(((overallDiff[0] * NS_PER_SEC) + overallDiff[1]) / 1000000)}ms`;
|
|
599
|
+
if (error) {
|
|
600
|
+
const newError = error;
|
|
601
|
+
if (!newError.metrics) {
|
|
602
|
+
newError.metrics = {};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
newError.metrics.capabilityTime = overallEnd;
|
|
606
|
+
// console.log(newError); //can't test b/c no errors built into tests rn.
|
|
607
|
+
// will call from adapterFunction.ejs only eventually since it will have all metrics here + the 2 missing ones.
|
|
608
|
+
if (this.saveMetric) {
|
|
609
|
+
dbCalls('metrics', entity, action, newError.metrics, (saved) => {
|
|
610
|
+
if (saved) {
|
|
611
|
+
log.info(`${origin}: Metrics Saved`);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
return callback(null, newError);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
let newRes = result;
|
|
619
|
+
if (!newRes) {
|
|
620
|
+
newRes = {
|
|
621
|
+
metrics: {
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
} else if (!newRes.metrics) {
|
|
625
|
+
newRes.metrics = {};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
newRes.metrics.capabilityTime = overallEnd;
|
|
629
|
+
// overallEnd is from start of identifyRequest to right before dbCalls is called (error or good).
|
|
630
|
+
// will call from adapterFunction.ejs only eventually since it will have all metrics here + the 2 missing ones.
|
|
631
|
+
if (this.saveMetric) {
|
|
632
|
+
dbCalls('metrics', entity, action, newRes.metrics, (saved) => {
|
|
633
|
+
if (saved) {
|
|
634
|
+
log.info(`${origin}: Metrics Saved`);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
return callback(newRes);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Unsupported protocols
|
|
643
|
+
const errorObj = this.formatErrorObject(this.myid, meth, 'Unsupported Protocol', [entitySchema.protocol], null, null, null);
|
|
644
|
+
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
|
|
645
|
+
return callback(null, errorObj);
|
|
646
|
+
});
|
|
647
|
+
} catch (e) {
|
|
648
|
+
// handle any exception
|
|
649
|
+
const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue identifiying request');
|
|
650
|
+
return callback(null, errorObj);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* @summary Method that identifies the protocol for the healthcheck and then
|
|
656
|
+
* takes the appropriate action.
|
|
657
|
+
*
|
|
658
|
+
* @function identifyHealthcheck
|
|
659
|
+
* @param {Object} requestObj - an object that contains all of the possible
|
|
660
|
+
* parts of the request (payload, uriPathVars,
|
|
661
|
+
* uriQuery, uriOptions and addlHeaders
|
|
662
|
+
* (optional). Can be a stringified Object.
|
|
663
|
+
* @param {Callback} callback - a callback function to return the result of
|
|
664
|
+
* the Healthcheck
|
|
665
|
+
*/
|
|
666
|
+
identifyHealthcheck(requestObj, callback) {
|
|
667
|
+
const meth = 'requestHandler-identifyHealthcheck';
|
|
668
|
+
const origin = `${this.myid}-${meth}`;
|
|
669
|
+
log.trace(origin);
|
|
670
|
+
const overallTime = process.hrtime();
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
let prot = this.props.healthcheck.protocol;
|
|
674
|
+
|
|
675
|
+
// Get the entity schema from the file system
|
|
676
|
+
return this.propUtil.getEntitySchema('.system', 'healthcheck', this.dbUtil, (healthSchema, healthError) => {
|
|
677
|
+
if (healthError || !healthSchema || Object.keys(healthSchema).length === 0) {
|
|
678
|
+
log.debug(`${origin}: Using adapter properties for healthcheck information`);
|
|
679
|
+
} else {
|
|
680
|
+
log.debug(`${origin}: Using action and schema for healthcheck information`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (healthSchema && healthSchema.protocol) {
|
|
684
|
+
prot = healthSchema.protocol;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Determine the Protocol so the appropriate handler can be called
|
|
688
|
+
if (prot.toUpperCase() === 'REST') {
|
|
689
|
+
return this.restHandler.healthcheckRest(healthSchema, requestObj, (result, error) => {
|
|
690
|
+
const overallDiff = process.hrtime(overallTime);
|
|
691
|
+
const overallEnd = `${Math.round(((overallDiff[0] * NS_PER_SEC) + overallDiff[1]) / 1000000)}ms`;
|
|
692
|
+
if (error) {
|
|
693
|
+
const newError = error;
|
|
694
|
+
if (!newError.metrics) {
|
|
695
|
+
newError.metrics = {};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
newError.metrics.capabilityTime = overallEnd;
|
|
699
|
+
// console.log(newError); //can't test b/c no errors built into tests rn.
|
|
700
|
+
// will call from adapterFunction.ejs only eventually since it will have all metrics here + the 2 missing ones.
|
|
701
|
+
if (this.saveMetric) {
|
|
702
|
+
dbCalls('metrics', '.system', 'healthcheck', newError.metrics, (saved) => {
|
|
703
|
+
if (saved) {
|
|
704
|
+
log.info(`${origin}: Metrics Saved`);
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
return callback(null, newError);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let newRes = result;
|
|
712
|
+
if (!newRes) {
|
|
713
|
+
newRes = {
|
|
714
|
+
metrics: {
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
} else if (!newRes.metrics) {
|
|
718
|
+
newRes.metrics = {};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
newRes.metrics.capabilityTime = overallEnd;
|
|
722
|
+
// overallEnd is from start of identifyRequest to right before dbCalls is called (error or good).
|
|
723
|
+
// will call from adapterFunction.ejs only eventually since it will have all metrics here + the 2 missing ones.
|
|
724
|
+
if (this.saveMetric) {
|
|
725
|
+
dbCalls('metrics', '.system', 'healthcheck', newRes.metrics, (saved) => {
|
|
726
|
+
if (saved) {
|
|
727
|
+
log.info(`${origin}: Metrics Saved`);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
return callback(newRes);
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Unsupported protocols
|
|
736
|
+
const errorObj = this.formatErrorObject(this.myid, meth, 'Unsupported Protocol', [prot], null, null, null);
|
|
737
|
+
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
|
|
738
|
+
return callback(null, errorObj);
|
|
739
|
+
});
|
|
740
|
+
} catch (e) {
|
|
741
|
+
// handle any exception
|
|
742
|
+
const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue identifiying healthcheck');
|
|
743
|
+
return callback(null, errorObj);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* getQueue is used to get information for all of the requests currently in the queue.
|
|
749
|
+
*
|
|
750
|
+
* @function getQueue
|
|
751
|
+
* @param {queueCallback} callback - a callback function to return the result (Queue)
|
|
752
|
+
* or the error
|
|
753
|
+
*/
|
|
754
|
+
getQueue(callback) {
|
|
755
|
+
const meth = 'requestHandler-getQueue';
|
|
756
|
+
const origin = `${this.myid}-${meth}`;
|
|
757
|
+
log.trace(origin);
|
|
758
|
+
|
|
759
|
+
try {
|
|
760
|
+
// Determine the Protocol so the appropriate handler can be called
|
|
761
|
+
if (this.props.healthcheck.protocol.toUpperCase() === 'REST') {
|
|
762
|
+
return this.restHandler.getQueue(callback);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Unsupported protocols
|
|
766
|
+
const errorObj = this.formatErrorObject(this.myid, meth, 'Unsupported Protocol', [this.props.healthcheck.protocol], null, null, null);
|
|
767
|
+
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
|
|
768
|
+
return callback(null, errorObj);
|
|
769
|
+
} catch (e) {
|
|
770
|
+
// handle any exception
|
|
771
|
+
const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue getting queue');
|
|
772
|
+
return callback(null, errorObj);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* @summary Takes in property text and an encoding/encryption and returns
|
|
778
|
+
* the resulting encoded/encrypted string
|
|
779
|
+
*
|
|
780
|
+
* @function encryptProperty
|
|
781
|
+
* @param {String} property - the property to encrypt
|
|
782
|
+
* @param {String} technique - the technique to use to encrypt
|
|
783
|
+
*
|
|
784
|
+
* @param {Callback} callback - a callback function to return the result
|
|
785
|
+
* Encrypted String or the Error
|
|
786
|
+
*/
|
|
787
|
+
encryptProperty(property, technique, callback) {
|
|
788
|
+
const meth = 'requestHandler-encryptProperty';
|
|
789
|
+
const origin = `${this.myid}-${meth}`;
|
|
790
|
+
log.trace(origin);
|
|
791
|
+
|
|
792
|
+
try {
|
|
793
|
+
const returnObj = {
|
|
794
|
+
icode: 'AD.200'
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
returnObj.response = this.propUtil.encryptProperty(property, technique);
|
|
798
|
+
return callback(returnObj);
|
|
799
|
+
} catch (e) {
|
|
800
|
+
// handle any exception
|
|
801
|
+
const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue encrypting property');
|
|
802
|
+
return callback(null, errorObj);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* @summary Build a standard error object from the data provided
|
|
808
|
+
*
|
|
809
|
+
* @function formatErrorObject
|
|
810
|
+
* @param {String} adaptId - the id of the adapter (required).
|
|
811
|
+
* @param {String} origin - the originator of the error (required).
|
|
812
|
+
* @param {String} failCode - the internal IAP error code or error type (required).
|
|
813
|
+
* @param {Integer} sysCode - the error code from the other system (optional).
|
|
814
|
+
* @param {Object} sysRes - the raw response from the other system (optional).
|
|
815
|
+
* @param {Exception} stack - tany available stack trace from the issue (optional).
|
|
816
|
+
*
|
|
817
|
+
* @return {Object} the error object, null if missing pertinent information
|
|
818
|
+
*/
|
|
819
|
+
formatErrorObject(adaptId, origin, failCode, variables, sysCode, sysRes, stack) {
|
|
820
|
+
const morigin = `${this.myid}-requestHandler-formatErrorObject`;
|
|
821
|
+
log.trace(morigin);
|
|
822
|
+
|
|
823
|
+
// this is just a pass through for clients to use - rather then expose translator
|
|
824
|
+
return this.transUtil.formatErrorObject(`${adaptId}-${origin}`, failCode, variables, sysCode, sysRes, stack);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* @summary Determines whether the error is one that can be failed over to
|
|
829
|
+
* the next adapter to handle.
|
|
830
|
+
*
|
|
831
|
+
* @function setFailover
|
|
832
|
+
* @param {Object} errorObj - the error object
|
|
833
|
+
*
|
|
834
|
+
* @return {Enumeration} failoverCode - a string containing the failover code
|
|
835
|
+
* 'AD.300' - failover OK
|
|
836
|
+
* 'AD.500' - no failover
|
|
837
|
+
* code from response if can not decide
|
|
838
|
+
*/
|
|
839
|
+
setFailover(errorObj) {
|
|
840
|
+
const origin = `${this.myid}-requestHandler-setFailover`;
|
|
841
|
+
log.trace(origin);
|
|
842
|
+
|
|
843
|
+
try {
|
|
844
|
+
// if the errorMsg exists and has a code, check it
|
|
845
|
+
if (errorObj && errorObj.code) {
|
|
846
|
+
if (this.failoverCodes) {
|
|
847
|
+
// is it a code that we allow failover?
|
|
848
|
+
for (let f = 0; f < this.failoverCodes.length; f += 1) {
|
|
849
|
+
if (errorObj.code === this.failoverCodes[f]) {
|
|
850
|
+
return allowFailover;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// if not resolved based on code, return what was in the error if provided
|
|
857
|
+
if (errorObj && errorObj.icode) {
|
|
858
|
+
return errorObj.icode;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// return the default result
|
|
862
|
+
return noFailover;
|
|
863
|
+
} catch (e) {
|
|
864
|
+
log.error(`${origin}: Caught Exception ${e}`);
|
|
865
|
+
return noFailover;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* @summary Check the current cache to see if we know about a specific entity
|
|
871
|
+
*
|
|
872
|
+
* @function isEntityCached
|
|
873
|
+
* @param {String} entityType - the entity type to check for
|
|
874
|
+
* @param {String/Array} entityId - the specific entity we are looking for
|
|
875
|
+
*
|
|
876
|
+
* @return {Array of Enumeration} - whether the entity was
|
|
877
|
+
* 'found' - entity was found
|
|
878
|
+
* 'notfound' - entity was not found
|
|
879
|
+
* 'error' - there was an error - check logs
|
|
880
|
+
* 'needupdate' - update cache and try again
|
|
881
|
+
*/
|
|
882
|
+
checkEntityCached(entityType, entityId, callback) {
|
|
883
|
+
const origin = `${this.myid}-requestHandler-checkEntityCached`;
|
|
884
|
+
log.trace(origin);
|
|
885
|
+
|
|
886
|
+
try {
|
|
887
|
+
if (this.cacheLocation === 'redis') {
|
|
888
|
+
const ckey = `${this.myid}__%%__cache`;
|
|
889
|
+
|
|
890
|
+
// get the cache from redis
|
|
891
|
+
return g_redis.get(ckey, (err, res) => {
|
|
892
|
+
if (err) {
|
|
893
|
+
log.error(`${origin}: Error on retrieve cache for ${ckey}`);
|
|
894
|
+
return callback(['error']);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// if there was no cache returned
|
|
898
|
+
if (!res) {
|
|
899
|
+
log.error(`${origin}: No cache for ${ckey}`);
|
|
900
|
+
return callback(['needupdate']);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// set the local cache to what we got from redis (temp storage)
|
|
904
|
+
cache = res;
|
|
905
|
+
return callback(isEntityCached(entityType, entityId));
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return callback(isEntityCached(entityType, entityId));
|
|
910
|
+
} catch (e) {
|
|
911
|
+
log.error(`${origin}: Caught Exception ${e}`);
|
|
912
|
+
return callback(['error']);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* @summary Adds the provided entity list to the cache
|
|
918
|
+
*
|
|
919
|
+
* @function addEntityCache
|
|
920
|
+
* @param {String} entityType - the entity type for the list
|
|
921
|
+
* @param {Array} entities - the list of entities to be added
|
|
922
|
+
*
|
|
923
|
+
* @param {Callback} callback - whether the cache was updated
|
|
924
|
+
*/
|
|
925
|
+
addEntityCache(entityType, entities, callback) {
|
|
926
|
+
const meth = 'requestHandler-addEntityCache';
|
|
927
|
+
const origin = `${this.myid}-${meth}`;
|
|
928
|
+
log.trace(origin);
|
|
929
|
+
let storeEnt = entities;
|
|
930
|
+
|
|
931
|
+
if (!Array.isArray(entities)) {
|
|
932
|
+
storeEnt = [entities];
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const entityEntry = {
|
|
936
|
+
list: storeEnt,
|
|
937
|
+
updated: new Date().getTime()
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
try {
|
|
941
|
+
// Lock the cache while adding items to it
|
|
942
|
+
return clock.acquire(cachelock, (done) => {
|
|
943
|
+
// if only storing locally, done
|
|
944
|
+
if (this.cacheLocation === 'local') {
|
|
945
|
+
// add the entities to the cache
|
|
946
|
+
cache[entityType] = entityEntry;
|
|
947
|
+
done(true, null);
|
|
948
|
+
} else {
|
|
949
|
+
// set the redis key
|
|
950
|
+
const ckey = `${this.myid}__%%__cache`;
|
|
951
|
+
|
|
952
|
+
// get the cache from redis
|
|
953
|
+
g_redis.get(ckey, (err, res) => {
|
|
954
|
+
if (err) {
|
|
955
|
+
log.error(`${origin}: Error on retrieve cache for ${ckey}`);
|
|
956
|
+
done(false, null);
|
|
957
|
+
} else {
|
|
958
|
+
cache = res;
|
|
959
|
+
|
|
960
|
+
// if no cache was found
|
|
961
|
+
if (!cache) {
|
|
962
|
+
cache = {};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// add the entities to the cache
|
|
966
|
+
cache[entityType] = entityEntry;
|
|
967
|
+
|
|
968
|
+
// store the cache in redis
|
|
969
|
+
g_redis.set(ckey, JSON.stringify(cache), (error) => {
|
|
970
|
+
if (error) {
|
|
971
|
+
log.error(`${origin}: Cache: ${ckey} not stored in redis`);
|
|
972
|
+
done(false, null);
|
|
973
|
+
} else {
|
|
974
|
+
done(true, null);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
}, (ret, error) => {
|
|
981
|
+
if (error) {
|
|
982
|
+
log.error(`${origin}: Error from retrieving entity cache: ${error}`);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
return callback(ret, error);
|
|
986
|
+
});
|
|
987
|
+
} catch (e) {
|
|
988
|
+
// handle any exception
|
|
989
|
+
const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue adding entity to cache');
|
|
990
|
+
return callback(null, errorObj);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* @summary Provides a way for the adapter to tell north bound integrations
|
|
996
|
+
* whether the adapter supports type and specific entity
|
|
997
|
+
*
|
|
998
|
+
* @function verifyCapability
|
|
999
|
+
* @param {String} entityType - the entity type to check for
|
|
1000
|
+
* @param {String} actionType - the action type to check for
|
|
1001
|
+
* @param {String/Array} entityId - the specific entity we are looking for
|
|
1002
|
+
*
|
|
1003
|
+
* @return {Array of Enumeration} - whether the entity was
|
|
1004
|
+
* 'found' - entity was found
|
|
1005
|
+
* 'notfound' - entity was not found
|
|
1006
|
+
* 'error' - there was an error - check logs
|
|
1007
|
+
* 'needupdate' - update cache and try again
|
|
1008
|
+
*/
|
|
1009
|
+
verifyCapability(entityType, actionType, entityId, callback) {
|
|
1010
|
+
const origin = `${this.myid}-requestHandler-verifyCapability`;
|
|
1011
|
+
log.trace(origin);
|
|
1012
|
+
const entitiesD = `${this.adapterBaseDir}/entities`;
|
|
1013
|
+
|
|
1014
|
+
try {
|
|
1015
|
+
// verify the entities directory exists
|
|
1016
|
+
if (!fs.existsSync(entitiesD)) {
|
|
1017
|
+
log.error(`${origin}: Missing ${entitiesD} directory - Aborting!`);
|
|
1018
|
+
return callback(['error']);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// get the entities this adapter supports
|
|
1022
|
+
const entities = fs.readdirSync(entitiesD);
|
|
1023
|
+
|
|
1024
|
+
// go through the entities
|
|
1025
|
+
for (let e = 0; e < entities.length; e += 1) {
|
|
1026
|
+
// did we find the entity type?
|
|
1027
|
+
if (entities[e] === entityType) {
|
|
1028
|
+
// if we are only interested in the entity
|
|
1029
|
+
if (!actionType && !entityId) {
|
|
1030
|
+
return callback(['found']);
|
|
1031
|
+
}
|
|
1032
|
+
// if we do not have an action, check for the specific entity
|
|
1033
|
+
if (!actionType) {
|
|
1034
|
+
return this.checkEntityCached(entityType, entityId, callback);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// get the entity actions from the action file
|
|
1038
|
+
const actionFile = `${entitiesD}/${entities[e]}/action.json`;
|
|
1039
|
+
if (fs.existsSync(actionFile)) {
|
|
1040
|
+
const actionJson = require(actionFile);
|
|
1041
|
+
const { actions } = actionJson;
|
|
1042
|
+
|
|
1043
|
+
// go through the actions for a match
|
|
1044
|
+
for (let a = 0; a < actions.length; a += 1) {
|
|
1045
|
+
if (actions[a].name === actionType) {
|
|
1046
|
+
// if we are not interested in a specific entity
|
|
1047
|
+
if (!entityId) {
|
|
1048
|
+
return callback(['found']);
|
|
1049
|
+
}
|
|
1050
|
+
return this.checkEntityCached(entityType, entityId, callback);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
log.warn(`${origin}: Action ${actionType} not found on entity ${entityType}`);
|
|
1055
|
+
|
|
1056
|
+
// return an array for the entityids since can not act on any
|
|
1057
|
+
const result = ['notfound'];
|
|
1058
|
+
if (entityId && Array.isArray(entityId)) {
|
|
1059
|
+
// add not found for each entity, (already added the first)
|
|
1060
|
+
for (let r = 1; r < entityId.length; r += 1) {
|
|
1061
|
+
result.push('notfound');
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return callback(result);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
log.error(`${origin}: Action ${actionType} on entity ${entityType} missing action file`);
|
|
1068
|
+
return callback(['error']);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
log.error(`${origin}: Entity ${entityType} not found in adapter`);
|
|
1073
|
+
|
|
1074
|
+
// return an array for the entityids since can not act on any
|
|
1075
|
+
const result = ['notfound'];
|
|
1076
|
+
if (entityId && Array.isArray(entityId)) {
|
|
1077
|
+
// add not found for each entity, (already added the first)
|
|
1078
|
+
for (let r = 1; r < entityId.length; r += 1) {
|
|
1079
|
+
result.push('notfound');
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return callback(result);
|
|
1084
|
+
} catch (e) {
|
|
1085
|
+
log.error(`${origin}: Caught Exception ${e}`);
|
|
1086
|
+
return callback(['error']);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* @summary Provides a way for the adapter to tell north bound integrations
|
|
1092
|
+
* all of the capabilities for the current adapter
|
|
1093
|
+
*
|
|
1094
|
+
* @function getAllCapabilities
|
|
1095
|
+
*
|
|
1096
|
+
* @return {Array} - containing the entities and the actions available on each entity
|
|
1097
|
+
*/
|
|
1098
|
+
getAllCapabilities() {
|
|
1099
|
+
const origin = `${this.myid}-requestHandler-getAllCapabilities`;
|
|
1100
|
+
log.trace(origin);
|
|
1101
|
+
const entitiesD = `${this.adapterBaseDir}/entities`;
|
|
1102
|
+
const capabilities = [];
|
|
1103
|
+
|
|
1104
|
+
try {
|
|
1105
|
+
// verify the entities directory exists
|
|
1106
|
+
if (!fs.existsSync(entitiesD)) {
|
|
1107
|
+
log.error(`${origin}: Missing ${entitiesD} directory`);
|
|
1108
|
+
return capabilities;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// get the entities this adapter supports
|
|
1112
|
+
const entities = fs.readdirSync(entitiesD);
|
|
1113
|
+
|
|
1114
|
+
// go through the entities
|
|
1115
|
+
for (let e = 0; e < entities.length; e += 1) {
|
|
1116
|
+
// get the entity actions from the action file
|
|
1117
|
+
const actionFile = `${entitiesD}/${entities[e]}/action.json`;
|
|
1118
|
+
const entityActions = [];
|
|
1119
|
+
|
|
1120
|
+
if (fs.existsSync(actionFile)) {
|
|
1121
|
+
const actionJson = require(actionFile);
|
|
1122
|
+
const { actions } = actionJson;
|
|
1123
|
+
|
|
1124
|
+
// go through the actions for a match
|
|
1125
|
+
for (let a = 0; a < actions.length; a += 1) {
|
|
1126
|
+
entityActions.push(actions[a].name);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const newEntity = {
|
|
1131
|
+
entity: entities[e],
|
|
1132
|
+
actions: entityActions
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
capabilities.push(newEntity);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return capabilities;
|
|
1139
|
+
} catch (e) {
|
|
1140
|
+
log.error(`${origin}: Caught Exception ${e}`);
|
|
1141
|
+
return capabilities;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* @summary get a token with the provide parameters
|
|
1147
|
+
*
|
|
1148
|
+
* @function makeTokenRequest
|
|
1149
|
+
* @param {Object} reqBody - Any data to add to the body of the token request
|
|
1150
|
+
* @param {Object} callProperties - Properties that override the default properties
|
|
1151
|
+
*
|
|
1152
|
+
* @return {Object} - containing the token(s)
|
|
1153
|
+
*/
|
|
1154
|
+
makeTokenRequest(reqBody, callProperties, callback) {
|
|
1155
|
+
const origin = `${this.myid}-requestHandler-makeTokenRequest`;
|
|
1156
|
+
log.trace(origin);
|
|
1157
|
+
|
|
1158
|
+
try {
|
|
1159
|
+
// set up the right credentials - passed in overrides default
|
|
1160
|
+
let useUser = username;
|
|
1161
|
+
if (callProperties && callProperties.authentication && callProperties.authentication.username) {
|
|
1162
|
+
useUser = callProperties.authentication.username;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// validate the action files for the adapter
|
|
1166
|
+
return this.connector.makeTokenRequest('/none/token/path', useUser, reqBody, null, callProperties, callback);
|
|
1167
|
+
} catch (e) {
|
|
1168
|
+
// handle any exception
|
|
1169
|
+
const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue getting token');
|
|
1170
|
+
return callback(null, errorObj);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
module.exports = RequestHandler;
|