UNPKG

@sap/xsodata

Version:

Expose data from a HANA database as OData V2 service with help of .xsodata files.

266 lines (234 loc) 9.37 kB
'use strict'; const _ = require('lodash'); const HttpError = require('./../utils/errors/httpError'); const SqlError = require('../utils/errors/sqlError'); const dataCollector2 = require('./../sql/dataCollector2'); const EXIT_REGEXP = /^([\w\.\/\-]+)(:((\w+)\.(js|xsjslib)))?::(\w+)$/; module.exports = { ExitSpec: ExitSpec, eventHandler: eventHandler, executeExit: executeExit, }; function ExitSpec(sExitSpec) { const 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) { let modifications; // The defined modifications (custom exits) of the given odata service entry (association | entityType) let association, entityType; let operation, modelExitSpec; let 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) { const exitSpec = new ExitSpec(sExitSpec); exitSpec._dbClient = context.db ? context.db.client : null; let dbSegment = context.oData.dbSegmentLast; let attribute1name; // afterTableName | (if links) principalTableName let table1name; // the corresponding table name for attrib.1 let attribute2name; // beforeTableName | (if links) dependentTableName let table2name; // the corresponding table name for attrib.2 let 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 } const sParams = aParams.join(','); const 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) { const appError = outTable[0]; const statusCode = appError.HTTP_STATUS_CODE; const errorMessage = appError.ERROR_MESSAGE; const innerError = JSON.stringify(appError); err = new HttpError( errorMessage, context, null, statusCode, innerError ); } logInfoSave(context, 'custom exit', '... execution done'); return asyncDone(err, context); } } ); } }