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 | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
17 |
|
18 | function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
19 |
|
20 | const Metadata = require('./metadata');
|
21 |
|
22 | const Logger = require('@accordproject/ergo-compiler').Logger;
|
23 |
|
24 | const ParserManager = require('./parsermanager');
|
25 |
|
26 | const crypto = require('crypto');
|
27 |
|
28 | const stringify = require('json-stable-stringify');
|
29 |
|
30 | const TemplateLogic = require('@accordproject/ergo-compiler').TemplateLogic;
|
31 |
|
32 | const TemplateLoader = require('./templateloader');
|
33 |
|
34 | const TemplateSaver = require('./templatesaver');
|
35 | /**
|
36 | * A template for a legal clause or contract. A Template has a template model, request/response transaction types,
|
37 | * a template grammar (natural language for the template) as well as Ergo code for the business logic of the
|
38 | * template.
|
39 | * @class
|
40 | * @public
|
41 | * @abstract
|
42 | * @memberof module:cicero-core
|
43 | */
|
44 |
|
45 |
|
46 | class Template {
|
47 | /**
|
48 | * Create the Template.
|
49 | * Note: Only to be called by framework code. Applications should
|
50 | * retrieve instances from {@link Template.fromArchive} or {@link Template.fromDirectory}.
|
51 | * @param {object} packageJson - the JS object for package.json
|
52 | * @param {String} readme - the readme in markdown for the template (optional)
|
53 | * @param {object} samples - the sample text for the template in different locales
|
54 | * @param {object} request - the JS object for the sample request
|
55 | */
|
56 | constructor(packageJson, readme, samples, request) {
|
57 | this.metadata = new Metadata(packageJson, readme, samples, request);
|
58 | this.templateLogic = new TemplateLogic('cicero');
|
59 | this.parserManager = new ParserManager(this);
|
60 | }
|
61 | /**
|
62 | * Verifies that the template is well formed.
|
63 | * Throws an exception with the details of any validation errors.
|
64 | */
|
65 |
|
66 |
|
67 | validate() {
|
68 | this.getModelManager().validateModelFiles();
|
69 | this.getTemplateModel();
|
70 | this.getTemplateLogic().compileLogicSync(true);
|
71 | }
|
72 | /**
|
73 | * Returns the template model for the template
|
74 | * @throws {Error} if no template model is found, or multiple template models are found
|
75 | * @returns {ClassDeclaration} the template model for the template
|
76 | */
|
77 |
|
78 |
|
79 | getTemplateModel() {
|
80 | let modelType = 'org.accordproject.cicero.contract.AccordContract';
|
81 |
|
82 | if (this.getMetadata().getTemplateType() !== 0) {
|
83 | modelType = 'org.accordproject.cicero.contract.AccordClause';
|
84 | }
|
85 |
|
86 | const templateModels = this.getIntrospector().getClassDeclarations().filter(item => {
|
87 | return !item.isAbstract() && Template.instanceOf(item, modelType);
|
88 | });
|
89 |
|
90 | if (templateModels.length > 1) {
|
91 | throw new Error("Found multiple instances of ".concat(modelType, " in ").concat(this.metadata.getName(), ". The model for the template must contain a single asset that extends ").concat(modelType, "."));
|
92 | } else if (templateModels.length === 0) {
|
93 | throw new Error("Failed to find an asset that extends ".concat(modelType, " in ").concat(this.metadata.getName(), ". The model for the template must contain a single asset that extends ").concat(modelType, "."));
|
94 | } else {
|
95 | return templateModels[0];
|
96 | }
|
97 | }
|
98 | /**
|
99 | * Returns the identifier for this template
|
100 | * @return {String} the identifier of this template
|
101 | */
|
102 |
|
103 |
|
104 | getIdentifier() {
|
105 | return this.getMetadata().getIdentifier();
|
106 | }
|
107 | /**
|
108 | * Returns the metadata for this template
|
109 | * @return {Metadata} the metadata for this template
|
110 | */
|
111 |
|
112 |
|
113 | getMetadata() {
|
114 | return this.metadata;
|
115 | }
|
116 | /**
|
117 | * Returns the name for this template
|
118 | * @return {String} the name of this template
|
119 | */
|
120 |
|
121 |
|
122 | getName() {
|
123 | return this.getMetadata().getName();
|
124 | }
|
125 | /**
|
126 | * Returns the version for this template
|
127 | * @return {String} the version of this template. Use semver module
|
128 | * to parse.
|
129 | */
|
130 |
|
131 |
|
132 | getVersion() {
|
133 | return this.getMetadata().getVersion();
|
134 | }
|
135 | /**
|
136 | * Returns the description for this template
|
137 | * @return {String} the description of this template
|
138 | */
|
139 |
|
140 |
|
141 | getDescription() {
|
142 | return this.getMetadata().getDescription();
|
143 | }
|
144 | /**
|
145 | * Gets a content based SHA-256 hash for this template. Hash
|
146 | * is based on the metadata for the template plus the contents of
|
147 | * all the models and all the script files.
|
148 | * @return {string} the SHA-256 hash in hex format
|
149 | */
|
150 |
|
151 |
|
152 | getHash() {
|
153 | const content = {};
|
154 | content.metadata = this.getMetadata();
|
155 |
|
156 | if (this.parserManager.getTemplatizedGrammar()) {
|
157 | content.templatizedGrammar = this.parserManager.getTemplatizedGrammar();
|
158 | } else {
|
159 | // do not include the generated grammar because
|
160 | // the contents is not deterministic
|
161 | content.grammar = this.parserManager.getGrammar();
|
162 | }
|
163 |
|
164 | content.models = {};
|
165 | content.scripts = {};
|
166 | let modelFiles = this.getModelManager().getModels();
|
167 | modelFiles.forEach(function (file) {
|
168 | content.models[file.name] = file.content;
|
169 | });
|
170 | let scriptManager = this.getScriptManager();
|
171 | let scriptFiles = scriptManager.getScripts();
|
172 | scriptFiles.forEach(function (file) {
|
173 | content.scripts[file.getIdentifier()] = file.contents;
|
174 | });
|
175 | const hasher = crypto.createHash('sha256');
|
176 | hasher.update(stringify(content));
|
177 | return hasher.digest('hex');
|
178 | }
|
179 | /**
|
180 | * Persists this template to a Cicero Template Archive (cta) file.
|
181 | * @param {string} [language] - target language for the archive (should be 'ergo')
|
182 | * @param {Object} [options] - JSZip options
|
183 | * @return {Promise<Buffer>} the zlib buffer
|
184 | */
|
185 |
|
186 |
|
187 | toArchive(language, options) {
|
188 | var _this = this;
|
189 |
|
190 | return _asyncToGenerator(function* () {
|
191 | return TemplateSaver.toArchive(_this, language, options);
|
192 | })();
|
193 | }
|
194 | /**
|
195 | * Builds a Template from the contents of a directory.
|
196 | * The directory must include a package.json in the root (used to specify
|
197 | * the name, version and description of the template).
|
198 | *
|
199 | * @param {String} path to a local directory
|
200 | * @param {Object} [options] - an optional set of options to configure the instance.
|
201 | * @return {Promise<Template>} a Promise to the instantiated template
|
202 | */
|
203 |
|
204 |
|
205 | static fromDirectory(path, options) {
|
206 | return _asyncToGenerator(function* () {
|
207 | return TemplateLoader.fromDirectory(Template, path, options);
|
208 | })();
|
209 | }
|
210 | /**
|
211 | * Create a template from an archive.
|
212 | * @param {Buffer} buffer - the buffer to a Cicero Template Archive (cta) file
|
213 | * @return {Promise<Template>} a Promise to the template
|
214 | */
|
215 |
|
216 |
|
217 | static fromArchive(buffer) {
|
218 | return _asyncToGenerator(function* () {
|
219 | return TemplateLoader.fromArchive(Template, buffer);
|
220 | })();
|
221 | }
|
222 | /**
|
223 | * Create a template from an URL.
|
224 | * @param {String} url - the URL to a Cicero Template Archive (cta) file
|
225 | * @param {object} options - additional options
|
226 | * @return {Promise} a Promise to the template
|
227 | */
|
228 |
|
229 |
|
230 | static fromUrl(url) {
|
231 | var _arguments = arguments;
|
232 | return _asyncToGenerator(function* () {
|
233 | let options = _arguments.length > 1 && _arguments[1] !== undefined ? _arguments[1] : null;
|
234 | return TemplateLoader.fromUrl(Template, url, options);
|
235 | })();
|
236 | }
|
237 | /**
|
238 | * Visitor design pattern
|
239 | * @param {Object} visitor - the visitor
|
240 | * @param {Object} parameters - the parameter
|
241 | * @return {Object} the result of visiting or null
|
242 | * @private
|
243 | */
|
244 |
|
245 |
|
246 | accept(visitor, parameters) {
|
247 | return visitor.visit(this, parameters);
|
248 | }
|
249 | /**
|
250 | * Provides access to the parser manager for this template.
|
251 | * The parser manager can convert template data to and from
|
252 | * natural language text.
|
253 | * @return {ParserManager} the ParserManager for this template
|
254 | */
|
255 |
|
256 |
|
257 | getParserManager() {
|
258 | return this.parserManager;
|
259 | }
|
260 | /**
|
261 | * Provides access to the template logic for this template.
|
262 | * The template logic encapsulate the code necessary to
|
263 | * execute the clause or contract.
|
264 | * @return {TemplateLogic} the TemplateLogic for this template
|
265 | */
|
266 |
|
267 |
|
268 | getTemplateLogic() {
|
269 | return this.templateLogic;
|
270 | }
|
271 | /**
|
272 | * Provides access to the Introspector for this template. The Introspector
|
273 | * is used to reflect on the types defined within this template.
|
274 | * @return {Introspector} the Introspector for this template
|
275 | */
|
276 |
|
277 |
|
278 | getIntrospector() {
|
279 | return this.templateLogic.getIntrospector();
|
280 | }
|
281 | /**
|
282 | * Provides access to the Factory for this template. The Factory
|
283 | * is used to create the types defined in this template.
|
284 | * @return {Factory} the Factory for this template
|
285 | */
|
286 |
|
287 |
|
288 | getFactory() {
|
289 | return this.templateLogic.getFactory();
|
290 | }
|
291 | /**
|
292 | * Provides access to the Serializer for this template. The Serializer
|
293 | * is used to serialize instances of the types defined within this template.
|
294 | * @return {Serializer} the Serializer for this template
|
295 | */
|
296 |
|
297 |
|
298 | getSerializer() {
|
299 | return this.templateLogic.getSerializer();
|
300 | }
|
301 | /**
|
302 | * Provides access to the ScriptManager for this template. The ScriptManager
|
303 | * manage access to the scripts that have been defined within this template.
|
304 | * @return {ScriptManager} the ScriptManager for this template
|
305 | * @private
|
306 | */
|
307 |
|
308 |
|
309 | getScriptManager() {
|
310 | return this.templateLogic.getScriptManager();
|
311 | }
|
312 | /**
|
313 | * Provides access to the ModelManager for this template. The ModelManager
|
314 | * manage access to the models that have been defined within this template.
|
315 | * @return {ModelManager} the ModelManager for this template
|
316 | * @private
|
317 | */
|
318 |
|
319 |
|
320 | getModelManager() {
|
321 | return this.templateLogic.getModelManager();
|
322 | }
|
323 | /**
|
324 | * Set the samples within the Metadata
|
325 | * @param {object} samples the samples for the tempalte
|
326 | * @private
|
327 | */
|
328 |
|
329 |
|
330 | setSamples(samples) {
|
331 | this.metadata = new Metadata(this.metadata.getPackageJson(), this.metadata.getREADME(), samples, this.metadata.getRequest());
|
332 | }
|
333 | /**
|
334 | * Set a locale-specified sample within the Metadata
|
335 | * @param {object} sample the samples for the template
|
336 | * @param {string} locale the IETF Language Tag (BCP 47) for the language
|
337 | * @private
|
338 | */
|
339 |
|
340 |
|
341 | setSample(sample, locale) {
|
342 | const samples = this.metadata.getSamples();
|
343 | samples[locale] = sample;
|
344 | this.metadata = new Metadata(this.metadata.getPackageJson(), this.metadata.getREADME(), samples, this.metadata.getRequest());
|
345 | }
|
346 | /**
|
347 | * Set the request within the Metadata
|
348 | * @param {object} request the samples for the template
|
349 | * @private
|
350 | */
|
351 |
|
352 |
|
353 | setRequest(request) {
|
354 | this.metadata = new Metadata(this.metadata.getPackageJson(), this.metadata.getREADME(), this.metadata.getSamples(), request);
|
355 | }
|
356 | /**
|
357 | * Set the readme file within the Metadata
|
358 | * @param {String} readme the readme in markdown for the template
|
359 | * @private
|
360 | */
|
361 |
|
362 |
|
363 | setReadme(readme) {
|
364 | this.metadata = new Metadata(this.metadata.getPackageJson(), readme, this.metadata.getSamples(), this.metadata.getRequest());
|
365 | }
|
366 | /**
|
367 | * Set the packageJson within the Metadata
|
368 | * @param {object} packageJson the JS object for package.json
|
369 | * @private
|
370 | */
|
371 |
|
372 |
|
373 | setPackageJson(packageJson) {
|
374 | this.metadata = new Metadata(packageJson, this.metadata.getREADME(), this.metadata.getSamples(), this.metadata.getRequest());
|
375 | }
|
376 | /**
|
377 | * Provides a list of the input types that are accepted by this Template. Types use the fully-qualified form.
|
378 | * @return {Array} a list of the request types
|
379 | */
|
380 |
|
381 |
|
382 | getRequestTypes() {
|
383 | return this.extractFuncDeclParameter(1);
|
384 | }
|
385 | /**
|
386 | * Provides a list of the response types that are returned by this Template. Types use the fully-qualified form.
|
387 | * @return {Array} a list of the response types
|
388 | */
|
389 |
|
390 |
|
391 | getResponseTypes() {
|
392 | return this.extractFuncDeclParameter(2);
|
393 | }
|
394 | /**
|
395 | * Provides a list of the emit types that are emitted by this Template. Types use the fully-qualified form.
|
396 | * @return {Array} a list of the emit types
|
397 | */
|
398 |
|
399 |
|
400 | getEmitTypes() {
|
401 | return this.extractFuncDeclParameter(3);
|
402 | }
|
403 | /**
|
404 | * Provides a list of the state types that are expected by this Template. Types use the fully-qualified form.
|
405 | * @return {Array} a list of the state types
|
406 | */
|
407 |
|
408 |
|
409 | getStateTypes() {
|
410 | return this.extractFuncDeclParameter(4);
|
411 | }
|
412 | /**
|
413 | * Helper method to retrieve types from a function declarations
|
414 | * @param {number} index the parameter index for the function declaration
|
415 | * 1 - Request Types
|
416 | * 2 - Return Types
|
417 | * 3 - Emit Types
|
418 | * 4 - State Types
|
419 | * @returns {Array} a list of types
|
420 | * @private
|
421 | */
|
422 |
|
423 |
|
424 | extractFuncDeclParameter(index) {
|
425 | const functionDeclarations = this.getScriptManager().allFunctionDeclarations();
|
426 | let types = [];
|
427 | functionDeclarations.forEach((ele, n) => {
|
428 | const type = ele.getParameterTypes()[index];
|
429 |
|
430 | if (type) {
|
431 | types.push(type);
|
432 | }
|
433 | });
|
434 | Logger.debug(types);
|
435 | return types;
|
436 | }
|
437 | /**
|
438 | * Returns true if the template has logic, i.e. has more than one script file.
|
439 | * @return {boolean} true if the template has logic
|
440 | */
|
441 |
|
442 |
|
443 | hasLogic() {
|
444 | return this.getScriptManager().getAllScripts().length > 0;
|
445 | }
|
446 | /**
|
447 | * Check to see if a ClassDeclaration is an instance of the specified fully qualified
|
448 | * type name.
|
449 | * @internal
|
450 | * @param {ClassDeclaration} classDeclaration The class to test
|
451 | * @param {String} fqt The fully qualified type name.
|
452 | * @returns {boolean} True if classDeclaration an instance of the specified fully
|
453 | * qualified type name, false otherwise.
|
454 | */
|
455 |
|
456 |
|
457 | static instanceOf(classDeclaration, fqt) {
|
458 | // Check to see if this is an exact instance of the specified type.
|
459 | if (classDeclaration.getFullyQualifiedName() === fqt) {
|
460 | return true;
|
461 | } // Now walk the class hierachy looking to see if it's an instance of the specified type.
|
462 |
|
463 |
|
464 | let superTypeDeclaration = classDeclaration.getSuperTypeDeclaration();
|
465 |
|
466 | while (superTypeDeclaration) {
|
467 | if (superTypeDeclaration.getFullyQualifiedName() === fqt) {
|
468 | return true;
|
469 | }
|
470 |
|
471 | superTypeDeclaration = superTypeDeclaration.getSuperTypeDeclaration();
|
472 | }
|
473 |
|
474 | return false;
|
475 | }
|
476 |
|
477 | }
|
478 |
|
479 | module.exports = Template; |
\ | No newline at end of file |