@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
435 lines (375 loc) • 14.2 kB
JavaScript
;
var fs = require('fs');
var sql = require('./../sql/sqlStatement');
var path = require('path');
//cache the css file
var styleCSS = fs.readFileSync(path.join(__dirname, '/style.css'));
/**
* Checks if query parameter "sap-ds-debug" exists, and if yes
* "context.debugView" attribute is set to object { "format" : <format> }
* @param context
* @param asyncDone
* @returns {*}
*/
exports.checkParameter = function (context, asyncDone) {
var showDebugViewFormat = context.uriTree.queryParameters["sap-ds-debug"];
if (showDebugViewFormat) {
context.debugView = { "format": showDebugViewFormat };
}
return asyncDone(null, context);
};
exports.writeDebugInfo = function (context, rFrom, rTo) {
switch (context.debugView.format) {
case "json":
rTo.writeHead(rFrom.statusCode, generateHead(context.response.headers, 'application/json'));
rTo.write(JSON.stringify(generateViewJSON(context), null, 4));
break;
case "html":
rTo.writeHead(rFrom.statusCode, generateHead(context.response.headers, 'text/html'));
rTo.write(generateViewHTML(context));
break;
default:
rTo.writeHead(rFrom.statusCode, generateHead(context.response.headers, 'text/plain'));
rTo.write('Unknown debug view format');
}
};
function generateHead(responseHeaders, contentType) {
// clone the response headers using JSON
var debugHeaders = JSON.parse(JSON.stringify(responseHeaders));
// change the application type according to query parameters: e.g. json, html, ..
debugHeaders['Content-Type'] = contentType + ';charset=utf-8';
return debugHeaders;
}
function generateViewJSON(context) {
var debView = {
'request': {},
'response': {},
'server': {},
'SQL': {},
'xsodata': {}
};
try {
debView.request = createRequestInfo(context);
debView.response = createResponseInfo(context);
debView.server = createServerInfo(context);
debView.SQL = createSQLInfo(context);
debView.xsodata = getXSOData(context);
if (context.response.statusCode >= 400) {
debView.stacktrace = {};
debView.stacktrace = serializeStacktrace(context.response.data);
}
return debView;
} catch (exp) {
return exp;
}
}
function generateViewHTML(context) {
var debView = "<html>";
try {
debView += getHTMLHead();
debView += getHTMLBody(context);
} catch (exp) {
return null;
}
debView += "</html>";
return debView;
}
function getHTMLHead() {
return '<head>' + '<style type="text/css">' + styleCSS + '</style>' + '</head>';
}
function getHTMLBody(context) {
var body = '<body>';
body += objectToHTML(createRequestInfo(context), 'Request', true);
body += objectToHTML(createResponseInfo(context), 'Response', true);
body += objectToHTML(createServerInfo(context), 'Server', true);
body += objectToHTML(createSQLInfo(context), 'SQL', true);
body += objectToHTML(getXSOData(context), 'xsodata', true);
body += sectionDownload();
body += '</body>';
return body;
}
function createRequestInfo(context) {
var req = {};
var pr, obj;
req.method = context.request.method;
req.uri = context.request.url;
req.protocol = 'HTTP/' + context.request.httpVersion;
req.headers = {};
obj = context.request.headers;
for (pr in obj) {
if (obj.hasOwnProperty(pr) && typeof obj[pr] !== 'function') {
req.headers[pr] = obj[pr];
}
}
return req;
}
function createResponseInfo(context) {
var res = {};
var pr, obj;
res.status = context.response.statusCode;
res.headers = {};
obj = context.response.headers;
for (pr in obj) {
if (obj.hasOwnProperty(pr) && typeof obj[pr] !== 'function') {
res.headers[pr] = obj[pr];
}
}
res.body = context.response.data;
return res;
}
function createServerInfo(context) {
var pr, obj, prSub, segment;
var server = {
'version': '',
'dbVersion': '',
'environment': {},
'uri': {},
'runtime': {}
};
//// Version
server.version = require('../../package.json').version;
server.dbVersion = context.gModel.getDbVersion();
//// Server.environment
server.environment.localAddr = context.httpResponse.connection.localAddress;
server.environment.localPort = context.httpResponse.connection.localPort;
server.environment.remoteAddr = context.httpResponse.connection.remoteAddress;
server.environment.remotePort = context.httpResponse.connection.remotePort;
server.environment.remoteFamily = context.httpResponse.connection.remoteFamily;
//server.environment = process.env;
//// Server.uri
////// Query parameters
obj = context.uriTree.queryParameters;
for (pr in obj) {
if (Object.prototype.hasOwnProperty.call(obj, pr) && pr.substr(0, 1) === '$') { // ignore non-property parameters
prSub = pr.substring(1, pr.length); // ignore $ sign in the output
server.uri[prSub] = (prSub === 'select' ? obj[pr].split(',') : obj[pr]);
}
}
////// uriResourceParts
var segmentKind = ['empty', '', 'entity', 'resourceNavigation', 'property', 'count', 'navigation']; // read from dbSegment.js
server.uri.uriResourceParts = [];
segment = (context.oData === null ? null : context.oData.dbSegment);
var segObj;
while (typeof segment !== 'undefined' && segment !== null) {
segObj = {};
segObj.segment = segment._AliasName || "";
segObj.uriResourceKind = segmentKind[segment.kind];
segObj.isCollection = segment.isCollection;
segObj.singleProperty = segment.singleProperty;
segObj.restriction = {};
obj = segment.restriction;
for (pr in obj) {
if (obj.hasOwnProperty(pr)) {
segObj.restriction[pr] = obj[pr];
}
}
server.uri.uriResourceParts.push(segObj);
segment = segment.nextDBSegment;
}
//// Runtime
server.runtime = measurementsToObject(context.measurements);
function measurementsToObject(measurementsArr) {
var measurementsObj = {};
measurementsArr.forEach(function (m) {
measurementsObj[m.name] = {};
measurementsObj[m.name].duration = m.time + ' microSec';
if (m.children.length !== 0) {
measurementsObj[m.name].children = measurementsToObject(m.children);
}
});
return measurementsObj;
}
return server;
}
/**
* Extracts SQL information from "context.sql.container" and returns a Java Script object containing
* all sql statements as string and their parameters.
* @param context
* @returns {{createTmp: Array, insertTmp: Array, selectTmp: Array, select: Array}}
*/
function createSQLInfo(context) {
if (!context.sql) {
return;
}
var container = context.sql.container;
var i;
var tmp;
var out = {
createTmp: [],
insertTmp: [],
selectTmp: [],
select: []
};
for (i = 0; i < container.createTmp.length; i++) {
out.createTmp.push(stmToObject(container.createTmp[i].stm));
}
for (i = 0; i < container.insertTmp.length; i++) {
out.insertTmp.push(stmToObject(container.insertTmp[i].stm));
}
for (i = 0; i < container.selectTmp.length; i++) {
out.selectTmp.push(stmToObject(container.selectTmp[i].stm));
}
for (i = 0; i < container.select.length; i++) {
out.select.push(stmToObject(container.select[i].stm));
if (container.select[i].getFallbackStatement) {
tmp = container.select[i].getFallbackStatement();
if (tmp) {
//sqlLocal = 'Count Fallback: ' + tmp.toSqlHana(new sql.SqlBuildHanaContext(context), out.parameters);
out.select.push(stmToObject(tmp));
}
}
}
function stmToObject(stm) {
var sqlLocal = {
sql: null,
parameters: []
};
sqlLocal.sql = stm.toSqlHana(new sql.SqlBuildHanaContext(context), sqlLocal.parameters);
return sqlLocal;
}
return out;
}
function getXSOData(context) {
var xs = {};
var entity; // temporary variables
//// Database objects
xs['Database Objects'] = {};
context.gModel._entityTypes.forEach(function (elemEnt) {
entity = elemEnt.tableName;
xs['Database Objects'][entity] = {};
////// Name
xs['Database Objects'][entity].EntitySet = elemEnt.name;
////// Navigation
xs['Database Objects'][entity].navigates = [];
elemEnt.navProperties.forEach(function (elemNav) {
xs['Database Objects'][entity].navigates.push(createNavigationText(elemNav));
});
////// Modifications
xs['Database Objects'][entity].modifications = {};
if (elemEnt.modifications.hasOwnProperty('create')) {
xs['Database Objects'][entity].modifications.create = elemEnt.modifications.create;
}
if (elemEnt.modifications.hasOwnProperty('update')) {
xs['Database Objects'][entity].modifications.update = elemEnt.modifications.update;
}
if (elemEnt.modifications.hasOwnProperty('delete')) {
xs['Database Objects'][entity].modifications.delete = elemEnt.modifications.delete;
}
////// Properties
xs['Database Objects'][entity].properties = [];
elemEnt.properties.forEach(function (elemProp) {
xs['Database Objects'][entity].properties.push(
elemProp.COLUMN_NAME +
'(Type: ' + elemProp.DATA_TYPE_NAME + ', ' +
(elemProp.IS_NULLABLE === 'TRUE' ? 'Nullable' : 'Not nullable') + ')');
});
});
//// Associations
xs.Associations = {};
context.gModel._associations.forEach(function (elemAsc) {
xs.Associations[elemAsc.name] = {};
xs.Associations[elemAsc.name].principal = createAssociationEndText(elemAsc, 'principal');
xs.Associations[elemAsc.name].dependent = createAssociationEndText(elemAsc, 'dependent');
});
function createNavigationText(navigation) {
return navigation.association +
' as ' + navigation.name + ' from ' +
(navigation.from.dependent ? 'dependent' : (navigation.from.principal ? 'principal' : '-'));
}
function createAssociationEndText(association, end) {
if (end !== 'principal' && end !== 'dependent') {
return;
}
return association[end].type +
' (' + association[end].joinproperties[0] + ') ' +
'multiplicity ' + '\'' + association[end].multiplicity + '\'';
}
return xs;
}
function serializeStacktrace(reponse) {
var error = JSON.parse(reponse).error;
var line = [];
var serliaized = {};
serliaized.exception = error.message.value;
serliaized.stack = error.message.stack.split(' at ').map(function (value) {
// input example: ResourcePathReader.processResourcePath (C:\\git\\xsodata\\lib\\uri\\resourcePathParser.js:275:34)
//* cut out lines, where there is no "methodName (path)"
line = value.split(' (');
if (line.length === 1) {
return null;
}
return line;
}).filter(function (value) {
// cut out returned nulls
return value !== null;
}).map(function (value) {
try {
var node = {};
node.method = value[0];
node.path = value[1].split('.js')[0] + '.js'; // first segment before .js
node.line = value[1].split('.js')[1].split(':')[1]; // first segment after .js -> e.g. ':275:34'
return node;
} catch (ex) {
return {
method: '<unknown>',
path: '<unknown>',
line: '<unknown>'
};
}
});
return serliaized;
}
function objectToHTML(objIn, varName, firstLevel) {
var pr;
var html = '';
if (firstLevel) {
html += '<div class="header" id="sec_' + varName + '">' +
'<h1><a href="#sec_' + varName + '">' + varName + '</a></h1>' +
'</div>';
html += '<div class="section">' +
'<table>';
for (pr in objIn) {
if (objIn.hasOwnProperty(pr) && typeof objIn[pr] !== 'function') {
html += objectToHTML(objIn[pr], pr, false);
}
}
html += '</table></div>';
} else if (typeof objIn === 'object' && !Array.isArray(objIn)) {
html += '<tr class="tabtr">' +
'<td class="name"><h2>' + varName + '</h2></td>' +
'<td class="value">' +
'<table class="objTab">';
for (pr in objIn) {
if (objIn.hasOwnProperty(pr) && typeof objIn[pr] !== 'function') {
html += objectToHTML(objIn[pr], pr, false);
}
}
html += '</table></td></tr>';
} else if (typeof objIn === 'object' && Array.isArray(objIn)) {
html += '<tr>' +
'<td class=""><h2>' + varName + '</h2>' +
//'<td class="value">' +
'<table>';
objIn.forEach(function (x) {
html += objectToHTML(x, '', false);
});
html += '</table></td></tr>';
} else {
html += '<tr>' +
'<td class="name"><h2>' + varName + '</h2></td>' +
'<td class="value">' + objIn + '</td>' +
'</tr>';
}
return html;
}
function sectionDownload() {
var html = '';
html += '<div class="header2" id="sec_download">' +
'<h1><a href="#sec_download"> Download </a></h1>' +
'</div>';
html += '<div class="section">' +
'<a onclick="this.href=,data:text/html;charset=UTF-8," href="#" download="page.html">Click here</a>' +
', to download this document as an HTML file!' +
'</div>';
return html;
}