UNPKG

@sap/xsodata

Version:

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

510 lines (456 loc) 15.2 kB
'use strict'; const fs = require('fs'); const sql = require('./../sql/sqlStatement'); const path = require('path'); //cache the css file const 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) { const 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 const 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) { const 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) { let 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) { let 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) { const req = {}; let 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) { const res = {}; let 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) { let pr, obj, prSub, segment; const 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.uri ////// Query parameters obj = context.uriTree.queryParameters; for (pr in obj) { if ( Object.prototype.hasOwnProperty.call(obj, pr) && pr.startsWith('$') ) { // 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 const segmentKind = [ 'empty', '', 'entity', 'resourceNavigation', 'property', 'count', 'navigation', ]; // read from dbSegment.js server.uri.uriResourceParts = []; segment = context.oData === null ? null : context.oData.dbSegment; let 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) { const 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; } const container = context.sql.container; let i; let tmp; const 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) { out.select.push(stmToObject(tmp)); } } } function stmToObject(stm) { const sqlLocal = { sql: null, parameters: [], }; sqlLocal.sql = stm.toSqlHana( new sql.SqlBuildHanaContext(context), sqlLocal.parameters ); return sqlLocal; } return out; } function getXSOData(context) { const xs = {}; let 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) { const principal = navigation.from.principal ? 'principal' : '-'; return ( navigation.association + ' as ' + navigation.name + ' from ' + (navigation.from.dependent ? 'dependent' : 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) { const error = JSON.parse(reponse).error; let line = []; const 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 { const 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) { let pr; let 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() { let 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; }