UNPKG

@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
'use strict'; 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); } }); } }