UNPKG

@sap/xsodata

Version:

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

389 lines (326 loc) 13.1 kB
'use strict'; const async = require('async'); const connect = require('../db/connect'); const utils = require('./../utils/utils'); const contentIdHelper = require('./contentIdHelper'); const contentTypeCheck = require('./../utils/checkContentType'); const dataCollectorPost = require('./../sql/dataCollectorPost'); const exitProcessor = require('./exitProcessor'); const serializer = require('./../serializer/serializer'); const sqlPost = require('./../sql/createPostStatements'); const typeConverter = require('./../utils/typeConverter'); const AtomXmlSerializer = require('./../serializer/atomXmlToJsonSerializer'); const BadRequestError = require('./../utils/errors/http/badRequest'); const InternalError = require('./../utils/errors/internalError'); const UnsupportedMediaType = require('./../utils/errors/http/unsupportedMediaType'); const model = require('../model/model.js'); const { eventPost } = require('./eventActionHandler'); const eventBefore = eventPost.eventBefore; const eventAfter = eventPost.eventAfter; const eventPrecommit = eventPost.eventPrecommit; const eventPostCommit = eventPost.eventPostCommit; function movePayloadToDbSegment_RecordNV(context, asyncDone) { const req = context.request; const contentType = req.headers['content-type']; try { context.logger.silly('resourceProcessorPost', 'movePayloadToDbSegment'); if (contentType) { const 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 { const dbSeg = context.oData.dbSegmentLast; const data = buffer.toString('utf-8'); if ( contentTypeCheck.isContentTypeXml(contentType) === true && utils.isXml(data) === true ) { const typeModel = dbSeg.entityType.propertiesMap; const serializer = new AtomXmlSerializer(data, typeModel); return serializer.serialize(function (err, innerContext) { if (err) { return asyncDone(err, context); } if (!Array.isArray(innerContext.result)) { const err1 = new InternalError( 'Serialization result is not an array', context ); return asyncDone(err1, context); } const len = innerContext.result.length; if (len !== 1) { const err2 = new InternalError( 'Serialization result length must be 1 but is ' + len, context ); return asyncDone(err2, context); } const result = innerContext.result[0]; dbSeg.setRecordFromPostPayload(context, result); return asyncDone(null, context); }); } if (contentTypeCheck.isContentTypeJson(contentType) === true) { let 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); } } } function modifyDbSegment(context, asyncDone) { const 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) { const dbSeg = context.oData.dbSegmentLast; const entityType = dbSeg.entityType; const 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 setStatus(context, asyncDone) { context.response.status(201); return asyncDone(null, context); } function writeLocationHeader(context, asyncDone) { const dbSeg = context.oData.dbSegmentLast; let row; try { let location = dbSeg.entityType.name; const keysProperties = dbSeg.getKeysProperties(); const rows = dbSeg.getRowsWithGenKey(); 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 const value = typeConv.serializeDbValueToUriLiteral(row[index.toString()]/*keys[index]*/, keyProperty); let 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) { const 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); } ); };