@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
332 lines (280 loc) • 12.7 kB
JavaScript
;
var async = require('async');
var applyUriChecks = require('./../../uri/applyChecks');
var authorizationProcessor = require('./../../processor/authorizationProcessor');
var batchConst = require('./batchConst');
var configuration = require('./../../configuration');
var errorProcessor = require('./../../processor/errorProcessor');
var json = require('./../../serializer/jsonSerializer');
var metadataDbReader = require('./../../model/metadataReader');
var oDataUriParser = require('./../../uri/oDataUriParser');
var oDataProcessor = require('./../../processor/processor');
var uriChecks = require('./../../uri/checks');
var uriKeyValueParser = require('./../../parsers/uriKeyValue');
var uriProcessor = require('./../../http/uriParser');
var utils = require('./../utils');
var xsodataFileReader = require('./../../model/xsodataReader');
var ConditionalHttpHandler = require('./../../http/conditionalHttpHandler');
var DebugInfo = require('./../errors/debugInfo');
var RequestContext = require('./../requestContext');
var TestError = require('./../errors/testError');
exports.processPrePostCommitRun = function (request, response, batchContext, context, done) {
context.logger.silly('batchProcessor', 'processPrePostCommitRun');
try {
async.waterfall(
[
utils.injectContext(context),
utils.try(oDataProcessor.processRequest)
], function (err/*, contextFinish*/) {
if (err) {
if (context.mode === configuration.modes.development) {
if (err instanceof TestError) {
context.logger.debug('xsodata', 'dev mode: is TestError');
return (done ? done(err, context) : null);
}
}
if (err instanceof DebugInfo) {
var serializer = new json.JsonSerializer(context, 65536, 200);
serializer.write(err.message);
serializer.flush();
} else {
errorProcessor.process(context, err);
}
if (batchContext.inChangeSet) {
err._changeSetErrorResponse = response;
}
finish(err, context, done);
return;
}
finish(null, context, done);
}
);
} catch (exception) {
context.logger.info('xsodata', 'process-exception: ' + JSON.stringify(exception));
if (done) {
done(exception);
} else {
throw exception;
}
}
};
// STEP batch create tables --> after createNewContentIdFromRecordMapFromPayload
// move the contentId information to app.contentID which is an link to the corresponding id in part.contentIds
function fillContentID(context, asyncDone) {
var batchContext = context.batchContext;
var app = context.app;
if (batchContext.inChangeSet) {
if (app.contentId) {
context.logger.silly('batchExecutor', 'set contentID ' + app.contentId.id + ' = ' + context.contendId);
app.contentId.value = context.contentId;
app.contentId.key_nv = context.contentIdKeyObj;
}
}
return asyncDone(null, context);
}
// STEP batch process --> before processConditionalRequest and processRequest
// Because in the step create table not yet calculated auto key values may have been set to nil in the contentId, now
// (in the process step) this key values values must be updated with the calculated values.
// Updated are only the values in the dbsegment, not in the Uri, also reparsing the Uri MUST be avoided.
function updateContextIdKeyValues(context, asyncDone) {
var batchContext = context.batchContext;
if (!batchContext.inChangeSet) {
return asyncDone(null, context);
}
var updateDbSegment = null;
const firstSegment = context.oData.dbSegment;
if (firstSegment.isLinks === true) {
// no error if there is only one segment or it is a $links segment and there is only one more segment after the $links segment
if (firstSegment.nextDBSegment.nextDBSegment !== null) {
return asyncDone(new Error('The usage of ContentIds is only supported for Uris with one odata segment'), context);
}
updateDbSegment = firstSegment;
} else {
if (firstSegment.nextDBSegment !== null) {
return asyncDone(new Error('The usage of ContentIds is only supported for Uris with one odata segment'), context);
}
updateDbSegment = firstSegment;
}
var part = batchContext.processedPart;
var contentIds = part.contentIds.list;
var contentIdInfo = findContentIdInfo(context.request.urlOrig, contentIds);
if (!contentIdInfo) {
return asyncDone(null, context);
}
var newKeyNV = contentIdInfo.key_nv;
var newKeysNames = Object.keys(newKeyNV);
// instead of rewriting the uri and reparsing the uri, the already parsed keys are directly modified
if (newKeysNames.length !== updateDbSegment._KeyValues.length) {
return asyncDone(new Error('The key list of the referenced ContentIDs must fit to the parsed key list'), context);
}
var keyName;
var i;
var oldValue, newValue, newValueUri, keyProperty, newValueParsed;
for (i = 0; i < updateDbSegment._KeyValues.length; i++) {
keyName = updateDbSegment._KeyValues[i].name;
keyProperty = updateDbSegment.entityType.propertiesMap[keyName];
oldValue = updateDbSegment._KeyValues[i].value;
newValue = newKeyNV[keyName];
newValueUri = newValue; //typeConv.serializeDbValueToUriLiteral(newValue, keyProperty);
if (newKeyNV[keyName] === undefined) {
return asyncDone(new Error('The key ' + keyName + 'has not been set by the referenced request'), context);
}
newValueParsed = uriKeyValueParser.parse(newValueUri);
updateDbSegment._KeyValues[i].value = newValueParsed;
}
return asyncDone(null, context);
}
function findContentIdInfo(urlOrig, contentIds) {
var contentIdName;
var contentIdIndex;
for (contentIdIndex = 0; contentIdIndex < contentIds.length; contentIdIndex++) {
contentIdName = '$' + contentIds[contentIdIndex].id;
if (urlOrig.indexOf(contentIdName) > -1) {
return contentIds[contentIdIndex];
}
}
return null;
}
// STEP batch create tables --> before uri parsing
// Check the own URI for '$' and replace them
// Note the replacement may not be save since application data may still be missing (e.g. auto generated keys).
// but it is save enough for tmp table creation of this request
function replaceUriContendIds(context, asyncDone) {
context.logger.silly('batchExecutor', 'check uri for contentID');
var uri = context.request.url;
var batchContext = context.batchContext;
if (!batchContext.inChangeSet) {
return asyncDone(null, context);
}
var part = batchContext.processedPart;
var contentIds = part.contentIds.list;
var id;
for (var i = 0; i < contentIds.length; i++) {
id = '$' + contentIds[i].id;
if (uri.indexOf(id) > -1) {
uri = uri.replace(id, contentIds[i].value);
}
}//TODO add check if not all $ are replaced
context.request.urlOrig = context.request.url;
context.request.url = uri;
context.logger.silly('batchExecutor', 'new uri:' + uri);
return asyncDone(null, context);
}
exports.processCreateTables = function (app, request, response, batchContext, done) {
batchContext.parentContext.logger.silly(
'batchExecutor', 'process step: ' + batchContext.status + ' ' + batchConst.runStateText[batchContext.status]
);
var context = new RequestContext(
batchContext.parentContext.networkContext,
batchContext.parentContext.networkContext.requestOptions
);
app.context = context;
context.app = app;
context.db = batchContext.parentContext.db;
context.logger.silly('xsodata', 'process batch: ' + request.url);
context.batchContext = batchContext;
context.callRegisteredStep = batchContext.callRegisteredStep;
//set req,res,db data
context.request = request;
context.response = response;
//inject model into context
context.modelData = batchContext.parentContext.modelData;
context.userName = batchContext.parentContext.userName;
try {
async.waterfall(
[
utils.injectContext(context),
utils.try(replaceUriContendIds),
utils.try(uriProcessor.prepareUri),
utils.try(xsodataFileReader.loadXsodataConfiguration),
utils.try(metadataDbReader.loadModelMetadata),
utils.try(oDataUriParser.parseODataUri),
utils.try(authorizationProcessor.processAuthorization),
utils.try(applyUriChecks.bind(null, uriChecks)),
utils.try(oDataProcessor.processRequest),
utils.try(fillContentID)
], function (err) {
if (err) {
if (context.mode === configuration.modes.development) {
if (err instanceof TestError) {
context.logger.debug('xsodata', 'dev mode: is TestError');
return (done ? done(err, context) : null);
}
}
if (err instanceof DebugInfo) {
var serializer = new json.JsonSerializer(context, 65536, 200);
serializer.write(err.message);
serializer.flush();
} else {
errorProcessor.process(context, err);
}
if (batchContext.inChangeSet) {
err._changeSetErrorResponse = app.response;
}
// this request has and error and the response is already written, so don't process further step in
// the processing chain.
app.errorResponseWritten = true;
return finish(err, context, done);
}
return finish(null, context, done);
}
);
} catch (exception) {
context.logger.info('xsodata', 'process-exception: ' + JSON.stringify(exception));
if (done) {
done(exception);
} else {
throw exception;
}
}
};
exports.process = function (request, response, batchContext, context, done) {
context.logger.silly('batchExecutor', 'process step: ' + batchContext.status + ' ' + batchConst.runStateText[batchContext.status]);
var app = context.app;
try {
async.waterfall(
[
utils.injectContext(context),
utils.try(updateContextIdKeyValues),
utils.try(ConditionalHttpHandler.processConditionalRequest),
utils.try(oDataProcessor.processRequest),
utils.try(fillContentID)
], function (err) {
if (err) {
if (context.mode === configuration.modes.development) {
if (err instanceof TestError) {
context.logger.debug('xsodata', 'dev mode: is TestError');
return (done ? done(err, context) : null);
}
}
if (err instanceof DebugInfo) {
var serializer = new json.JsonSerializer(context, 65536, 200);
serializer.write(err.message);
serializer.flush();
} else {
errorProcessor.process(context, err);
}
if (batchContext.inChangeSet) {
err._changeSetErrorResponse = app.response;
}
return finish(err, context, done);
}
return finish(null, context, done);
}
);
} catch (exception) {
context.logger.info('xsodata', 'process-exception: ' + JSON.stringify(exception));
if (done) {
done(exception);
} else {
throw exception;
}
}
};
function finish(err, context, done) {
context.logger.info('xsodata', 'batchExecutor finish');
context.response.end();
if (done) {
return done(err, context);
}
}