UNPKG

@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
'use strict'; 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=&#44;data:text/html;charset=UTF-8&#44;" href="#" download="page.html">Click here</a>' + ', to download this document as an HTML file!' + '</div>'; return html; }