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