@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
461 lines (390 loc) • 14.8 kB
JavaScript
;
const async = require('async');
const connect = require('../db/connect');
const sqlPutPostLinks = require('./../sql/createPutPostLinksStatements');
const dataCollectorLinks = require('./../sql/dataCollectorLinks');
const dataCollectorPutPostLinks = require('./../sql/dataCollectorPutPostLinks');
const DBSegment = require('./../db/dbSegment');
const serializer = require('./../serializer/serializer');
const exitProcessor = require('./exitProcessor');
const utils = require('./../utils/utils');
const contenTypeCheck = require('./../utils/checkContentType');
const oDataSegmentParser = require('../parsers/jison_segment_parser');
const UnsupportedMediaType = require('./../utils/errors/http/unsupportedMediaType');
const BadRequest = require('./../utils/errors/http/badRequest');
const { eventPutPostLinks } = require('./eventActionHandler');
const eventBefore = eventPutPostLinks.eventBefore;
const eventAfter = eventPutPostLinks.eventAfter;
const eventPrecommit = eventPutPostLinks.eventPrecommit;
const eventPostCommit = eventPutPostLinks.eventPostCommit;
function movePayloadToDbSegment(context, asyncDone) {
const req = context.request;
const contentType = req.headers['content-type'];
try {
context.logger.silly(
'resourceProcessorPutPostLinks',
'movePayloadToDbSegment'
);
if (contentType) {
if (!contenTypeCheck.isSupportedContentType(contentType)) {
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) {
let data;
let json;
if (err) {
return asyncDone(err, context);
}
try {
data = buffer.toString('utf-8');
if (contenTypeCheck.isContentTypeJson(contentType) === true) {
try {
json = JSON.parse(data);
} catch (e) {
return asyncDone(
new BadRequest(
'Request payload is not a valid JSON object',
context
),
context
);
}
const keys = getKeyFromPostPutDeleteLinksUrl(
json,
context.oData.dbSegmentLast.entityType.name,
context.uriTree.baseUrl
);
context.oData.dbSegmentLast.setKeyValues(keys);
if (!context.oData.dbSegmentLast.m2n) {
DBSegment.DbSegment.setRecordPutPostLinks(context);
}
return asyncDone(null, 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: ' +
data
);
} catch (err) {
return asyncDone(err, context);
}
}
}
/**
* Move key information from uri into key maps if $links is used
* @param linksPayload
* @param refEntity
* @param baseUrl
*/
function getKeyFromPostPutDeleteLinksUrl(linksPayload, refEntity, baseUrl) {
if (!linksPayload.uri) {
throw new BadRequest('Error in Payload: Missing URI parameter');
}
let count = 0;
for (const p in linksPayload) {
if (linksPayload.hasOwnProperty(p)) {
count++;
}
}
if (count > 1) {
throw new BadRequest('Error in Payload: Too many parameters');
}
linksPayload.uri = decodeURIComponent(linksPayload.uri);
const lastSegIndexBegin = linksPayload.uri.lastIndexOf('/');
if (lastSegIndexBegin > 0) {
// Absolute URL
const payloadBaseUrl = linksPayload.uri.substring(
0,
lastSegIndexBegin + 1
);
if (payloadBaseUrl !== baseUrl) {
throw new BadRequest(
'Error in Payload: The base URL does not match the request base URL.'
);
}
}
let segment = linksPayload.uri.substring(
lastSegIndexBegin + 1,
linksPayload.uri.length
);
let parsed;
try {
parsed = oDataSegmentParser.parse(segment);
} catch (e) {
throw new BadRequest('Invalid entity URI', context);
}
if (parsed.identifier !== refEntity) {
throw new BadRequest(
'Error in Payload: Entity name does not match the last DBSegment in URI'
);
}
return parsed.keys;
}
function insertTmpTableToRealTable(context, asyncDone) {
const dbSeg = context.oData.dbSegmentLast;
const entityType = dbSeg.entityType;
const create = (entityType.modifications || {}).update || {};
if (create.using) {
exitProcessor.executeExit(
create.using,
'using',
context.oData.links.sOperation,
context,
asyncDone
);
} else {
dataCollectorLinks.insertTmpTableToRealTable(context, asyncDone);
}
}
function insertTmpTableToRealMNTable(context, asyncDone) {
const dbSeg = context.oData.links.toBeUpdated;
const entityType = dbSeg.entityType;
const create = (entityType.modifications || {}).create || {};
if (create.using) {
exitProcessor.executeExit(
create.using,
'using',
'create',
context,
asyncDone
);
} else {
dataCollectorPutPostLinks.insertTmpTableToRealMNTable(
context,
asyncDone
);
}
}
function setStatus(context, asyncDone) {
context.response.status(204);
return asyncDone(null, context);
}
exports.process = function (context, asyncDone) {
context.logger.silly('resourceProcessorPutPostLinks', 'process');
// Adapt the operation that is passed to the custom exit handler
if (context.request.method === 'POST') {
context.oData.links.sOperation = 'create';
} else if (context.request.method === 'PUT') {
context.oData.links.sOperation = 'update';
}
let execArr;
const m2n = context.oData.dbSegmentLast.getOver() !== undefined;
if (m2n) {
// INSERT into 3rd table
context.oData.dbSegmentLast.m2n = true;
execArr = [
utils.injectContext(context),
//preparation
utils.try(
sqlPutPostLinks.createPutPostLinksMNStatementsCreateTmpTables
),
utils.try(dataCollectorLinks.createTmpTableMN), //create //NEW ORL
//utils.try(contentIdHelper.createNewContentIdFromRecordMapFromPayload),
utils.try(movePayloadToDbSegment),
utils.try(sqlPutPostLinks.createPutPostLinksMNStatementsInsert),
utils.try(dataCollectorLinks.movePayloadFromMNSegmentToSelectStm), //no change
// no auto key gen usage here
utils.try(
dataCollectorLinks.insertOldDataToPrincipalDependentTables
),
utils.try(dataCollectorLinks.insertPayloadIntoTempTable), //insert NEW
//execution
utils.try(eventBefore),
utils.try(insertTmpTableToRealMNTable), //insert real
utils.try(eventAfter),
//post processing
//utils.try(dataCollectorLinks.selectData), //select
//commit handling
utils.try(eventPrecommit),
utils.try(dataCollectorLinks.commit),
utils.try(eventPostCommit),
// cleanup
utils.try(dataCollectorLinks.truncateTempTablesMN),
utils.try(dataCollectorLinks.dropTempTablesMN),
utils.try(dataCollectorLinks.commit),
//utils.try(modifyDbSegment),
//utils.try(writeLocationHeader),
//utils.try(serializer.serializeData),
utils.try(setStatus),
];
} else {
execArr = [
utils.injectContext(context),
//preparation
utils.try(
sqlPutPostLinks.createPutPostLinksStatementsCreateTmpTables
),
utils.try(dataCollectorLinks.createTmpTables), //create //NEW ORL
//utils.try(contentIdHelper.createNewContentIdFromRecordMapFromPayload),
utils.try(movePayloadToDbSegment),
utils.try(sqlPutPostLinks.createPutPostLinksStatementsInsert),
utils.try(dataCollectorLinks.moveRecordNV_ToSelectStm), //no change
// no auto key gen usage here
utils.try(dataCollectorLinks.insertOldDataToOldTable),
utils.try(
dataCollectorLinks.insertOldDataToPrincipalDependentTables
),
utils.try(dataCollectorLinks.insertPayloadIntoTempTable), //insert NEW
//execution
utils.try(eventBefore),
utils.try(insertTmpTableToRealTable),
utils.try(eventAfter),
//commit handling
utils.try(eventPrecommit),
utils.try(dataCollectorLinks.commit),
utils.try(eventPostCommit),
// cleanup
utils.try(dataCollectorLinks.truncateTempTables),
utils.try(dataCollectorLinks.dropTempTables),
utils.try(dataCollectorLinks.commit),
//post processing
utils.try(serializer.serializeNoContent),
];
}
async.waterfall(execArr, 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(
'resourceProcessorPutPostLinks',
'processInBatchCreateTables'
);
let execArr;
const m2n = context.oData.dbSegmentLast.getOver() !== undefined;
if (m2n) {
// INSERT into 3rd table
context.oData.dbSegmentLast.m2n = true;
execArr = [
utils.injectContext(context),
utils.try(
sqlPutPostLinks.createPutPostLinksMNStatementsCreateTmpTables
),
utils.try(dataCollectorLinks.createTmpTableMN), //create //NEW ORL
//utils.try(contentIdHelper.createNewContentIdFromRecordMapFromPayload),
];
} else {
execArr = [
utils.injectContext(context),
//preparation
utils.try(
sqlPutPostLinks.createPutPostLinksStatementsCreateTmpTables
),
utils.try(dataCollectorLinks.createTmpTables), //create //NEW ORL
//utils.try(contentIdHelper.createNewContentIdFromRecordMapFromPayload),
];
}
async.waterfall(execArr, function (err, context) {
return asyncDone(err, context);
});
};
exports.processInBatch = function (context, asyncDone) {
context.logger.silly('resourceProcessorPutPostLinks', 'processInBatch');
let execArr;
const m2n = context.oData.dbSegmentLast.getOver() !== undefined;
if (m2n) {
// INSERT into 3rd table
context.oData.dbSegmentLast.m2n = true;
execArr = [
utils.injectContext(context),
utils.try(movePayloadToDbSegment),
utils.try(sqlPutPostLinks.createPutPostLinksMNStatementsInsert),
utils.try(dataCollectorLinks.movePayloadFromMNSegmentToSelectStm), //no change
// no auto key gen usage here
utils.try(
dataCollectorLinks.insertOldDataToPrincipalDependentTables
),
utils.try(dataCollectorLinks.insertPayloadIntoTempTable), //insert NEW
//execution
utils.try(eventBefore),
utils.try(insertTmpTableToRealMNTable), //insert real
utils.try(eventAfter),
//post processing
//utils.try(dataCollectorLinks.selectData), //select
];
} else {
execArr = [
utils.injectContext(context),
utils.try(movePayloadToDbSegment),
utils.try(sqlPutPostLinks.createPutPostLinksStatementsInsert),
//utils.try(contentIdHelper.createNewContentIdFromRecordMapFromPayload),
utils.try(dataCollectorLinks.moveRecordNV_ToSelectStm), //no change
utils.try(dataCollectorLinks.insertOldDataToOldTable),
utils.try(
dataCollectorLinks.insertOldDataToPrincipalDependentTables
),
utils.try(dataCollectorLinks.insertPayloadIntoTempTable), //insert NEW
//execution
utils.try(eventBefore),
utils.try(insertTmpTableToRealTable),
utils.try(eventAfter),
];
}
async.waterfall(execArr, function (err, context) {
return asyncDone(err, context);
});
};
exports.processInBatchPreCommitRun = function (context, asyncDone) {
context.logger.silly(
'resourceProcessorPutPostLinks',
'processInBatchPreCommitRun'
);
async.waterfall(
[
utils.injectContext(context),
//commit handling
utils.try(eventPrecommit),
],
function (err, context) {
return asyncDone(err, context);
}
);
};
exports.processInBatchPostCommitRun = function (context, asyncDone) {
context.logger.silly(
'resourceProcessorPutPostLinks',
'processInBatchPostCommitRun'
);
async.waterfall(
[
utils.injectContext(context),
utils.try(eventPostCommit),
// truncate temporary tables to clean the session
utils.try(dataCollectorLinks.truncateTempTables),
// drop temporary tables to clean the session
utils.try(dataCollectorLinks.dropTempTables),
//post processing
utils.try(serializer.serializeNoContent),
],
function (err, context) {
return asyncDone(err, context);
}
);
};