@sap/async-xsjs 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +37 -0
  3. package/README.md +445 -0
  4. package/differences.md +162 -0
  5. package/docs/$.Application.html +262 -0
  6. package/docs/$.Session.html +674 -0
  7. package/docs/$.db.CallableStatement.html +2524 -0
  8. package/docs/$.db.Connection.html +511 -0
  9. package/docs/$.db.ParameterMetaData.html +805 -0
  10. package/docs/$.db.PreparedStatement.html +1796 -0
  11. package/docs/$.db.ResultSet.html +1308 -0
  12. package/docs/$.db.ResultSetMetaData.html +800 -0
  13. package/docs/$.db.SQLException.html +259 -0
  14. package/docs/$.db.html +773 -0
  15. package/docs/$.hdb.ColumnMetadata.html +438 -0
  16. package/docs/$.hdb.Connection.html +663 -0
  17. package/docs/$.hdb.ProcedureResult.html +280 -0
  18. package/docs/$.hdb.ResultSet.html +324 -0
  19. package/docs/$.hdb.ResultSetIterator.html +315 -0
  20. package/docs/$.hdb.ResultSetMetaData.html +259 -0
  21. package/docs/$.hdb.SQLException.html +259 -0
  22. package/docs/$.hdb.html +557 -0
  23. package/docs/$.html +471 -0
  24. package/docs/$.jobs.Job.html +783 -0
  25. package/docs/$.jobs.JobLog.html +380 -0
  26. package/docs/$.jobs.JobSchedules.html +852 -0
  27. package/docs/$.jobs.html +238 -0
  28. package/docs/$.net.Destination.html +304 -0
  29. package/docs/$.net.Mail.Part.html +510 -0
  30. package/docs/$.net.Mail.html +504 -0
  31. package/docs/$.net.SMTPConnection.html +347 -0
  32. package/docs/$.net.html +749 -0
  33. package/docs/$.net.http.Client.html +562 -0
  34. package/docs/$.net.http.Destination.html +237 -0
  35. package/docs/$.net.http.Request.html +567 -0
  36. package/docs/$.net.http.html +292 -0
  37. package/docs/$.security.AntiVirus.html +361 -0
  38. package/docs/$.security.Store.html +636 -0
  39. package/docs/$.security.crypto.html +414 -0
  40. package/docs/$.security.html +247 -0
  41. package/docs/$.security.x509.html +373 -0
  42. package/docs/$.text.analysis.Session.html +983 -0
  43. package/docs/$.text.analysis.html +242 -0
  44. package/docs/$.text.html +246 -0
  45. package/docs/$.text.mining.Session.html +2018 -0
  46. package/docs/$.text.mining.html +242 -0
  47. package/docs/$.trace.html +525 -0
  48. package/docs/$.util.SAXParser.html +955 -0
  49. package/docs/$.util.Zip.html +474 -0
  50. package/docs/$.util.codec.html +414 -0
  51. package/docs/$.util.compression.html +357 -0
  52. package/docs/$.util.html +325 -0
  53. package/docs/$.util.sql.html +290 -0
  54. package/docs/$.web.Body.html +333 -0
  55. package/docs/$.web.EntityList.html +296 -0
  56. package/docs/$.web.TupelList.html +496 -0
  57. package/docs/$.web.WebEntityRequest.html +393 -0
  58. package/docs/$.web.WebEntityResponse.html +392 -0
  59. package/docs/$.web.WebRequest.html +560 -0
  60. package/docs/$.web.WebResponse.html +609 -0
  61. package/docs/$.web.html +246 -0
  62. package/docs/Copyright-SAP.html +39 -0
  63. package/docs/Disclaimer-SAP.html +55 -0
  64. package/docs/index.html +232 -0
  65. package/docs/styles/jsdoc-default.css +382 -0
  66. package/lib/AppConfig.js +36 -0
  67. package/lib/AuditLogger.js +41 -0
  68. package/lib/cacert.js +26 -0
  69. package/lib/ctypes.js +153 -0
  70. package/lib/destinations/dest-provider.js +57 -0
  71. package/lib/index.js +235 -0
  72. package/lib/jobs/Action.js +40 -0
  73. package/lib/jobs/Job.js +100 -0
  74. package/lib/jobs/JobManager.js +150 -0
  75. package/lib/jobs/JobsRuntime.js +133 -0
  76. package/lib/jobs/SqlScriptJobRunner.js +36 -0
  77. package/lib/jobs/XsjsJobRunner.js +78 -0
  78. package/lib/jobs/index.js +11 -0
  79. package/lib/logging.js +16 -0
  80. package/lib/middleware.js +125 -0
  81. package/lib/odata/ODataService.js +125 -0
  82. package/lib/odata/index.js +7 -0
  83. package/lib/odata/service-factory.js +26 -0
  84. package/lib/passport-noauth.js +17 -0
  85. package/lib/routes.js +115 -0
  86. package/lib/runtime.js +740 -0
  87. package/lib/sandbox.js +40 -0
  88. package/lib/utils/XsJsFunctionRunner.js +57 -0
  89. package/lib/utils/XsJsLibFunctionRunner.js +57 -0
  90. package/lib/utils/buffer-utils.js +77 -0
  91. package/lib/utils/compression-utils.js +14 -0
  92. package/lib/utils/date-utils.js +104 -0
  93. package/lib/utils/errors/HttpError.js +20 -0
  94. package/lib/utils/errors/wrap-app-error.js +18 -0
  95. package/lib/utils/index.js +17 -0
  96. package/lib/utils/xs-function-runner.js +51 -0
  97. package/lib/utils/xs-types.js +21 -0
  98. package/lib/utils/xspath.js +36 -0
  99. package/lib/utils/xsstack.js +28 -0
  100. package/lib/views/error.html +28 -0
  101. package/lib/xsjs/Application.js +28 -0
  102. package/lib/xsjs/Locale.js +53 -0
  103. package/lib/xsjs/Session.js +31 -0
  104. package/lib/xsjs/constants.js +71 -0
  105. package/lib/xsjs/db/common/DbBase.js +85 -0
  106. package/lib/xsjs/db/common/DbOptions.js +163 -0
  107. package/lib/xsjs/db/common/arguments-validation.js +102 -0
  108. package/lib/xsjs/db/common/connection.js +12 -0
  109. package/lib/xsjs/db/common/enums.js +93 -0
  110. package/lib/xsjs/db/common/execute-batch.js +38 -0
  111. package/lib/xsjs/db/common/parse-time.js +139 -0
  112. package/lib/xsjs/db/dbapi/CallableStatement.js +192 -0
  113. package/lib/xsjs/db/dbapi/Connection.js +78 -0
  114. package/lib/xsjs/db/dbapi/DB.js +39 -0
  115. package/lib/xsjs/db/dbapi/ParameterMetaData.js +118 -0
  116. package/lib/xsjs/db/dbapi/PreparedStatement.js +78 -0
  117. package/lib/xsjs/db/dbapi/ResultSet.js +220 -0
  118. package/lib/xsjs/db/dbapi/ResultSetMetaData.js +116 -0
  119. package/lib/xsjs/db/dbapi/Statement.js +514 -0
  120. package/lib/xsjs/db/dbapi/conversions.js +113 -0
  121. package/lib/xsjs/db/dbapi/fetch-rows.js +32 -0
  122. package/lib/xsjs/db/hdbapi/Connection.js +525 -0
  123. package/lib/xsjs/db/hdbapi/HDB.js +32 -0
  124. package/lib/xsjs/db/hdbapi/ResultSetIterator.js +40 -0
  125. package/lib/xsjs/db/hdbapi/convert.js +77 -0
  126. package/lib/xsjs/db/hdbapi/table-string-parser.js +52 -0
  127. package/lib/xsjs/db/index.js +4 -0
  128. package/lib/xsjs/index.js +13 -0
  129. package/lib/xsjs/jobs/Job.js +228 -0
  130. package/lib/xsjs/jobs/Jobs.js +11 -0
  131. package/lib/xsjs/jobs/Logs.js +127 -0
  132. package/lib/xsjs/jobs/Schedule.js +110 -0
  133. package/lib/xsjs/jobs/Schedules.js +108 -0
  134. package/lib/xsjs/net/Destination.js +43 -0
  135. package/lib/xsjs/net/http/Client.js +220 -0
  136. package/lib/xsjs/net/http/HTTP.js +72 -0
  137. package/lib/xsjs/net/index.js +5 -0
  138. package/lib/xsjs/net/smtp/Mail.js +38 -0
  139. package/lib/xsjs/net/smtp/Part.js +30 -0
  140. package/lib/xsjs/net/smtp/SMTPConnection.js +39 -0
  141. package/lib/xsjs/net/smtp/index.js +18 -0
  142. package/lib/xsjs/net/smtp/nodemailer-util.js +77 -0
  143. package/lib/xsjs/require.js +39 -0
  144. package/lib/xsjs/security/AntiVirus.js +31 -0
  145. package/lib/xsjs/security/Store.js +119 -0
  146. package/lib/xsjs/security/crypto.js +23 -0
  147. package/lib/xsjs/security/index.js +5 -0
  148. package/lib/xsjs/security/x509.js +12 -0
  149. package/lib/xsjs/text/analysis/Session.js +128 -0
  150. package/lib/xsjs/text/index.js +30 -0
  151. package/lib/xsjs/text/mining/Session.js +82 -0
  152. package/lib/xsjs/trace/trace.js +41 -0
  153. package/lib/xsjs/util/SAXParser.js +174 -0
  154. package/lib/xsjs/util/Zip.js +220 -0
  155. package/lib/xsjs/util/codec.js +33 -0
  156. package/lib/xsjs/util/compression.js +24 -0
  157. package/lib/xsjs/util/index.js +22 -0
  158. package/lib/xsjs/web/BasicWebEntity.js +41 -0
  159. package/lib/xsjs/web/EntityList.js +11 -0
  160. package/lib/xsjs/web/TupelLists/CookiesTupelList.js +47 -0
  161. package/lib/xsjs/web/TupelLists/HeadersTupelList.js +55 -0
  162. package/lib/xsjs/web/TupelLists/ParametersTupelList.js +83 -0
  163. package/lib/xsjs/web/TupelLists/TupelListBase.js +45 -0
  164. package/lib/xsjs/web/WebBody.js +135 -0
  165. package/lib/xsjs/web/WebEntityRequest.js +40 -0
  166. package/lib/xsjs/web/WebEntityResponse.js +26 -0
  167. package/lib/xsjs/web/WebRequest.js +209 -0
  168. package/lib/xsjs/web/WebResponse.js +183 -0
  169. package/lib/xsjs/web/index.js +4 -0
  170. package/lib/xsjs/web/utils/HeadersParser.js +53 -0
  171. package/lib/xsjs/web/utils/HttpRequestParser.js +93 -0
  172. package/lib/xsjs/web/utils/MultipartParser.js +163 -0
  173. package/lib/xsjs/web/utils/MultipartResponseBuilder.js +73 -0
  174. package/lib/xsjs/web/utils/SetCookieParser.js +32 -0
  175. package/lib/xsjslib/TextBundleWrapper.js +46 -0
  176. package/lib/xsjslib/index.js +11 -0
  177. package/npm-shrinkwrap.json +11540 -0
  178. package/package.json +84 -0
package/lib/index.js ADDED
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ var bodyParser = require('body-parser');
4
+ var compress = require('compression');
5
+ var cookieParser = require('cookie-parser');
6
+ var express = require('express');
7
+ var http = require('http');
8
+ var _ = require('lodash');
9
+ var passport = require('passport');
10
+ var connOptions = require('@sap/hdbext').connectionOptions;
11
+ var xssec = require('@sap/xssec');
12
+ var createDsrMiddleware = require('@sap/e2e-trace').createDsrMiddleware;
13
+ var AuditLogger = require('./AuditLogger');
14
+ var AnonymousStrategy = require('./passport-noauth');
15
+ var middleware = require('./middleware');
16
+ var routes = require('./routes');
17
+ var runtime = require('./runtime');
18
+ var logging = require('@sap/logging');
19
+ var logger = require('./logging').logger;
20
+ var odata = require('./odata');
21
+ var jobs = require('./jobs');
22
+ var cacert = require('./cacert');
23
+
24
+ exports = module.exports = async function (options) {
25
+ logger.info('@sap/xsjs version: %s, Node.js version: %s', require('../package.json').version, process.version);
26
+ options = _.cloneDeep(options);
27
+ checkOptions(options);
28
+
29
+ cacert.loadCertificates(options);
30
+
31
+ if (options.hana) {
32
+ _.defaultsDeep(options, {
33
+ hana: connOptions.getGlobalOptions()
34
+ });
35
+ }
36
+
37
+ // bootstrap runtime
38
+ var rt = await runtime.createRuntime(options);
39
+
40
+ // configure passport
41
+ var passportStrategies = [];
42
+
43
+ var JWT = 'JWT';
44
+ if (options.uaa) {
45
+ passport.use(JWT, new xssec.JWTStrategy(options.uaa));
46
+ passportStrategies.push(JWT);
47
+ }
48
+
49
+ if (options.anonymous) {
50
+ passport.use(new AnonymousStrategy());
51
+ passportStrategies.push('anonymous');
52
+ }
53
+
54
+ // configure express
55
+ var app = createApp();
56
+ options.auditLog && app.set('auditLog', new AuditLogger(options.auditLog));
57
+ app.use(logging.middleware({ appContext: require('./logging').appContext }));
58
+ app.use(createDsrMiddleware());
59
+ app.set('query parser', 'simple');
60
+ app.set('runtime', rt);
61
+
62
+ app.use(function (req, res, next) {
63
+ if (req.method === 'TRACE') {
64
+ return res.status(403).send('HTTP TRACE Method is not allowed');
65
+ }
66
+ next();
67
+ });
68
+
69
+ if (rt.get('compression')) {
70
+ app.use(compress());
71
+ }
72
+ app.use(cookieParser());
73
+ app.use(middleware.locale);
74
+
75
+ app.use(passport.initialize());
76
+ app.use(function (req, res, next) {
77
+ passport.authenticate(passportStrategies, handleAuthenticate(req, res, next))(req, res, next);
78
+ });
79
+ app.use(middleware.urlRewrite(rt));
80
+
81
+ // redirect
82
+ rt.redirectRules.forEach(function (rule) {
83
+ app.get(rule.pathname, function (req, res) {
84
+ res.redirect(rule.status || 302, rule.location);
85
+ });
86
+ });
87
+
88
+ addXsjsHandlers(app, options, rt);
89
+
90
+ addOdataHandlers(app, options, rt);
91
+
92
+ addJobHandlers(app, options, rt);
93
+
94
+ // static content
95
+ rt.staticDirectories.forEach(function (staticDirectory) {
96
+ app.use(staticDirectory.pathname, express.static(staticDirectory.dirname));
97
+ });
98
+
99
+ // catch 404 and forward to error handler
100
+ app.use(middleware.notFound);
101
+
102
+ // error handlers
103
+ app.use(routes.error);
104
+
105
+ return app;
106
+ };
107
+
108
+ function handleAuthenticate(req, res, next) {
109
+ return function (err, user, info, statusCodes) {
110
+ if (err) { return next(err); }
111
+
112
+ if (user) {
113
+ return req.logIn(user, { session: false }, function (err) {
114
+ if (err) { return next(err); }
115
+ req.authInfo = info;
116
+ next();
117
+ });
118
+ }
119
+
120
+ var jwtStrategyStatusCode = statusCodes[0];
121
+
122
+ auditLogAuthFailed(req, function (err) {
123
+ if (err) { return next(err); }
124
+ sendAuthFailed(res, jwtStrategyStatusCode);
125
+ });
126
+ };
127
+ }
128
+
129
+ function addXsjsHandlers(app, options, rt) {
130
+ var xsjsPaths = _.map(Object.keys(rt.xsjs), function addWildCard(path) {
131
+ // wildcard needs to be mapped to support $.request.queryPath
132
+ return path + '*';
133
+ });
134
+ if (xsjsPaths.length > 0) {
135
+ app.all(xsjsPaths, middleware.anyBody(rt, options), middleware.xsjs(rt), routes.xsjs(rt));
136
+ }
137
+ }
138
+
139
+ function addOdataHandlers(app, options, rt) {
140
+ var odataServices = odata.createServices(rt);
141
+ odataServices.forEach(function (odataService) {
142
+ if (!isValidHanaSettings(options)) {
143
+ app.use(odataService.getRootUriPath(), function (req, res, next) {
144
+ next(new Error('Invalid HANA settings provided. OData services are disabled.'));
145
+ });
146
+ } else {
147
+ var odataApp = createApp();
148
+ odataApp.all('*', middleware.anyBody(rt, options), routes.xsodata(rt, odataService));
149
+ app.use(odataService.getRootUriPath(), odataApp);
150
+ }
151
+ });
152
+
153
+ app.clearODataCache = function (tenant) {
154
+ odataServices.forEach(function (svc) {
155
+ svc.clearCache(tenant);
156
+ });
157
+ };
158
+ }
159
+
160
+ function addJobHandlers(app, options, rt) {
161
+ var jobsRt = new jobs.JobsRuntime(rt);
162
+ var validJobs = jobsRt.getValidJobs();
163
+ if (validJobs.length) {
164
+ if (isValidHanaSettings(options)) {
165
+ jobsRt.registerAllJobs();
166
+
167
+ validJobs.forEach(function (job) {
168
+ app.post(job.urlPath, bodyParser.json(), routes.startJob(jobsRt, options));
169
+ });
170
+ } else {
171
+ logger.error('Jobs execution requires valid HANA configuration.');
172
+ }
173
+ }
174
+ }
175
+
176
+ function checkOptions(options) {
177
+ if (!isValidHanaSettings(options)) {
178
+ logger.warn('No HANA credentials provided. DB access and OData services will be disabled.');
179
+ }
180
+ validateOptionalBoolean(options, 'xsApplicationUser');
181
+ validateOptionalBoolean(options, 'anonymous');
182
+ if (!options.anonymous && !options.uaa) {
183
+ throw new Error('No UAA configuration is provided in non-anonymous mode.');
184
+ }
185
+ if (!options.auditLog) {
186
+ logger.warn('No Audit log configuration provided. Audit logging will be disabled.');
187
+ }
188
+ if (options.auditLog && options.auditLog.logToConsole) {
189
+ logger.warn('Audit log messages will be written to console.');
190
+ }
191
+ if (!options.uaa) {
192
+ logger.warn('No UAA configuration provided. JWT token authentication disabled.');
193
+ }
194
+ if (!options.mail) {
195
+ logger.warn('No Mail options provided. $.net.Mail and $.net.SMTPConnection will be disabled.');
196
+ }
197
+ if (!options.jobs) {
198
+ logger.warn('No Jobs options provided. $.jobs will be disabled.');
199
+ }
200
+ if (!options.secureStore) {
201
+ logger.warn('Secure store not configured. $.security.Store will be disabled.');
202
+ }
203
+ }
204
+
205
+ function isValidHanaSettings(options) {
206
+ return options.hana &&
207
+ ((options.hana.user && options.hana.password) ||
208
+ (options.hana.clientid && options.hana.clientsecret));
209
+ }
210
+
211
+ function validateOptionalBoolean(options, prop) {
212
+ var value = options[prop];
213
+ if (value !== undefined && typeof value !== 'boolean') {
214
+ throw new Error(prop + ' option should be a boolean');
215
+ }
216
+ }
217
+
218
+ function auditLogAuthFailed(req, cb) {
219
+ var auditLog = req.app.get('auditLog');
220
+ if (!auditLog) {
221
+ return cb(null);
222
+ }
223
+ auditLog.logSecurityEvent(req, 'Failed login attempt', 'UNKNOWN', cb);
224
+ }
225
+
226
+ function sendAuthFailed(res, statusCode) {
227
+ statusCode === 401 && res.setHeader('WWW-Authenticate', 'Bearer realm="XSJS Compatibility Layer"');
228
+ return res.status(statusCode).end(http.STATUS_CODES[statusCode]);
229
+ }
230
+
231
+ function createApp() {
232
+ var app = express();
233
+ app.disable('x-powered-by');
234
+ return app;
235
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var _ = require('lodash');
4
+ var utils = require('../utils');
5
+ var fs = require('fs');
6
+ var fsPath = require('path');
7
+
8
+ module.exports = Action;
9
+
10
+ function Action(actionStr, rootDirs) {
11
+ var parts = actionStr.match(/^([^:]+)(:[^:]+\.xsjs)?:{2}(.+)$/);
12
+ if (!parts) {
13
+ throw new Error('Invalid action. Expected "<package>:<XSJS_Service>.xsjs::function", found: "' + actionStr + '"');
14
+ }
15
+
16
+ if (!parts[2]) {
17
+ fileExists(rootDirs, parts[1]) ? this.filename = parts[1] : this.packagename = parts[1];
18
+ } else {
19
+ this.packagename = parts[1];
20
+ this.filename = parts[2].slice(1);
21
+ }
22
+ this.funcname = parts[3];
23
+ }
24
+
25
+ Action.prototype.isJavaScript = function () {
26
+ return !!this.filename;
27
+ };
28
+
29
+ Action.prototype.getScriptPath = function () {
30
+ return utils.toPath(this.packagename, this.filename);
31
+ };
32
+
33
+ function fileExists(dirs, pathname) {
34
+ var dir = _.find(dirs, function (dir) {
35
+ var path = fsPath.join(dir, pathname);
36
+ return fs.existsSync(path) && fs.lstatSync(path).isFile();
37
+ });
38
+
39
+ return !!dir;
40
+ }
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ var assert = require('assert');
4
+ var _ = require('lodash');
5
+ var Action = require('./Action');
6
+
7
+ module.exports = Job;
8
+
9
+ function Job (xsjob, urlPath, rootDirs) {
10
+ this._validateXsjob(xsjob);
11
+
12
+ if (!urlPath) {
13
+ throw new TypeError('urlPath is required, provide valid value');
14
+ }
15
+
16
+ this.action = new Action(xsjob.action, rootDirs);
17
+ this.urlPath = urlPath;
18
+ this.active = true;
19
+
20
+ this._xsjob = xsjob;
21
+ }
22
+
23
+ Job.prototype.toSchedulerJob = function(host, jobsCallbackUrl) {
24
+ if (!host) {
25
+ throw new TypeError('Valid host is expected');
26
+ }
27
+ if (!jobsCallbackUrl) {
28
+ throw new TypeError('Jobs callback URL expected');
29
+ }
30
+
31
+ var schedules = this._xsjob.schedules.map(function(schedule) {
32
+ return {
33
+ description: schedule.description,
34
+ cron: schedule.xscron,
35
+ data: schedule.parameter,
36
+ active: true
37
+ };
38
+ });
39
+
40
+ var jobUrl = _.trimEnd(jobsCallbackUrl, '/') + '/' + _.trimStart(this.urlPath, '/');
41
+ return {
42
+ name: Job.buildName(host, this.urlPath),
43
+ description: this._xsjob.description,
44
+ action: jobUrl,
45
+ schedules: schedules,
46
+ httpMethod: 'POST',
47
+ active: true
48
+ };
49
+ };
50
+
51
+ Job.prototype._validateXsjob = function(xsjob) {
52
+ if (!xsjob) {
53
+ throw new TypeError('xsjob is required, provide valid value');
54
+ }
55
+
56
+ if (!xsjob.action) {
57
+ throw new TypeError('Missing action property');
58
+ }
59
+
60
+ var schedules = xsjob.schedules;
61
+ if (!Array.isArray(schedules) || schedules.length === 0) {
62
+ throw new TypeError('Missing job schedules');
63
+ }
64
+
65
+ for (var i = 0; i < schedules.length; i++) {
66
+ if (!schedules[i].xscron) {
67
+ throw new TypeError('Missing xscron for schedule [' + i + ']');
68
+ }
69
+ }
70
+ };
71
+
72
+ Job.buildName = function(hostName, jobPath) {
73
+ assert(hostName, 'valid hostname expected');
74
+ assert(jobPath, 'Job path expected');
75
+
76
+ var host = (hostName.charAt(hostName.length - 1) === '/') ? hostName : (hostName + '/') ;
77
+ var jobName = host + jobPathToName(jobPath);
78
+
79
+ return jobName.replace(/[^a-zA-Z0-9._]/g, '_');
80
+ };
81
+
82
+ function jobPathToName(jobPath) {
83
+ assert(jobPath, 'valid path expected');
84
+
85
+ var path = (jobPath.charAt(0) === '/') ? jobPath.substr(1) : jobPath;
86
+ var segments = path.split('/');
87
+
88
+ var fileName = segments.pop();
89
+ if (segments.length === 0) {
90
+ return fileName;
91
+ }
92
+ var packName = segments.join('.');
93
+
94
+ if (fileName.indexOf('.') > -1) {
95
+ return packName + '_' + fileName;
96
+ }
97
+
98
+ return packName + '.' + fileName;
99
+ }
100
+
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ var _ = require('lodash');
4
+ var assert = require('assert');
5
+ var util = require('util');
6
+ var jobsclient = require('@sap/jobs-client');
7
+ var EventEmitter = require('events').EventEmitter;
8
+
9
+ var XsjsJobRunner = require('./XsjsJobRunner');
10
+ var SqlScriptJobRunner = require('./SqlScriptJobRunner');
11
+
12
+ module.exports = JobManager;
13
+
14
+
15
+ util.inherits(JobManager, EventEmitter);
16
+
17
+ /**
18
+ * @param {Object} jobServiceConfig - required, job scheduler config
19
+ */
20
+ function JobManager(jobServiceConfig) {
21
+ assert(_.isObject(jobServiceConfig), 'valid job scheduler config object required');
22
+ assert(_.isString(jobServiceConfig.url), 'job sceduler config should contain valid "url" property');
23
+
24
+ this._jc = jobServiceConfig;
25
+
26
+ EventEmitter.call(this);
27
+ }
28
+
29
+ /**
30
+ * Registers all jobs in Job Scheduler service asynchronously. Does not throw errors, 'registered' event is triggered with
31
+ * error parameter having value <code>null</code> in case registration was successful or valid error otherwise
32
+ */
33
+ JobManager.prototype.registerAllJobs = function (jobs, appConfig) {
34
+ assert(_.isArray(jobs), 'Valid jobs array expected');
35
+ assert(appConfig, 'Valid application config expected');
36
+
37
+ var scheduler = new jobsclient.Scheduler(this._schedulerOptions());
38
+
39
+ var self = this;
40
+ jobs.forEach(function (job) {
41
+ var jobsCallbackUrl = self._jc.jobsCallbackUrl || appConfig.url;
42
+ var scJob = job.toSchedulerJob(appConfig.host, jobsCallbackUrl);
43
+ var options = {
44
+ name: scJob.name,
45
+ job: scJob
46
+ };
47
+
48
+ scheduler.upsertJob(options, function (error, body) {
49
+ if (!error) { job.id = body._id; }
50
+ self.emit('register', error, job);
51
+ });
52
+ });
53
+ };
54
+
55
+ /**
56
+ * Starts new job asynchronously, when job finished an event will be triggered
57
+ * @param rt xsjs runtime
58
+ * @param job the job to run
59
+ * @param request http request object
60
+ * @throws in case some of parameters is invalid
61
+ */
62
+ JobManager.prototype.startJobAsync = async function (rt, job, request) {
63
+ assert(rt, 'Valid xsjs runtime object expected');
64
+ assert(job, 'Valid job object expected');
65
+ assert(request, 'Valid request object expected');
66
+
67
+ var inputParams = this._parseInput(request);
68
+ var jobRunDetails = this._getJobRunDetails(request, job);
69
+
70
+ var runnable = null;
71
+ if (job.action.isJavaScript()) {
72
+ runnable = await this._createXsjsRunnable(rt, job, request, jobRunDetails.runId);
73
+ } else {
74
+ runnable = this._createSqlProcRunnable(rt.get('hanaDbOptions').forRequest(request));
75
+ }
76
+
77
+ this._startJob(runnable, job, inputParams, jobRunDetails);
78
+ };
79
+
80
+ JobManager.prototype._startJob = function (runnable, job, inputParams, jobRunDetails) {
81
+ var self = this;
82
+
83
+ process.nextTick(async function() {
84
+ try {
85
+ await runnable(job, inputParams);
86
+ self.emit('job-finished', null, job, jobRunDetails);
87
+ self._sendJobFinished(null, job, jobRunDetails);
88
+ } catch (ex) {
89
+ self.emit('job-finished', ex, job, jobRunDetails);
90
+ self._sendJobFinished(ex, job, jobRunDetails);
91
+ }
92
+ });
93
+ };
94
+
95
+
96
+ JobManager.prototype._sendJobFinished = function (err, job, jobRunDetails) {
97
+ var message = (!err) ? 'OK' : ('Job run failed, error: ' + err.message);
98
+ var options = _.extend({}, jobRunDetails, {
99
+ data: {
100
+ success: !err,
101
+ message: message
102
+ }
103
+ });
104
+
105
+ var self = this;
106
+ var scheduler = new jobsclient.Scheduler(this._schedulerOptions());
107
+ scheduler.updateJobRunLog(options, function(err) {
108
+ self.emit('status-update', err, job, jobRunDetails);
109
+ });
110
+ };
111
+
112
+ JobManager.prototype._getJobRunDetails = function(request, job) {
113
+ var jobRunDetails = {
114
+ jobId: request.headers['x-sap-job-id'],
115
+ scheduleId: request.headers['x-sap-job-schedule-id'],
116
+ runId: request.headers['x-sap-job-run-id']
117
+ };
118
+
119
+ assert(jobRunDetails.jobId, 'Expecting job ID to be provided when starting JOB:' + job.name);
120
+ assert(jobRunDetails.scheduleId, 'Expecting job schedule ID to be provided when starting JOB:' + job.name);
121
+ assert(jobRunDetails.runId, 'Expecting job run ID to be provided when starting JOB:' + job.name);
122
+
123
+ return jobRunDetails;
124
+ };
125
+
126
+ JobManager.prototype._schedulerOptions = function () {
127
+ return _.extend(this._jc, {
128
+ baseURL: this._jc.url
129
+ });
130
+ };
131
+
132
+ JobManager.prototype._createXsjsRunnable = async function (rt, job, req, runId) {
133
+ var runner = new XsjsJobRunner(rt);
134
+ await runner.prepare(job, req, runId);
135
+
136
+ return async function xsjsRunnable(job, inputParams) {
137
+ await runner.run(inputParams);
138
+ };
139
+ };
140
+
141
+ JobManager.prototype._createSqlProcRunnable = function (dbReqOptions) {
142
+ return async function SqlScriptRunnable(job, inputParams) {
143
+ var runner = new SqlScriptJobRunner(dbReqOptions);
144
+ await runner.run(job, inputParams);
145
+ };
146
+ };
147
+
148
+ JobManager.prototype._parseInput = function (request) {
149
+ return request.body;
150
+ };
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ var _ = require('lodash');
4
+ var assert = require('assert');
5
+ var format = require('util').format;
6
+ var logging = require('../logging');
7
+ var logger = logging.logger;
8
+ var tracer = logging.tracer;
9
+ var JobManager = require('./JobManager');
10
+ var HttpError = require('../utils/errors/HttpError');
11
+ var wrapAppError = require('../utils/errors/wrap-app-error');
12
+
13
+
14
+ module.exports = JobsRuntime;
15
+
16
+ function JobsRuntime(rt) {
17
+ assert(rt, 'runtime is required for jobs support');
18
+
19
+ var jobsOptions = rt.get('jobs');
20
+ if (!jobsOptions) {
21
+ return;
22
+ }
23
+
24
+ this._rt = rt;
25
+
26
+ var jobManager = this._createJobManager(jobsOptions);
27
+ Object.defineProperty(this, '_jobManager', {
28
+ enumerable: false,
29
+ configurable: false,
30
+ writable: false,
31
+ value: jobManager
32
+ });
33
+ }
34
+
35
+ JobsRuntime.prototype.getValidJobs = function () {
36
+ var jobs = [];
37
+ var rt = this._rt;
38
+
39
+ if (!rt) { return jobs; }
40
+
41
+ Object.keys(rt.xsjobs).forEach(function(key) {
42
+ var job = rt.xsjobs[key];
43
+ if (job.active) {
44
+ jobs.push(job);
45
+ }
46
+ });
47
+
48
+ return jobs;
49
+ };
50
+
51
+ JobsRuntime.prototype.registerAllJobs = function() {
52
+ if (this._jobManager) {
53
+ this._jobManager.registerAllJobs(this.getValidJobs(), this._rt.get('appConfig'));
54
+ }
55
+ };
56
+
57
+ JobsRuntime.prototype.startJobAsync = async function(req) {
58
+ if (!this._jobManager) {
59
+ throw new Error('Cannot start job, job manager not initialized, ' +
60
+ ' check if scheduler service is bound to this application');
61
+ }
62
+ var job = this._rt.getJob(req.path);
63
+ if (!job) {
64
+ throw new HttpError(404, 'Job not found');
65
+ }
66
+
67
+ return await this._jobManager.startJobAsync(this._rt, job, req);
68
+ };
69
+
70
+ JobsRuntime.prototype._createJobManager = function(jobsOptions)
71
+ {
72
+ var jobManager = new JobManager(jobsOptions, this._appConfig);
73
+
74
+ jobManager.on('register', function(error, job) {
75
+ if (error) {
76
+ return logger.error(error, 'Failed to register job "%s"', job.urlPath);
77
+ }
78
+ tracer.info('Job "%s" registered successfully', job.urlPath);
79
+ });
80
+
81
+ jobManager.on('job-finished', function(error, job, jobRunDetails) {
82
+ var runId = jobRunDetails.runId;
83
+ var jobRunName = createJobRunName(job, jobRunDetails);
84
+ if (error) {
85
+ return createJobRunLogger(runId).error(wrapAppError(error), format('Job "%s" execution failed', jobRunName));
86
+ }
87
+ createJobRunTracer(runId).info('Job "%s" finished successfully', job.urlPath);
88
+ });
89
+
90
+ jobManager.on('status-update', function(error, job, jobRunDetails) {
91
+ var runId = jobRunDetails.runId;
92
+ var jobRunName = createJobRunName(job, jobRunDetails);
93
+ if (error) {
94
+ return createJobRunLogger(runId).error(error, 'Failed to update job "%s" status', jobRunName);
95
+ }
96
+ createJobRunTracer(runId).info('Job "%s" status updated successfully', job.urlPath);
97
+ });
98
+
99
+ if (jobsOptions.listener) {
100
+ this._registerJobListener(jobManager, jobsOptions.listener);
101
+ }
102
+
103
+ return jobManager;
104
+ };
105
+
106
+ JobsRuntime.prototype._registerJobListener = function(jobManager, jobListener) {
107
+ if (_.isFunction(jobListener.onRegister)) {
108
+ jobManager.on('register', jobListener.onRegister);
109
+ }
110
+
111
+ if (_.isFunction(jobListener.onJobFinished)) {
112
+ jobManager.on('job-finished', jobListener.onJobFinished);
113
+ }
114
+
115
+ if (_.isFunction(jobListener.onStatusUpdate)) {
116
+ jobManager.on('status-update', jobListener.onStatusUpdate);
117
+ }
118
+ };
119
+
120
+ function createJobRunName(job, jobRunDetails) {
121
+ return format('%s/%s/%s/%s', job.urlPath,
122
+ jobRunDetails.jobId,
123
+ jobRunDetails.scheduleId,
124
+ jobRunDetails.runId);
125
+ }
126
+
127
+ function createJobRunLogger(runId) {
128
+ return logging.appContext.createLogContext({ id: runId }).getLogger(logging.CATEGORY);
129
+ }
130
+
131
+ function createJobRunTracer(runId) {
132
+ return logging.appContext.createLogContext({ id: runId }).getTracer(__filename);
133
+ }
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ var assert = require('assert');
4
+ var hdbext = require('@sap/hdbext');
5
+ var connection = require('../xsjs/db/common/connection');
6
+
7
+ module.exports = SqlScriptJobRunner;
8
+
9
+
10
+ function SqlScriptJobRunner(dbReqOptions) {
11
+ assert(dbReqOptions, 'Valid dbReqOptions expected');
12
+ this._dbReqOptions = dbReqOptions;
13
+ }
14
+
15
+ SqlScriptJobRunner.prototype.run = async function(job, inputParams) {
16
+ var self = this;
17
+ var hanaOptions;
18
+ var hdbClient;
19
+
20
+ var hanaOpts = await this._dbReqOptions.getInstanceOptionsPromise();
21
+ hanaOptions = hanaOpts;
22
+ var client = await connection.connect(hanaOptions);
23
+ hdbClient = client;
24
+ client.setAutoCommit(true);
25
+ var procedure = await hdbext.loadProcedurePromise(client, hanaOptions.schema, self._defineSqlProcName(job.action));
26
+ await procedure(inputParams);
27
+ hdbClient && await hdbClient.close();
28
+ };
29
+
30
+ SqlScriptJobRunner.prototype._defineSqlProcName = function(action) {
31
+ if (!action.packagename) {
32
+ return action.funcname;
33
+ }
34
+
35
+ return action.packagename + '::' + action.funcname;
36
+ };