UNPKG

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