UNPKG

@sap/xsodata

Version:

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

291 lines (252 loc) 8.98 kB
'use strict'; const typeConverter = require('./../utils/typeConverter'); const dbs = require('./../db/dbSegment'); const NotFound = require('./../utils/errors/http/notFound'); const InternalError = require('./../utils/errors/internalError'); const configuration = require('./../configuration'); const model = require('../model/model.js'); exports.JsonBuilder = JsonBuilder; function JsonBuilder(context) { this._ns = context.gModel.getNamespace(); this._baseUrl = context.uriTree.baseUrl; this._context = context; } JsonBuilder.prototype.createConverterArray = function (dbSeg) { return dbSeg .getSelectedPropsWithGenKey() .map(function toConverter(property) { return dbSeg.entityType.converterMapToJsonPayload[property]; }); }; JsonBuilder.prototype.serializeEntity = function (dbSegment) { const entity = this._serializeEntity(dbSegment); return toODataJson(this._context, entity); }; JsonBuilder.prototype._serializeEntity = function (dbSeg) { const rows = dbSeg.getRowsWithGenKey(); if (rows.length === 0) { if ( dbSeg.entityType._entityType.parameters && dbSeg.entityType._entityType.parameters.viaKey === true ) { throw new NotFound( 'Entity with provided input parameter and key not found.' ); } else { throw new NotFound('Resource not found.'); } } const converter = this.createConverterArray(dbSeg); return this.serializeRow(this._context, dbSeg, rows[0], converter); }; JsonBuilder.prototype.serializeProperty = function (dbSeg) { if (dbSeg.getRowsWithGenKey().length > 1) { throw new InternalError('Too many rows for serializing a property.'); } const property = dbSeg.singleProperty; let converter; const result = {}; converter = dbSeg.entityType.converterMapToJsonPayload[property]; result[property] = converter(dbSeg.getRowsWithGenKey()[0][property]); return toODataJson(this._context, result); }; JsonBuilder.prototype.serializeFeed = function (dbSegment, inlineCount) { const results = this._serializeFeed(dbSegment, inlineCount); const feed = { __count: inlineCount, results: results, }; return toODataJson(this._context, feed); }; JsonBuilder.prototype._serializeFeed = function (dbSeg) { const rows = dbSeg.getRowsWithGenKey(); const results = []; const converter = this.createConverterArray(dbSeg); for (const row of rows) { const line = this.serializeRow(this._context, dbSeg, row, converter); results.push(line); } return results; }; JsonBuilder.prototype.serializeNavMany = function (context, dbSeg, parent1row) { const rows = dbSeg.getRowsWithGenKey(); const results = []; let row = rows[dbSeg.sql.readPosition]; const converter = this.createConverterArray(dbSeg); while (row && row['0row'] === parent1row) { const line = this.serializeRow(context, dbSeg, row, converter); results.push(line); //next dbSeg.sql.readPosition++; row = rows[dbSeg.sql.readPosition]; } return results; }; JsonBuilder.prototype.serializeNavOne = function (context, dbSeg, parent1row) { const rows = dbSeg.getRowsWithGenKey(); const results = []; let row = rows[dbSeg.sql.readPosition]; const converter = this.createConverterArray(dbSeg); while (row && row['0row'] === parent1row) { const line = this.serializeRow(context, dbSeg, row, converter); results.push(line); //next dbSeg.sql.readPosition++; row = rows[dbSeg.sql.readPosition]; } if (results.length > 1) { context.logger.silly( 'json', 'Error: Too many records for nav to one: ' + results.length + ', ' + parent1row ); throw new InternalError('Too many records for nav to one'); } return results[0]; }; JsonBuilder.prototype.createAbsUrl = function (dbSeg, row) { let ret = this._baseUrl + dbSeg.entityType.name; const keysProperties = dbSeg.getKeysProperties(); ret += '('; ret += keysProperties .map(toValues) .filter((v) => v !== undefined) .join(','); return ret + ')'; function toValues(keyProperty, index, array) { const value = computeKeyValueForUrl(row, keyProperty, dbSeg); if (array.length === 1) { return value; } return keyProperty.COLUMN_NAME + '=' + value; } }; JsonBuilder.prototype.serializeRow = function (context, dbSeg, row, converter) { const ret = {}; const url = this.createAbsUrl(dbSeg, row); if (context.oData.dbSegment.isLinks) { ret.uri = url; return ret; } ret.__metadata = { uri: url, type: this._ns + '.' + dbSeg.entityType.name + 'Type', }; if (row.__etag) { ret.__metadata.etag = 'W/"' + row.__etag + '"'; } if (!dbSeg.hasAliasedKeyPropertiesOnCalcView()) { dbSeg .getSelectedPropsWithGenKey() .forEach(function toConverter(property, index) { ret[property] = converter[index](row[property]); }); } else { let selectedProperties = dbSeg.getSelectedPropsWithGenKey(); for (let i = 0; i < selectedProperties.length; i++) { if (!row[selectedProperties[i]]) { let aliasedKeyProperty = dbSeg.getAliasedKeyPropertyOnCalcView( selectedProperties[i] ); if (aliasedKeyProperty !== null && aliasedKeyProperty.alias) { ret[selectedProperties[i]] = converter[i]( row[aliasedKeyProperty.alias] ); } else { ret[selectedProperties[i]] = converter[i]( row[selectedProperties[i]] ); // keeps null } } else { ret[selectedProperties[i]] = converter[i]( row[selectedProperties[i]] ); } } } for (const sn of dbSeg._SelectedNavigations) { const navDbSeg = dbSeg.getRelevantNavigationSegments()[sn]; if (navDbSeg && navDbSeg.isExpand) { ret[sn] = this.serializeDbSeg(context, navDbSeg, row['1row']); } else { ret[sn] = { __deferred: { uri: url + '/' + sn } }; } } return ret; }; JsonBuilder.prototype.serializeDbSeg = function (context, dbSeg, parent1row) { let d; if (dbSeg.kind === dbs.DBS_Entity) { if (dbSeg.isCollection) { d = { results: this._serializeFeed(context, dbSeg) }; } else { d = this._serializeEntity(context, dbSeg); } } else if (dbSeg.kind === dbs.DBS_Navigation) { if (dbSeg.isCollection) { d = { results: this.serializeNavMany(context, dbSeg, parent1row) }; } else { d = this.serializeNavOne(context, dbSeg, parent1row); } } return d; }; function toODataJson(context, object) { const response = { d: object }; if (context.mode === configuration.modes.development) { return JSON.stringify(response, null, 4); } else { return JSON.stringify(response, null, 0); } } function computeKeyValueForUrl(row, keyProperty, dbSeg) { if (isCalcViewViaKey(dbSeg)) { const direct = row[keyProperty.COLUMN_NAME]; if (direct === null) { return null; } if (direct !== undefined) { return typeConverter.serializeDbValueToUriLiteral( direct, keyProperty ); } const alias = findAliasForCalcViewKey(dbSeg, keyProperty.COLUMN_NAME); if (alias !== null && row[alias] !== undefined) { return typeConverter.serializeDbValueToUriLiteral( row[alias], keyProperty ); } return null; } return typeConverter.serializeDbValueToUriLiteral( row[keyProperty.COLUMN_NAME], keyProperty ); } function isCalcViewViaKey(dbSeg) { return ( dbSeg.entityType && dbSeg.entityType.kind === model.entityKind.calculationView && dbSeg.entityType._entityType && dbSeg.entityType._entityType.parameters && dbSeg.entityType._entityType.parameters.viaKey === true ); } function findAliasForCalcViewKey(dbSeg, columnName) { if (!dbSeg.hasAliasedKeyPropertiesOnCalcView()) { return null; } for (const aliasedKeyProperty of dbSeg._aliasedKeyPropertiesOnCalcView) { if ( aliasedKeyProperty.property === columnName && aliasedKeyProperty.table === dbSeg._Alias ) { return aliasedKeyProperty.alias; } } return null; }