UNPKG

@sap/xsodata

Version:

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

316 lines (250 loc) 10.9 kB
'use strict'; var async = require('async'); var connect = require('../db/connect'); var batchParser = require('./../utils/batch/batchParser'); var batchExecutor = require('./../utils/batch/batchExecutor'); var simpleRequest = require('./../http/simpleHttpRequest'); var simpleResponse = require('./../http/simpleHttpResponse'); var batchRunState = require('./../utils/batch/batchConst').runState; var utils = require('./../utils/utils'); function parseBatchBody(batchContext, asyncDone) { batchContext.logger.silly('batchProcessor', 'parseBody'); var req = batchContext.parentContext.request; var boundary = batchParser.getBoundary(req.headers['content-type']); return req.getBodyAsString(function (err, body) { if (err) { return asyncDone(err, batchContext); } try { batchContext.logger.silly(body); // log batch payload batchContext.batchParsed = batchParser.convertBatch(body, boundary); batchContext.logger.silly('batchProcessor', 'parseBody OK'); return asyncDone(null, batchContext); } catch (err) { return asyncDone(err, batchContext); } }); } function processApp(batchContext, app, resultDone) { batchContext.logger.silly('batchProcessor', 'processApp'); if (batchContext.status === batchRunState.createTables) { app.request = simpleRequest.createRequestFromAppHttp(app); app.response = simpleResponse.createResponse(); } if (app.errorResponseWritten) { return resultDone(null, app); } if (batchContext.status === batchRunState.createTables) { batchExecutor.processCreateTables(app, app.request, app.response, batchContext, function (err) { return resultDone(batchContext.inChangeSet ? err : null, app); }); } else if (batchContext.status === batchRunState.execution) { batchExecutor.process(app.request, app.response, batchContext, app.context, function (err) { return resultDone(batchContext.inChangeSet ? err : null, app); }); } else if (batchContext.status === batchRunState.preCommitRun) { batchExecutor.processPrePostCommitRun(app.request, app.response, batchContext, app.context, function (err) { return resultDone(batchContext.inChangeSet ? err : null, app); }); } else if (batchContext.status === batchRunState.postCommitRun) { batchExecutor.processPrePostCommitRun(app.request, app.response, batchContext, app.context, function (err) { return resultDone(batchContext.inChangeSet ? err : null, app); }); } } function processParts(batchContext, asyncDone) { var parts = batchContext.processedPart.parts; async.mapSeries( parts, function (part, cb) { try { processApp(batchContext, part, cb); } catch (ex) { cb(ex); } }, function (err) { batchContext.logger.silly('Batch', 'processChangeSet finished'); return asyncDone(err, batchContext); } ); } function collectContentIds(batchContext, asyncDone) { var part = batchContext.processedPart; var contentIds = part.contentIds = {list: [], map: {}}; part.parts.forEach(function (app) { var id = app.rawData.headers['content-id']; if (id) { var idTriple = {id: id, app: app, value: null, key_nv : {}}; app.contentId = idTriple; contentIds.list.push(idTriple); contentIds.map[id] = idTriple; } }); return asyncDone(null, batchContext); } function processChangeSetPartsCreateTable(batchContext, asyncDone) { batchContext.status = batchRunState.createTables; processParts(batchContext, asyncDone); } function processChangeSetParts(batchContext, asyncDone) { batchContext.status = batchRunState.execution; processParts(batchContext, asyncDone); } function processChangeSetPartsPreCommit(batchContext, asyncDone) { batchContext.status = batchRunState.preCommitRun; processParts(batchContext, asyncDone); } function processChangeSetPartsCommit(batchContext, asyncDone) { var parentContext = batchContext.parentContext; var client = parentContext.db.client; batchContext.logger.debug('processChangeSetPartsCommit', 'commit changeset operations'); client.commit(function (err) { if (err) { batchContext.logger.info('SQL Exec', 'Commit Error: \n' + JSON.stringify(err)); return asyncDone(err, batchContext); } return asyncDone(null, batchContext); }); } function processChangeSetPartsPostCommit(batchContext, asyncDone) { batchContext.status = batchRunState.postCommitRun; processParts(batchContext, asyncDone); } function processChangeSet(batchContext, part, asyncDone) { try { batchContext.logger.silly('Batch', 'processChangeSet'); batchContext.inChangeSet = true; batchContext.processedPart = part; async.waterfall([ utils.injectContext(batchContext), utils.try(collectContentIds), utils.try(processChangeSetPartsCreateTable), // for *ALL* operations of current changeset: create all temp. tables once at beginning utils.try(processChangeSetParts), // execute all operations utils.try(processChangeSetPartsPreCommit), // mainly custom-exit processing (pre-commit exit) utils.try(processChangeSetPartsCommit), // all changes of operations are committed utils.try(processChangeSetPartsPostCommit) // mainly custom-exit processing (post-commit exit) ], function (err, batchContext) { if (!err) { batchContext.status = batchRunState.executed; batchContext.inChangeSet = false; batchContext.processedPart = null; return asyncDone(null, batchContext); } else { var dbClient = batchContext.parentContext.db.client; if (dbClient) { //ROLLBack the changes return connect.dbRollback(batchContext.parentContext, dbClient, function (errDB) { if (errDB) { //Error in rollback ends whole request return asyncDone(errDB, batchContext); } //inside one of the changeset part an error occured updateBatchContext(batchContext, part, err); //batchContext.status = batchRunState.executedWithError; //batchContext.inChangeSet = false; //part.changeSetError = err; //part.changeSetErrorResponse = err._changeSetErrorResponse; //batchContext.processedPart = null; //Error is set back with changeSetErrorResponse, proceed with further batch parts return asyncDone(null, batchContext); }); } else { updateBatchContext(batchContext, part, err); return asyncDone(null, batchContext); } } }); } catch (err) { return asyncDone(err, batchContext); } } function updateBatchContext(batchContext, part, err) { batchContext.status = batchRunState.executedWithError; batchContext.inChangeSet = false; part.changeSetError = err; part.changeSetErrorResponse = err._changeSetErrorResponse; batchContext.processedPart = null; } function processAppWithState(app, state, batchContext, asyncDone) { batchContext.status = state; processApp(batchContext, app, function (err) { asyncDone(err, batchContext); }); } function processPart(batchContext, part, asyncDone) { batchContext.logger.silly('batchProcessor', 'processPart'); if (part.type === 'app') { // processing a batch single operation (not in changeset) async.waterfall( [ utils.injectContext(batchContext), utils.try(processAppWithState, part, batchRunState.createTables), utils.try(processAppWithState, part, batchRunState.execution) ], function (err) { return asyncDone(err, batchContext); } ); } else { if (batchContext.inChangeSet === false) { processChangeSet(batchContext, part, asyncDone); } else { return asyncDone(new Error('Nested changesets not allowed'), batchContext); } } } function processBatch(batchContext, asyncDone) { batchContext.logger.silly('batchProcessor', 'processBatch'); var batchParts = batchContext.batchParsed.parts; async.mapSeries( batchParts, function (item, cb) { try { processPart(batchContext, item, cb); } catch (ex) { cb(ex); } }, function (err) { return asyncDone(err, batchContext); } ); } function sendBatchResponse(batchContext, asyncDone) { var context = batchContext.parentContext; batchContext.batchParsed.write(context, context.response); // We send http 202 because of OData v2 specification 2.2.7.6.6 Batch Responses: // // "If a data service receives Batch Request (section 2.2.7.6) with a valid set // of HTTP request headers,it MUST respond with a 202 Accepted HTTP response code // to indicate that the request has been accepted for processing, but that the processing // has not yet been completed." if (context.response.statusCode === 200) { // The status code is only set to 202 if no other implementation has already set to another // status code than 200 which is the default context.response.statusCode= 202; } return asyncDone(null, batchContext); } exports.process = function (context, asyncDone) { context.logger.silly('batchProcessor', 'started'); var batchContext = { parentContext: context, logger: context.logger, callRegisteredStep : context.callRegisteredStep, // pass to inner context inChangeSet: false, batchData: null, batchParsed: null, status: null }; async.waterfall( [ utils.injectContext(batchContext), utils.tryAndMeasure(parseBatchBody, 'parseBatchBody'), utils.tryAndMeasure(processBatch, 'processBatch'), utils.tryAndMeasure(sendBatchResponse, 'sendBatchResponse') ], function (err) { context.logger.silly('batchProcessor', 'finished'); return asyncDone(err, context); } ); };