1 | /*
|
2 | * Licensed under the Apache License, Version 2.0 (the "License");
|
3 | * you may not use this file except in compliance with the License.
|
4 | * You may obtain a copy of the License at
|
5 | *
|
6 | * http://www.apache.org/licenses/LICENSE-2.0
|
7 | *
|
8 | * Unless required by applicable law or agreed to in writing, software
|
9 | * distributed under the License is distributed on an "AS IS" BASIS,
|
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11 | * See the License for the specific language governing permissions and
|
12 | * limitations under the License.
|
13 | */
|
14 |
|
15 | ;
|
16 |
|
17 | const Logger = require('@accordproject/ergo-compiler').Logger;
|
18 | const Util = require('@accordproject/ergo-compiler').Util;
|
19 |
|
20 | /**
|
21 | * <p>
|
22 | * Engine class. Execution of template logic against a request object, returning a response to the caller.
|
23 | * </p>
|
24 | * @class
|
25 | * @public
|
26 | * @memberof module:ergo-engine
|
27 | */
|
28 | class Engine {
|
29 | /**
|
30 | * Create the Engine.
|
31 | */
|
32 | constructor() {
|
33 | this.scripts = {};
|
34 | }
|
35 |
|
36 | /**
|
37 | * Engine kind
|
38 | * @return {string} which kind of engine
|
39 | */
|
40 | kind() {
|
41 | return 'empty';
|
42 | }
|
43 |
|
44 | /**
|
45 | /**
|
46 | * Compile a script for a JavaScript machine
|
47 | * @param {string} script - the script
|
48 | */
|
49 | compileVMScript(script) {
|
50 | throw new Error('[compileVMScript] Cannot execute Engine: instantiate either VMEngine or EvalEngine');
|
51 | }
|
52 |
|
53 | /**
|
54 | * Execute a call in a JavaScript machine
|
55 | * @param {number} utcOffset - UTC Offset for this execution
|
56 | * @param {object} context - global variables to set in the VM
|
57 | * @param {object} script - the initial script to load
|
58 | * @param {object} call - the execution call
|
59 | */
|
60 | runVMScriptCall(utcOffset,context,script,call) {
|
61 | throw new Error('[runVMScriptCall] Cannot execute Engine: instantiate either VMEngine or EvalEngine');
|
62 | }
|
63 |
|
64 | /**
|
65 | * Clear the JavaScript logic cache
|
66 | * @private
|
67 | */
|
68 | clearCacheJsScript() {
|
69 | this.scripts = {};
|
70 | }
|
71 |
|
72 | /**
|
73 | * Compile and cache JavaScript logic
|
74 | * @param {ScriptManager} scriptManager - the script manager
|
75 | * @param {string} contractId - the contract identifier
|
76 | * @return {VMScript} the cached script
|
77 | * @private
|
78 | */
|
79 | cacheJsScript(scriptManager, contractId) {
|
80 | if (!this.scripts[contractId]) {
|
81 | const allJsScripts = scriptManager.getCompiledJavaScript();
|
82 | const script = this.compileVMScript(allJsScripts);
|
83 | this.scripts[contractId] = script;
|
84 | }
|
85 | return this.scripts[contractId];
|
86 | }
|
87 |
|
88 | /**
|
89 | * Trigger a clause, passing in the request object
|
90 | * @param {LogicManager} logic - the logic
|
91 | * @param {string} contractId - the contract identifier
|
92 | * @param {object} contract - the contract data
|
93 | * @param {object} request - the request, a JS object that can be deserialized
|
94 | * using the Composer serializer.
|
95 | * @param {object} state - the contract state, a JS object that can be deserialized
|
96 | * using the Composer serializer.
|
97 | * @param {string} currentTime - the definition of 'now'
|
98 | * @param {object} options to the text generation
|
99 | * @return {Promise} a promise that resolves to a result for the clause
|
100 | */
|
101 | async trigger(logic, contractId, contract, request, state, currentTime, options) {
|
102 | // Set the current time and UTC Offset
|
103 | const now = Util.setCurrentTime(currentTime);
|
104 | const utcOffset = now.utcOffset();
|
105 | const validOptions = options ? options : {
|
106 | options: {
|
107 | '$class': 'org.accordproject.ergo.options.Options',
|
108 | 'wrapVariables': false,
|
109 | 'template': false,
|
110 | }
|
111 | };
|
112 |
|
113 | const validContract = logic.validateContract(contract); // ensure the contract is valid
|
114 | const validRequest = logic.validateInput(request); // ensure the request is valid
|
115 | const validState = logic.validateInput(state); // ensure the state is valid
|
116 |
|
117 | Logger.debug('Engine processing request ' + request.$class + ' with state ' + state.$class);
|
118 |
|
119 | const script = this.cacheJsScript(logic.getScriptManager(), contractId);
|
120 | const callScript = logic.getDispatchCall();
|
121 |
|
122 | const context = {
|
123 | data: validContract.serialized,
|
124 | state: validState,
|
125 | request: validRequest
|
126 | };
|
127 |
|
128 | // execute the logic
|
129 | const result = this.runVMScriptCall(utcOffset,now,validOptions,context,script,callScript);
|
130 |
|
131 | const validResponse = logic.validateOutput(result.__response); // ensure the response is valid
|
132 | const validNewState = logic.validateOutput(result.__state); // ensure the new state is valid
|
133 | const validEmit = logic.validateOutputArray(result.__emit); // ensure all the emits are valid
|
134 |
|
135 | const answer = {
|
136 | 'clause': contractId,
|
137 | 'request': request, // Keep the original request
|
138 | 'response': validResponse,
|
139 | 'state': validNewState,
|
140 | 'emit': validEmit,
|
141 | };
|
142 | return Promise.resolve(answer);
|
143 | }
|
144 |
|
145 | /**
|
146 | * Invoke a clause, passing in the parameters for that clause
|
147 | * @param {LogicManager} logic - the logic
|
148 | * @param {string} contractId - the contract identifier
|
149 | * @param {string} clauseName - the clause name
|
150 | * @param {object} contract - the contract data
|
151 | * @param {object} params - the clause parameters
|
152 | * @param {object} state - the contract state, a JS object that can be deserialized
|
153 | * using the Composer serializer.
|
154 | * @param {string} currentTime - the definition of 'now'
|
155 | * @param {object} options to the text generation
|
156 | * @return {Promise} a promise that resolves to a result for the clause
|
157 | */
|
158 | async invoke(logic, contractId, clauseName, contract, params, state, currentTime, options) {
|
159 | // Set the current time and UTC Offset
|
160 | const now = Util.setCurrentTime(currentTime);
|
161 | const utcOffset = now.utcOffset();
|
162 | const validOptions = options ? options : {
|
163 | options: {
|
164 | '$class': 'org.accordproject.ergo.options.Options',
|
165 | 'wrapVariables': false,
|
166 | 'template': false,
|
167 | }
|
168 | };
|
169 |
|
170 | const validContract = logic.validateContract(contract, options); // ensure the contract is valid
|
171 | const validParams = logic.validateInputRecord(params); // ensure the parameters are valid
|
172 | const validState = logic.validateInput(state); // ensure the state is valid
|
173 |
|
174 | Logger.debug('Engine processing clause ' + clauseName + ' with state ' + state.$class);
|
175 |
|
176 | const script = this.cacheJsScript(logic.getScriptManager(), contractId);
|
177 | const callScript = logic.getInvokeCall(clauseName);
|
178 | const context = {
|
179 | data: validContract.serialized,
|
180 | state: validState,
|
181 | params: validParams
|
182 | };
|
183 |
|
184 | // execute the logic
|
185 | const result = this.runVMScriptCall(utcOffset,now,validOptions,context,script,callScript);
|
186 |
|
187 | const validResponse = logic.validateOutput(result.__response); // ensure the response is valid
|
188 | const validNewState = logic.validateOutput(result.__state); // ensure the new state is valid
|
189 | const validEmit = logic.validateOutputArray(result.__emit); // ensure all the emits are valid
|
190 |
|
191 | const answer = {
|
192 | 'clause': contractId,
|
193 | 'params': params, // Keep the original params
|
194 | 'response': validResponse,
|
195 | 'state': validNewState,
|
196 | 'emit': validEmit,
|
197 | };
|
198 | return Promise.resolve(answer);
|
199 | }
|
200 |
|
201 | /**
|
202 | * Initialize a clause
|
203 | * @param {LogicManager} logic - the logic
|
204 | * @param {string} contractId - the contract identifier
|
205 | * @param {object} contract - the contract data
|
206 | * @param {object} params - the clause parameters
|
207 | * @param {string} currentTime - the definition of 'now'
|
208 | * @param {object} options to the text generation
|
209 | * @return {Promise} a promise that resolves to a result for the clause initialization
|
210 | */
|
211 | async init(logic, contractId, contract, params, currentTime, options) {
|
212 | const defaultState = {
|
213 | '$class':'org.accordproject.cicero.contract.AccordContractState',
|
214 | 'stateId':'org.accordproject.cicero.contract.AccordContractState#1'
|
215 | };
|
216 | return this.invoke(logic, contractId, 'init', contract, params, defaultState, currentTime, options);
|
217 | }
|
218 |
|
219 | /**
|
220 | * Generate Text
|
221 | * @param {TemplateLogic} logic - the logic
|
222 | * @param {string} contractId - the contract identifier
|
223 | * @param {object} contract - the contract data
|
224 | * @param {object} params - the clause parameters
|
225 | * @param {string} currentTime - the definition of 'now'
|
226 | * @param {object} options to the text generation
|
227 | * @return {Promise} a promise that resolves to a result for the clause initialization
|
228 | */
|
229 | async draft(logic, contractId, contract, params, currentTime, options) {
|
230 | options = options || {};
|
231 |
|
232 | const defaultState = {
|
233 | '$class':'org.accordproject.cicero.contract.AccordContractState',
|
234 | 'stateId':'org.accordproject.cicero.contract.AccordContractState#1'
|
235 | };
|
236 | return this.invoke(logic, contractId, 'toText', contract, params, defaultState, currentTime, Object.assign(options, {convertResourcesToId: true}));
|
237 | }
|
238 |
|
239 | /**
|
240 | * Compile then initialize a clause
|
241 | *
|
242 | * @param {LogicManager} logic - the logic
|
243 | * @param {object} contract - the contract data
|
244 | * @param {object} params - the clause parameters
|
245 | * @param {string} currentTime - the definition of 'now'
|
246 | * @param {object} options to the text generation
|
247 | * @return {Promise} a promise that resolves to a result for the clause initialization
|
248 | */
|
249 | compileAndInit(logic, contract, params, currentTime, options) {
|
250 | return logic.compileLogic(false).then(() => {
|
251 | const contractId = logic.getContractName();
|
252 | return this.init(logic, contractId, contract, params, currentTime, options);
|
253 | });
|
254 | }
|
255 |
|
256 | /**
|
257 | * Compile then generate text
|
258 | *
|
259 | * @param {TemplateLogic} logic - the logic
|
260 | * @param {object} contract - the contract data
|
261 | * @param {object} params - the clause parameters
|
262 | * @param {string} currentTime - the definition of 'now'
|
263 | * @param {object} options to the text generation
|
264 | * @return {Promise} a promise that resolves to a result for the clause initialization
|
265 | */
|
266 | compileAndDraft(logic, contract, params, currentTime, options) {
|
267 | return logic.compileLogic(false).then(() => {
|
268 | const contractId = logic.getContractName();
|
269 | return this.draft(logic, contractId, contract, params, currentTime, options);
|
270 | });
|
271 | }
|
272 |
|
273 | /**
|
274 | * Compile then invoke a clause
|
275 | *
|
276 | * @param {LogicManager} logic - the logic
|
277 | * @param {string} clauseName - the clause name
|
278 | * @param {object} contract contract data in JSON
|
279 | * @param {object} params - the clause parameters
|
280 | * @param {object} state - the contract state, a JS object that can be deserialized
|
281 | * using the Composer serializer.
|
282 | * @param {string} currentTime - the definition of 'now'
|
283 | * @param {object} options to the text generation
|
284 | * @return {Promise} a promise that resolves to a result for the clause initialization
|
285 | */
|
286 | compileAndInvoke(logic, clauseName, contract, params, state, currentTime, options) {
|
287 | return logic.compileLogic(false).then(() => {
|
288 | const contractId = logic.getContractName();
|
289 | return this.invoke(logic, contractId, clauseName, contract, params, state, currentTime, options);
|
290 | });
|
291 | }
|
292 |
|
293 | /**
|
294 | * Compile then trigger a clause, passing in the request object
|
295 | *
|
296 | * @param {LogicManager} logic - the logic
|
297 | * @param {object} contract - the contract data
|
298 | * @param {object} request - the request, a JS object that can be deserialized
|
299 | * using the Composer serializer.
|
300 | * @param {object} state - the contract state, a JS object that can be deserialized
|
301 | * using the Composer serializer.
|
302 | * @param {string} currentTime - the definition of 'now'
|
303 | * @param {object} options to the text generation
|
304 | * @return {Promise} a promise that resolves to a result for the clause
|
305 | */
|
306 | compileAndTrigger(logic, contract, request, state, currentTime, options) {
|
307 | return logic.compileLogic(false).then(() => {
|
308 | const contractId = logic.getContractName();
|
309 | return this.trigger(logic, contractId, contract, request, state, currentTime, options);
|
310 | });
|
311 | }
|
312 |
|
313 | }
|
314 |
|
315 | module.exports = Engine;
|