UNPKG

@sap/xsodata

Version:

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

401 lines (317 loc) 13.8 kB
'use strict'; var async = require('async'); var connect = require('../db/connect'); var utils = require('./../utils/utils'); var contentIdHelper = require('./contentIdHelper'); var contentTypeCheck = require('./../utils/checkContentType'); var dataCollectorPost = require('./../sql/dataCollectorPost'); var exitProcessor = require('./exitProcessor'); var serializer = require('./../serializer/serializer'); var sqlPost = require('./../sql/createPostStatements'); var typeConverter = require('./../utils/typeConverter'); var AtomXmlSerializer = require('./../serializer/atomXmlToJsonSerializer'); var BadRequestError = require('./../utils/errors/http/badRequest'); var InternalError = require('./../utils/errors/internalError'); var UnsupportedMediaType = require('./../utils/errors/http/unsupportedMediaType'); const model = require('../model/model.js'); function movePayloadToDbSegment_RecordNV(context, asyncDone) { var req = context.request; var contentType = req.headers['content-type']; try { context.logger.silly('resourceProcessorPost', 'movePayloadToDbSegment'); if (contentType) { var isSupportedContentType = contentTypeCheck.isSupportedContentType(contentType); if (isSupportedContentType === false) { throw new UnsupportedMediaType('Content-Type ' + contentType + ' not supported.'); } } if (req.body) { //SAPINFO This IF covers the Express case. The Express module sets the body in the request object onData(null, req.body); } else { req.getBodyAsString(onData); } } catch (err) { return asyncDone(err, context); } function onData(err, buffer) { if (err) { return asyncDone(err, context); } try { var dbSeg = context.oData.dbSegmentLast; var data = buffer.toString('utf-8'); if (contentTypeCheck.isContentTypeXml(contentType) === true && utils.isXml(data) === true) { var typeModel = dbSeg.entityType.propertiesMap; var serializer = new AtomXmlSerializer(data, typeModel); return serializer .serialize(function (err, innerContext) { if (err) { return asyncDone(err, context); } if (!Array.isArray(innerContext.result)) { var err1 = new InternalError("Serialization result is not an array", context); return asyncDone(err1, context); } var len = innerContext.result.length; if (len !== 1) { var err2 = new InternalError("Serialization result length must be 1 but is " + len, context); return asyncDone(err2, context); } var result = innerContext.result[0]; dbSeg.setRecordFromPostPayload(context, result); return asyncDone(null, context); }); } if (contentTypeCheck.isContentTypeJson(contentType) === true) { var json; try { json = JSON.parse(data); } catch (e) { return asyncDone(new BadRequestError("Request payload is not a valid json" + " object", context), context); } try { dbSeg.setRecordFromPostPayload(context, json); return asyncDone(null, context); } catch (err) { return asyncDone(err, context); } } // If we reach this code no valid content type could be found // or the payload is not valid throw new UnsupportedMediaType('Content-Type ' + contentType + ' does not match payload.', context); } catch (err) { return asyncDone(err, context); } return asyncDone(null, context); } } function modifyDbSegment(context, asyncDone) { var dbSeg = context.oData.dbSegmentLast; //The dbSegments are build by URL so the last segment for a POST request is a collection. //But the returned value from a POST request is the newly created entity, so the last dbsegments //must be switched to by a collection to make the serialization work (the _KeyValues of the dbsegment are //filled properly before in movePayloadToDbSegment->setRecordPOST dbSeg.isCollection = false; return asyncDone(null, context); } function insertTmpTableToRealTable(context, asyncDone) { var dbSeg = context.oData.dbSegmentLast; var entityType = dbSeg.entityType; var create = (entityType.modifications || {}).create || {}; if (create.using) { exitProcessor.executeExit(create.using, 'using', 'create', context, asyncDone); } else { dataCollectorPost.insertTmpTableToRealTable(context, function (err) { return asyncDone(err, context); }); } } function eventBefore(context, asyncDone) { context.logger.info('create', 'event start before'); var eventFunction = exitProcessor.eventHandler('before', 'create'); eventFunction(context, function (err, context) { context.logger.info('create event end', 'before'); if (err) { context.logger.info('create event end', 'error occurred'); } return asyncDone(err, context); }); } function eventAfter(context, asyncDone) { context.logger.info('create', 'event start after'); var eventFunction = exitProcessor.eventHandler('after', 'create'); eventFunction(context, function (err, context) { context.logger.info('create event end', 'after'); if (err) { context.logger.info('create event end', 'error occurred'); } return asyncDone(err, context); }); } function eventPrecommit(context, asyncDone) { context.logger.info('create', 'event start precommit'); var eventFunction = exitProcessor.eventHandler('precommit', 'create'); eventFunction(context, function (err, context) { context.logger.info('create event end', 'precommit'); if (err) { context.logger.info('create event end', 'error occured'); } return asyncDone(err, context); }); } function eventPostCommit(context, asyncDone) { context.logger.info('create', 'event start postcommit'); var eventFunction = exitProcessor.eventHandler('postcommit', 'create'); eventFunction(context, function (err, context) { context.logger.info('create event end', 'postcommit'); if (err) { context.logger.info('create event end', 'error occured'); } return asyncDone(err, context); }); } function setStatus(context, asyncDone) { context.response.status(201); return asyncDone(null, context); } function writeLocationHeader(context, asyncDone) { var dbSeg = context.oData.dbSegmentLast; try { var location = dbSeg.entityType.name; var keysProperties = dbSeg.getKeysProperties(); var rows = dbSeg.getRowsWithGenKey(); var row = rows[0]; location += '('; location += keysProperties.map(toValues).filter(v => v !== undefined).join(','); location += ')'; //contentId //context.contentId = location; //Location header context.response.setHeader("location", context.uriTree.baseUrl + location); return asyncDone(null, context); } catch (err) { return asyncDone(err, context); } function toValues(keyProperty, index, array) { //old var value = typeConv.serializeDbValueToUriLiteral(row[index.toString()]/*keys[index]*/, keyProperty); var value; if (dbSeg.entityType.kind === model.entityKind.inputParameters || dbSeg.entityType.kind === model.entityKind.calculationView) { // for create, update, delete on a calcview, input parameters are optional and are NOT rendered in location url //if (dbSeg.entityType._entityType.parameters && dbSeg.entityType._entityType.parameters.viaKey === true) { return undefined; // optional in both case (provided as keys and as entity set) } else { value = typeConverter.serializeDbValueToUriLiteral(row[keyProperty.COLUMN_NAME], keyProperty); } if (array.length === 1) { return value; } return keyProperty.COLUMN_NAME + '=' + value; } } exports.process = function (context, asyncDone) { context.logger.silly('resourceProcessorPost', 'process'); async.waterfall( [ utils.injectContext(context), //preparation utils.try(sqlPost.createPostStatementsForCreateTmpTables), utils.try(dataCollectorPost.createTmpTables), //create //NEW ORL utils.try(contentIdHelper.createNewContentIdFromRecordMapFromPayload), utils.try(movePayloadToDbSegment_RecordNV), utils.try(sqlPost.createPostStatementsForInsert), utils.try(dataCollectorPost.moveRecordNV_ToSelectStm), //no change utils.try(dataCollectorPost.checkForAutoKeyGenUsage), utils.try(dataCollectorPost.insertPayloadIntoTempTable), //insert NEW //execution utils.try(eventBefore), utils.try(insertTmpTableToRealTable), //insert real utils.try(eventAfter), //post processing utils.try(dataCollectorPost.selectData), //select utils.try(contentIdHelper.createNewContentIdFromDbSegSelectedRows), // update contextId with auto key values //commit handling utils.try(eventPrecommit), utils.try(dataCollectorPost.selectData), //select utils.try(dataCollectorPost.commit), utils.try(eventPostCommit), // cleanup utils.try(dataCollectorPost.truncateTempTables), utils.try(dataCollectorPost.dropTempTables), utils.try(dataCollectorPost.commit), utils.try(modifyDbSegment), utils.try(writeLocationHeader), utils.try(serializer.serializeData), utils.try(setStatus) ], function (err, context) { if (err) { var dbClient = context.db.client; if (dbClient) { //ROLLBack the changes return connect.dbRollback(context, dbClient, function (errDB) { if (errDB) { return asyncDone(errDB, context); } return asyncDone(err, context); }); } } return asyncDone(err, context); } ); }; exports.processInBatchCreateTables = function (context, asyncDone) { context.logger.silly('resourceProcessorPost', 'process'); async.waterfall( [ utils.injectContext(context), utils.try(sqlPost.createPostStatementsForCreateTmpTables), utils.try(dataCollectorPost.createTmpTables), utils.try(contentIdHelper.createNewContentIdFromRecordMapFromPayload) ], function (err, context) { return asyncDone(err, context); } ); }; exports.processInBatch = function (context, asyncDone) { context.logger.silly('resourceProcessorPost', 'process'); async.waterfall( [ utils.injectContext(context), //preparation utils.try(movePayloadToDbSegment_RecordNV), utils.try(sqlPost.createPostStatementsForInsert), utils.try(dataCollectorPost.moveRecordNV_ToSelectStm), utils.try(dataCollectorPost.checkForAutoKeyGenUsage), utils.try(dataCollectorPost.insertPayloadIntoTempTable), //execution utils.try(eventBefore), utils.try(insertTmpTableToRealTable), utils.try(eventAfter), //post processing utils.try(dataCollectorPost.selectData), // for context id utils.try(contentIdHelper.createNewContentIdFromDbSegSelectedRows) // update contextId with auto key values //no commit handling ], function (err, context) { return asyncDone(err, context); } ); }; exports.processInBatchPreCommitRun = function (context, asyncDone) { context.logger.silly('resourceProcessorPost', 'processInBatchPreCommitRun'); async.waterfall( [ utils.injectContext(context), utils.try(eventPrecommit), utils.try(dataCollectorPost.selectData) // for response ], function (err, context) { return asyncDone(err, context); } ); }; exports.processInBatchPostCommitRun = function (context, asyncDone) { context.logger.silly('resourceProcessorPost', 'processInBatchPostCommitRun'); async.waterfall( [ utils.injectContext(context), utils.try(eventPostCommit), // clean up utils.try(dataCollectorPost.truncateTempTables), utils.try(dataCollectorPost.dropTempTables), //post processing utils.try(modifyDbSegment), utils.try(writeLocationHeader), utils.try(serializer.serializeData), utils.try(setStatus) ], function (err, context) { return asyncDone(err, context); } ); };