@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
222 lines (187 loc) • 8.35 kB
JavaScript
;
const _ = require('lodash');
const HttpError = require('./../utils/errors/httpError');
const SqlError = require('../utils/errors/sqlError');
const dataCollector2 = require('./../sql/dataCollector2');
var EXIT_REGEXP = /([\w\.\/\-]+)(:((\w+)\.(js|xsjslib)))?::(\w+)/;
module.exports = {
ExitSpec: ExitSpec,
eventHandler: eventHandler,
executeExit: executeExit
};
function ExitSpec(sExitSpec) {
var aMatch = EXIT_REGEXP.exec(sExitSpec);
function throwMalformedSpec() {
throw new Error('Invalid exit specification ' + sExitSpec +
'. Expected format package.package:file.ext::function');
}
if (!aMatch) {
throwMalformedSpec();
}
this.package = aMatch[1];
this.file = aMatch[4];
this.ext = aMatch[5];
this.functionName = aMatch[6];
if (!this.functionName) {
throwMalformedSpec();
}
}
/**
* @param sEventType : string, one of: before, after, precommit, postcommit
* @param sOperation : string, one of: create, update, delete
* @returns {Function} ready to be plugged in the async waterfall
*/
function eventHandler(sEventType, sOperation) {
return function callEventHandler(context, asyncDone) {
var modifications; // The defined modifications (custom exits) of the given odata service entry (association | entityType)
var association, entityType;
var operation, modelExitSpec;
var foundEvent;
// if links, e.g. /Employees('1')/$links/ne_Team, modifications are given in the in-xsodata-defined association (TeamEmployees)
if (context.oData.dbSegment && context.oData.dbSegment.isLinks) {
// extract the corresponding association, then its modifications
association = context.oData.links.association;
modifications = association.modification || {};
}
// otherwise, modifications belong to the single given entityType
else {
// extract the corresponding entityType, then its modifications
entityType = context.oData.dbSegmentLast.entityType;
modifications = entityType.modifications || {};
}
operation = modifications[sOperation] || {};
if (operation.events) {
foundEvent = _.find(operation.events, function (event) {
return event.type === sEventType;
});
if (foundEvent) {
modelExitSpec = foundEvent;
}
}
if (modelExitSpec) {
executeExit(modelExitSpec.action, sEventType, sOperation, context, asyncDone);
} else {
return asyncDone(null, context);
}
};
}
/**
*
* @param sExitSpec is exit spec as string, for example my_package.my_subpackage:my_file.xsjslib::my_function
* @param sEventType is a string, one of 'using','before','after', 'precommit', 'postcommit'
* @param sOperation is a string, one of 'create','update','delete'
* @param context is the odata context
* @param asyncDone is the async framework callback
*/
function executeExit(sExitSpec, sEventType, sOperation, context, asyncDone) {
try {
logInfoSave(context, 'custom exit', 'executing ...');
logInfoSave(context, 'custom exit', 'exitName:'+sExitSpec);
logInfoSave(context, 'custom exit', 'eventType:'+sEventType);
logInfoSave(context, 'custom exit', 'operation:'+sOperation);
_executeExit(sExitSpec, sEventType, sOperation, context, asyncDone);
} catch (err) {
return asyncDone(err, context);
}
}
function logInfoSave(context, preFix, logText) {
if (context.logger) {
context.logger.info(preFix, logText);
}
}
function logErrorSave(context, preFix, logText) {
if (context.logger) {
context.logger.error(preFix, logText);
}
}
function _executeExit(sExitSpec, sEventType, sOperation, context, asyncDone) {
var exitSpec = new ExitSpec(sExitSpec);
exitSpec._dbClient = context.db ? context.db.client : null;
var dbSegment = context.oData.dbSegmentLast;
var attribute1name; // afterTableName | (if links) principalTableName
var table1name; // the corresponding table name for attrib.1
var attribute2name; // beforeTableName | (if links) dependentTableName
var table2name; // the corresponding table name for attrib.2
var param = {}, aParams = [];
if (context.oData.dbSegment && context.oData.dbSegment.isLinks) {
dbSegment = context.oData.links.toBeUpdated;
attribute1name = "principalTableName";
table1name = dbSegment.sql.rIdPrincipal;
attribute2name = "dependentTableName";
table2name = dbSegment.sql.rIdDependent;
logInfoSave(context, 'custom exit - principalTable', table1name);
logInfoSave(context, 'custom exit - dependentTable', table2name);
} else {
dbSegment = context.oData.dbSegmentLast;
attribute1name = "afterTableName";
table1name = dbSegment.sql.rId || dbSegment.sql.rIdNew;
attribute2name = "beforeTableName";
table2name = dbSegment.sql.rIdOld;
logInfoSave(context,'custom exit - afterTableName', table1name);
logInfoSave(context,'custom exit - beforeTableName', table2name);
}
if (exitSpec.file) {
//script reference
param[attribute1name] = table1name;
param[attribute2name] = table2name;
logInfoSave(context,'custom exit - script-file', exitSpec.file);
context.functionExecutor(exitSpec, param, function (err, result) {
// Custom exits return errors of a specific format (DevGuid, SPS10, Table75, Page494)
// if so, returned error must be reformatted
if (err) {
logErrorSave(context, 'custom exit failed with error', err);
return asyncDone(new HttpError(err.ERROR_MESSAGE || 'Internal Server Error', context, null, err.HTTP_STATUS_CODE || 500, err), context);
}
if (result && result !== true) {
logErrorSave(context, 'custom exit returned invalid result', result);
return asyncDone(new HttpError(result.ERROR_MESSAGE || 'Internal Server Error', context, null, result.HTTP_STATUS_CODE || 500, result), context);
}
logInfoSave(context, 'custom exit', '... execution done');
return asyncDone(null, context);
});
} else {
//sql procedure reference
table1name = '"' + table1name + '"';
table2name = '"' + table2name + '"';
logErrorSave(context, 'custom exit', 'SQL-procedure');
logErrorSave(context, 'operation', sOperation);
logErrorSave(context, 'custom exit - table-1', table1name);
logErrorSave(context, 'custom exit - table-2', table2name);
switch (sOperation) {
case 'create' : {
aParams = [table1name];
break;
}
case 'update' : {
aParams = [table1name, table2name];
break;
}
case 'delete' : {
aParams = [table2name];
break;
}
}
if (sEventType !== 'postcommit') {
aParams.push('?'); // for the error out parameter of the stored procedure
}
var sParams = aParams.join(',');
var sql = 'call "' + sExitSpec + '"(' + sParams + ')';
logInfoSave(context, 'custom exit:SQL-procedure', sql);
dataCollector2.executeSqlAsPreparedStatement(context, sql, [], (err, outTable) => {
if (err) {
logErrorSave(context, 'custom exit SQL-procedure failed', err);
return asyncDone(new SqlError(context, err), context);
} else {
if (outTable && isNaN(outTable) && outTable.length > 0) {
var appError = outTable[0];
var statusCode = appError.HTTP_STATUS_CODE;
var errorMessage = appError.ERROR_MESSAGE;
var innerError = JSON.stringify(appError);
err = new HttpError(errorMessage, context, null, statusCode, innerError);
}
logInfoSave(context, 'custom exit', '... execution done');
return asyncDone(err, context);
}
});
}
}