@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
JavaScript
;
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);
}
);
};