@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
JavaScript
;
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);
}
);
};