enhancer-data-bridge
Version:
A bridge between Enhancer Clould and user business datasource
481 lines (439 loc) • 17.3 kB
JavaScript
var mysql = require('mysql');
var logger = require('log4js').getLogger('io');
var escapeHTML =require('escape-html');
var DatabaseService = require('../database-service');
var mocker = require('../mock');
var config = require('../config');
var pattern = {
variableAndIdentifier: /(@(\d+-)?\w+(\.\w+)*@)|(\$(\d+-)?\w+(\.\w+)*\$)/g,
variable: /@(\d+-)?\w+(\.\w+)*@/g,
clientVariable: /(@\d+-\w+(\.\w+)*@)|(\$\d+-\w+(\.\w+)*\$)/g,
identifier: /\$(\d+-)?\w+(\.\w+)*\$/g
};
var os = require('os');
var path = require('path');
var customModuleBase = path.resolve(__dirname, '../repository/project/custom-module');
if (os.platform() === 'win32') {
customModuleBase = customModuleBase.replace(/\\/g, '/') + '/';
} else {
customModuleBase = customModuleBase + '/';
}
var requireNocacheDefine = "var requireNocache = require('require-nocache')(module);";
var requireNocache = require('require-nocache')(module);
var MESSAGE = {
en: {
BEFORE_SQL_SYNTAX_ERR: 'The before sql function has syntax err. Reason: ',
AFTER_SQL_SYNTAX_ERR: 'The after sql function has syntax err. Reason: ',
AFTER_SQL_EXE_ERR: 'Error occured when executing after sql script. Reason: ',
SQL_COMPILE_ERR: 'Error occured when compile sql. Reason: ',
PROCEDURE_SYNTAX_ERR: 'Error occured when compile procedure. Reason: ',
PROCEDURE_EXE_ERR: 'Error occured when executing procedure. Reason: ',
TIMEOUT: 'Execute timeout. Please check if the done() method is called correctly',
OP_SUCC: 'Operation successfully!',
OP_FAILED: 'Operation failed!'
},
'zh-cn': {
BEFORE_SQL_SYNTAX_ERR: 'SQL执行前脚本有语法错误,请检查书写是否正确。原因:',
AFTER_SQL_SYNTAX_ERR: 'SQL执行后脚本有语法错误,请检查书写是否正确。原因:',
AFTER_SQL_EXE_ERR: '执行SQL执行后脚本时发生错误,请检查书写是否正确。原因:',
SQL_COMPILE_ERR: '解析 SQL 时发生错误,请检查书写是否正确。原因:',
PROCEDURE_SYNTAX_ERR: '代码有语法错误,请检查书写是否正确。原因:',
PROCEDURE_EXE_ERR: '执行过程时发生错误,原因: ',
TIMEOUT: '执行超时。请检查 done 方法是否被正确地调用,确保后续程序执行。',
OP_SUCC: '操作成功!',
OP_FAILED: '操作失败!'
}
};
exports.before = function(req, res, next) {
if (req.method === 'OPTIONS') {
res.sendStatus(200);
return;
}
var dbConfiguration;
try {
dbConfiguration = JSON.parse(req.body.database || req.query.database);
} catch (e) {
return next(
new Error('Invalid database setting for preview.')
);
}
var criteria;
try {
criteria = JSON.parse(req.body.criteria || req.query.criteria);
} catch (e) {
return res.end('Invalid criteria');
}
if (!criteria.query) {
return res.end();
}
// Mock server vars for user debug
var serverVars = req.body.serverVars || req.query.serverVars;
try {
serverVars = JSON.parse(decodeURIComponent(serverVars));
} catch(e) {
serverVars = {};
}
// Append Session Variables
var vbs = req.session._variables;
if (vbs) {
for (var i in vbs) {
serverVars[i.toUpperCase()] = vbs[i];
}
}
criteria.serverVars = serverVars;
criteria.sourceId = req.params[0];
req.criteria = criteria;
req.dbService = new DatabaseService(dbConfiguration);
next();
};
exports.queryData = function(req, res, next) {
try {
req.dbService.criteriaQuery(req.criteria.connectionName, req.criteria, function(err, result) {
if (err) {
logger.error('Error occured when executing SQL: ', err.message);
logger.error('Origin SQL:\n', req.criteria.query);
logger.error('Origin Params:\n', req.criteria.params);
logger.error('Origin Filters:\n', req.criteria.filters);
logger.error('Final Executed SQL:\n', err.sql);
res.jsonp({
success: false,
message: '<div style="width: 100%; text-align: left">'
+ '<div class="ui-state-error">' + err.message + ' CODE: ' + err.code + '</div>'
+ '<br>--- Debug Info ---<br><br>'
+ '<span class="ui-state-error">Origin SQL</span><br>'
+ escapeHTML(req.criteria.query)
+ '<br><br><span class="ui-state-error">Origin Params</span><br>'
+ JSON.stringify(req.criteria.params)
+ '<br><br><span class="ui-state-error">Final Executed SQL</span><br>'
+ escapeHTML(err.sql)
+ '</div>'
});
return;
}
res.jsonp({
success: true,
data: result
});
});
} catch(err) {
logger.error('Error occured when executing SQL:\n', err.message);
logger.error('Origin SQL:\n', req.criteria.query);
logger.error('Origin Params:\n', req.criteria.params);
res.jsonp({
success: false,
message: err.message
});
return;
}
};
exports.exportData = function(req, res, next) {
var format = req.criteria.exportFormat || "xlsx";
var formatter = DataFormatter[ format ];
if (typeof formatter !== 'function') {
return next(new Error('Invalid format: ' + format));
}
req.criteria.format = 'object';
req.dbService.criteriaQuery(req.criteria.connectionName, req.criteria, function(err, result) {
if (err) {
return next(err);
}
result = formatter(result.rows, req.criteria.colModel, req.criteria.title);
var name = encodeURIComponent(req.criteria.title
? req.criteria.title
: (req.criteria.sourceId + '_' + (new Date()).getTime())
)
+ '.' + format;
res.set({
"Content-Type": "application/octet-stream",
"Content-Disposition": "attachment; filename=" + name
});
res.send(result);
});
};
var xlsx = require( "node-xlsx" );
var fs = require( "fs" );
var DataFormatter = {
xlsx: function( result, colModel, title ) {
colModel = colModel || [];
var header = [];
// generate header;
for ( var i = 0; i < colModel.length; i++ ) {
header.push( colModel[ i ].name );
}
// generate data;
var data = [];
if (title) {
data.push(['', title]);
}
data.push(header);
var row;
var colName;
var rd;
var val;
for ( var i = 0; i < result.length; i++ ) {
rd = result[i];
row = [];
for ( var j = 0; j < colModel.length; j++ ) {
colName = colModel[j].id;
val = rd[colName];
if (typeof val === 'undefined') {
val = rd[colName.toUpperCase()];
}
if (val instanceof Date) {
val = val.toISOString();
}
row.push(val);
}
data.push( row );
}
title = (title || 'sheet1').substring(0, 30)
.replace(/\\|\/|\?|\*/g, '_')
.replace(/\[/g, '(')
.replace(/\]/g, ')')
return xlsx.build( [ {
name: title,
data: data
} ] );
},
csv: function(result, colModel) {
return result;
},
json: function( result, colModel ) {
return result;
},
xml: function( result, colModel ) {
return result;
}
};
var ESqlCompiler = require('../esql-compiler');
exports.execute = function(req, res, next) {
var dbConfiguration;
try {
dbConfiguration = JSON.parse(req.body.database);
} catch (e) {
return next(
new Error('Invalid database setting for preview.')
);
}
var MSG = MESSAGE[req.cookies.lang || 'zh-cn'];
var params, procedure;
try {
params = JSON.parse( req.body.params );
procedure = JSON.parse( req.body.procedure );
} catch (e) {
return next(new Error('Invalid params or procedure'));
}
var params2 = {};
for (var i in params) {
params2[i.toUpperCase()] = params[i];
}
params = params2;
if (procedure.escapeHTML !== false) {
for (var i in params) {
params[i] = typeof params[i] === 'string' ? escapeHTML(params[i]) : params[i];
}
}
// Mock server vars for user debug
var serverVars = req.body.serverVars;
try {
serverVars = JSON.parse(decodeURIComponent(serverVars));
} catch(e) {
serverVars = {};
}
// Append Session Variables
var vbs = req.session._variables;
if (vbs) {
for (var i in vbs) {
serverVars[i.toUpperCase()] = vbs[i];
}
}
var f;
procedure.type = procedure.type || 'sql';
if (procedure.type === 'sql') {
var beforeSql;
var compiledSqlFunc;
var afterSql;
try {
beforeSql = procedure.beforeSql || 'done()';
beforeSql = beforeSql.replace( pattern.variable, function( s ) {
return /\@\w+(\.\w+)*\@/.test(s)
? 'Enhancer.getVariable("' + s.replace(/\@/g, '').toUpperCase() + '")'
: 'parameters["' + s.replace(/\@/g, '').toUpperCase() + '"]';
}).replace(/require\(\s*(\'|\")@custom\//g, function(s, $1) {
return 'requireNocache(' + $1 + customModuleBase;
});
beforeSql = 'function(parameters, serverVariables, Enhancer, done) {\n'
+ beforeSql + '\n}';
beforeSql = eval( "(" + beforeSql + ")" );
} catch ( err ) {
return res.jsonp({
success: false,
message: MSG.BEFORE_SQL_SYNTAX_ERR + err.message,
debugInfo: JSON.stringify(err)
});
}
try {
compiledSqlFunc = ESqlCompiler.compile(procedure.func);
} catch (err) {
return res.jsonp({
success: false,
message: MSG.SQL_COMPILE_ERR + err.message,
debugInfo: err.message
});
}
try {
if (procedure.afterSql) {
afterSql = procedure.afterSql.replace( pattern.variable, function( s ) {
return /\@\w+(\.\w+)*\@/.test(s)
? 'Enhancer.getVariable("' + s.replace(/\@/g, '').toUpperCase() + '")'
: 'parameters["' + s.replace(/\@/g, '').toUpperCase() + '"]';
}).replace(/require\(\s*(\'|\")@custom\//g, function(s, $1) {
return 'requireNocache(' + $1 + customModuleBase;
});
afterSql = 'function(parameters, serverVariables, Enhancer, done) {\n'
+ afterSql + '\n}';
afterSql = eval( "(" + afterSql + ")" );
}
} catch ( err ) {
return res.jsonp({
success: false,
message: MSG.AFTER_SQL_SYNTAX_ERR + err.message,
debugInfo: JSON.stringify(err)
});
}
f = function(parameters, serverVariables, Enhancer, done) {
beforeSql(parameters, serverVariables, Enhancer, function(err, result, params) {
if (err) {
return done(err);
}
if (result && !result.success) {
return done(null, result);
}
if (params) {
for (var i in params) {
parameters[i.toUpperCase()] = params[i];
}
}
compiledSqlFunc(procedure.connectionName, parameters, serverVariables, Enhancer
, function(err, result) {
if (err) {
if (typeof afterSql === 'function') {
serverVariables['SQL_RESULT'] = {success:false, message: err.message};
serverVariables['SQL_ERROR'] = err;
try {
afterSql(parameters, serverVariables, Enhancer, done);
} catch(ex) {
ex.message = MSG.AFTER_SQL_EXE_ERR + ex.message
+ '. Before SQL error:' + err.message;
done(ex);
}
return;
}
return done(err);
}
if (typeof afterSql === 'function') {
serverVariables['SQL_RESULT'] = result;
try {
afterSql(parameters, serverVariables, Enhancer, done);
} catch(ex) {
ex.message = MSG.AFTER_SQL_EXE_ERR + ex.message;
done(ex);
}
return;
}
done(null, result);
});
});
}
} else if (procedure.type === 'proc') {
var funcStr;
try {
funcStr = procedure.func.replace( pattern.variable, function( s ) {
return /\@\w+(\.\w+)*\@/.test(s)
? 'Enhancer.getVariable("' + s.replace(/\@/g, '').toUpperCase() + '")'
: 'parameters["' + s.replace(/\@/g, '').toUpperCase() + '"]';
}).replace(/require\(\s*(\'|\")@custom\//g, function(s, $1) {
return 'requireNocache(' + $1 + customModuleBase;
});
funcStr = 'function(parameters, serverVariables, Enhancer, done) {'
+ funcStr + '\n}';
f = eval( "(" + funcStr + ")" );
} catch ( err ) {
return res.jsonp({
success: false,
message: MSG.PROCEDURE_SYNTAX_ERR + err.message,
debugInfo: JSON.stringify(err)
});
}
}
// Set timer
var completed = false;
try {
// Mock enhancer;
var Enhancer = mocker.mockEnhancer(dbConfiguration, serverVars, req, res);
// If in official environment, a timeout limitation must be set.
var timeout = config.isOfficialEnv ? 3000 : procedure.timeout;
if (timeout) {
setTimeout(function() {
if (completed) {
return;
}
completed = true;
res.jsonp({
success: false,
message: MSG.TIMEOUT
});
res.jsonp = res.send = res.end = function() {
logger.log('The request has been timeout and the response has been ended.');
}
}, timeout);
}
var t1 = new Date().getTime();
f( params, serverVars, Enhancer, function(err, result) {
if (req.conn) {
req.conn.destroy();
}
if (completed) {
return;
}
completed = true;
var t2 = new Date().getTime();
logger.debug('PROCEDURE COST: ', (t2 - t1) + 'ms' );
if (err) {
// Find error tips
var errorMessage;
for (i in procedure.errMsg) {
if (err.code === i || ~err.message.indexOf(i)) {
errorMessage = procedure.errMsg[i];
break;
}
}
errorMessage = errorMessage
|| (procedure.errMsg.default || MSG.PROCEDURE_EXE_ERR)
+ 'Reason: ' + err.message;
res.jsonp({
success: false,
message: errorMessage + '<br>Origin Message: ' + err.message,
errorCode: err.code,
debugInfo: err.debugInfo
});
return;
}
result = result || serverVars['SQL_RESULT'] || {};
result.message = result.message
|| (result.success ? procedure.succMsg : procedure.errMsg.default)
|| (result.success ? MSG.OP_SUCC : MSG.OP_FAILED);
res.jsonp(result);
} );
} catch ( err ) {
if (completed) {
return;
}
completed = true;
return res.jsonp({
success: false,
message: MSG.PROCEDURE_EXE_ERR + err.message.replace(customModuleBase, '@custom/'),
errInfo: JSON.stringify(err)
});
}
};