UNPKG

13.5 kBJavaScriptView Raw
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'use strict';
16
17const debug = require('debug')('concerto:Factory');
18const Globalize = require('./globalize');
19
20const ModelUtil = require('./modelutil');
21
22const InstanceGenerator = require('./serializer/instancegenerator');
23const ValueGeneratorFactory = require('./serializer/valuegenerator');
24const ResourceValidator = require('./serializer/resourcevalidator');
25const TypedStack = require('./serializer/typedstack');
26
27const Relationship = require('./model/relationship');
28const Resource = require('./model/resource');
29const ValidatedResource = require('./model/validatedresource');
30const Concept = require('./model/concept');
31const ValidatedConcept = require('./model/validatedconcept');
32
33const TransactionDeclaration = require('./introspect/transactiondeclaration');
34const EventDeclaration = require('./introspect/eventdeclaration');
35
36const uuid = require('uuid');
37
38const Moment = require('moment-mini');
39
40/**
41 * Use the Factory to create instances of Resource: transactions, participants
42 * and assets.
43 *
44 * @class
45 * @memberof module:concerto-core
46 */
47class Factory {
48
49 /**
50 * Create the factory.
51 *
52 * @param {ModelManager} modelManager - The ModelManager to use for this registry
53 */
54 constructor(modelManager) {
55 this.modelManager = modelManager;
56 }
57
58 /**
59 * Create a new Resource with a given namespace, type name and id
60 * @param {String} ns - the namespace of the Resource
61 * @param {String} type - the type of the Resource
62 * @param {String} id - the identifier
63 * @param {Object} [options] - an optional set of options
64 * @param {boolean} [options.disableValidation] - pass true if you want the factory to
65 * return a {@link Resource} instead of a {@link ValidatedResource}. Defaults to false.
66 * @param {String} [options.generate] - Pass one of: <dl>
67 * <dt>sample</dt><dd>return a resource instance with generated sample data.</dd>
68 * <dt>empty</dt><dd>return a resource instance with empty property values.</dd></dl>
69 * @param {boolean} [options.includeOptionalFields] - if <code>options.generate</code>
70 * is specified, whether optional fields should be generated.
71 * @param {boolean} [options.allowEmptyId] - if <code>options.allowEmptyId</code>
72 * is specified as true, a zero length string for id is allowed (allows it to be filled in later).
73 * @return {Resource} the new instance
74 * @throws {TypeNotFoundException} if the type is not registered with the ModelManager
75 */
76 newResource(ns, type, id, options) {
77 const method = 'newResource';
78 options = options || {};
79
80 if(typeof(id) !== 'string') {
81 let formatter = Globalize.messageFormatter('factory-newinstance-invalididentifier');
82 throw new Error(formatter({
83 namespace: ns,
84 type: type
85 }));
86 }
87
88 if(!(options.allowEmptyId && id==='')) {
89 if(id.trim().length === 0) {
90 let formatter = Globalize.messageFormatter('factory-newinstance-missingidentifier');
91 throw new Error(formatter({
92 namespace: ns,
93 type: type
94 }));
95 }
96 }
97
98 const qualifiedName = ModelUtil.getFullyQualifiedName(ns, type);
99 const classDecl = this.modelManager.getType(qualifiedName);
100
101 if(classDecl.isAbstract()) {
102 let formatter = Globalize.messageFormatter('factory-newinstance-abstracttype');
103 throw new Error(formatter({
104 namespace: ns,
105 type: type
106 }));
107 }
108
109 if(classDecl.isConcept()) {
110 throw new Error('Use newConcept to create concepts ' + classDecl.getFullyQualifiedName());
111 }
112
113 let newObj = null;
114 if(options.disableValidation) {
115 newObj = new Resource(this.modelManager, classDecl, ns, type, id);
116 }
117 else {
118 newObj = new ValidatedResource(this.modelManager, classDecl, ns, type, id, new ResourceValidator());
119 }
120 newObj.assignFieldDefaults();
121 this.initializeNewObject(newObj, classDecl, options);
122
123 // if we have an identifier, we set it now
124 let idField = classDecl.getIdentifierFieldName();
125 newObj[idField] = id;
126 debug(method, 'Factory.newResource created', id );
127 return newObj;
128 }
129
130 /**
131 * Create a new Concept with a given namespace and type name
132 * @param {String} ns - the namespace of the Concept
133 * @param {String} type - the type of the Concept
134 * @param {Object} [options] - an optional set of options
135 * @param {boolean} [options.disableValidation] - pass true if you want the factory to
136 * return a {@link Concept} instead of a {@link ValidatedConcept}. Defaults to false.
137 * @param {String} [options.generate] - Pass one of: <dl>
138 * <dt>sample</dt><dd>return a resource instance with generated sample data.</dd>
139 * <dt>empty</dt><dd>return a resource instance with empty property values.</dd></dl>
140 * @param {boolean} [options.includeOptionalFields] - if <code>options.generate</code>
141 * is specified, whether optional fields should be generated.
142 * @return {Resource} the new instance
143 * @throws {TypeNotFoundException} if the type is not registered with the ModelManager
144 */
145 newConcept(ns, type, options) {
146 const method = 'newConcept';
147 const qualifiedName = ModelUtil.getFullyQualifiedName(ns, type);
148 const classDecl = this.modelManager.getType(qualifiedName);
149
150 if(classDecl.isAbstract()) {
151 let formatter = Globalize.messageFormatter('factory-newinstance-abstracttype');
152 throw new Error(formatter({
153 namespace: ns,
154 type: type
155 }));
156 }
157
158 if(!classDecl.isConcept()) {
159 throw new Error('Class is not a concept ' + classDecl.getFullyQualifiedName());
160 }
161
162 let newObj = null;
163 options = options || {};
164 if(options.disableValidation) {
165 newObj = new Concept(this.modelManager, classDecl, ns, type);
166 }
167 else {
168 newObj = new ValidatedConcept(this.modelManager, classDecl, ns, type, new ResourceValidator());
169 }
170 newObj.assignFieldDefaults();
171 this.initializeNewObject(newObj, classDecl, options);
172
173 debug(method, 'created concept', classDecl.getFullyQualifiedName() );
174 return newObj;
175 }
176
177 /**
178 * Create a new Relationship with a given namespace, type and identifier.
179 * A relationship is a typed pointer to an instance. I.e the relationship
180 * with `namespace = 'org.example'`, `type = 'Vehicle'` and `id = 'ABC' creates`
181 * a pointer that points at an instance of org.example.Vehicle with the id
182 * ABC.
183 *
184 * @param {String} ns - the namespace of the Resource
185 * @param {String} type - the type of the Resource
186 * @param {String} id - the identifier
187 * @return {Relationship} - the new relationship instance
188 * @throws {TypeNotFoundException} if the type is not registered with the ModelManager
189 */
190 newRelationship(ns, type, id) {
191 // Load the type declaration to force an error if it doesn't exist
192 const fqn = ModelUtil.getFullyQualifiedName(ns, type);
193 const classDecl = this.modelManager.getType(fqn);
194 return new Relationship(this.modelManager, classDecl, ns, type, id);
195 }
196
197 /**
198 * Create a new transaction object. The identifier of the transaction is
199 * set to a UUID.
200 * @param {String} ns - the namespace of the transaction.
201 * @param {String} type - the type of the transaction.
202 * @param {String} [id] - an optional identifier for the transaction; if you do not specify
203 * one then an identifier will be automatically generated.
204 * @param {Object} [options] - an optional set of options
205 * @param {String} [options.generate] - Pass one of: <dl>
206 * <dt>sample</dt><dd>return a resource instance with generated sample data.</dd>
207 * <dt>empty</dt><dd>return a resource instance with empty property values.</dd></dl>
208 * @param {boolean} [options.includeOptionalFields] - if <code>options.generate</code>
209 * is specified, whether optional fields should be generated.
210 * @param {boolean} [options.allowEmptyId] - if <code>options.allowEmptyId</code>
211 * is specified as true, a zero length string for id is allowed (allows it to be filled in later).
212 * @return {Resource} A resource for the new transaction.
213 */
214 newTransaction(ns, type, id, options) {
215 if (!ns) {
216 throw new Error('ns not specified');
217 } else if (!type) {
218 throw new Error('type not specified');
219 }
220 id = id || uuid.v4();
221 let transaction = this.newResource(ns, type, id, options);
222 const classDeclaration = transaction.getClassDeclaration();
223
224 if (!(classDeclaration instanceof TransactionDeclaration)) {
225 throw new Error(transaction.getClassDeclaration().getFullyQualifiedName() + ' is not a transaction');
226 }
227
228 // set the timestamp
229 transaction.timestamp = Moment().utc();
230
231 return transaction;
232 }
233
234 /**
235 * Create a new event object. The identifier of the event is
236 * set to a UUID.
237 * @param {String} ns - the namespace of the event.
238 * @param {String} type - the type of the event.
239 * @param {String} [id] - an optional identifier for the event; if you do not specify
240 * one then an identifier will be automatically generated.
241 * @param {Object} [options] - an optional set of options
242 * @param {String} [options.generate] - Pass one of: <dl>
243 * <dt>sample</dt><dd>return a resource instance with generated sample data.</dd>
244 * <dt>empty</dt><dd>return a resource instance with empty property values.</dd></dl>
245 * @param {boolean} [options.includeOptionalFields] - if <code>options.generate</code>
246 * is specified, whether optional fields should be generated.
247 * @param {boolean} [options.allowEmptyId] - if <code>options.allowEmptyId</code>
248 * is specified as true, a zero length string for id is allowed (allows it to be filled in later).
249 * @return {Resource} A resource for the new event.
250 */
251 newEvent(ns, type, id, options) {
252 if (!ns) {
253 throw new Error('ns not specified');
254 } else if (!type) {
255 throw new Error('type not specified');
256 }
257 id = id || 'valid';
258 let event = this.newResource(ns, type, id, options);
259 const classDeclaration = event.getClassDeclaration();
260
261 if (!(classDeclaration instanceof EventDeclaration)) {
262 throw new Error(event.getClassDeclaration().getFullyQualifiedName() + ' is not an event');
263 }
264
265 // set the timestamp
266 event.timestamp = Moment().utc();
267
268 return event;
269 }
270
271 /**
272 * PRIVATE IMPLEMENTATION. DO NOT CALL FROM OUTSIDE THIS CLASS.
273 *
274 * Initialize the state of a newly created resource
275 * @private
276 * @param {Typed} newObject - resource to initialize.
277 * @param {ClassDeclaration} classDeclaration - class declaration for the resource.
278 * @param {Object} clientOptions - field generation options supplied by the caller.
279 */
280 initializeNewObject(newObject, classDeclaration, clientOptions) {
281 const generateParams = this.parseGenerateOptions(clientOptions);
282 if (generateParams) {
283 generateParams.stack = new TypedStack(newObject);
284 generateParams.seen = [newObject.getFullyQualifiedType()];
285 const visitor = new InstanceGenerator();
286 classDeclaration.accept(visitor, generateParams);
287 }
288 }
289
290 /**
291 * PRIVATE IMPLEMENTATION. DO NOT CALL FROM OUTSIDE THIS CLASS.
292 *
293 * Parse the client-supplied field generation options and return a corresponding set of InstanceGenerator
294 * options that can be used to initialize a resource.
295 * @private
296 * @param {Object} clientOptions - field generation options supplied by the caller.
297 * @return {Object} InstanceGenerator options.
298 */
299 parseGenerateOptions(clientOptions) {
300 if (!clientOptions.generate) {
301 return null;
302 }
303
304 const generateParams = { };
305 generateParams.modelManager = this.modelManager;
306 generateParams.factory = this;
307
308 if ((/^empty$/i).test(clientOptions.generate)) {
309 generateParams.valueGenerator = ValueGeneratorFactory.empty();
310 } else {
311 // Allow any other value for backwards compatibility with previous (truthy) behavior
312 generateParams.valueGenerator = ValueGeneratorFactory.sample();
313 }
314
315 generateParams.includeOptionalFields = clientOptions.includeOptionalFields ? true : false;
316
317 return generateParams;
318 }
319
320}
321
322module.exports = Factory;