1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | 'use strict';
|
16 |
|
17 | const ClassDeclaration = require('../introspect/classdeclaration');
|
18 | const Field = require('../introspect/field');
|
19 | const RelationshipDeclaration = require('../introspect/relationshipdeclaration');
|
20 | const Relationship = require('../model/relationship');
|
21 | const Util = require('../util');
|
22 | const ModelUtil = require('../modelutil');
|
23 | const ValidationException = require('./validationexception');
|
24 | const Moment = require('moment-mini');
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | function isSystemProperty(name) {
|
33 | return name.startsWith('$');
|
34 | }
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | function getAssignableProperties(resourceData) {
|
43 | return Object.keys(resourceData).filter((property) => {
|
44 | return !isSystemProperty(property) && !Util.isNull(resourceData[property]);
|
45 | });
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | function validateProperties(properties, classDeclaration) {
|
56 | const expectedProperties = classDeclaration.getProperties().map((property) => property.getName());
|
57 | const invalidProperties = properties.filter((property) => !expectedProperties.includes(property));
|
58 | if (invalidProperties.length > 0) {
|
59 | const errorText = `Unexpected properties for type ${classDeclaration.getFullyQualifiedName()}: ` +
|
60 | invalidProperties.join(', ');
|
61 | throw new ValidationException(errorText);
|
62 | }
|
63 | }
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | class JSONPopulator {
|
78 |
|
79 | |
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | constructor(acceptResourcesForRelationships, ergo) {
|
86 | this.acceptResourcesForRelationships = acceptResourcesForRelationships;
|
87 | this.ergo = ergo;
|
88 | }
|
89 |
|
90 | |
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | visit(thing, parameters) {
|
98 | if (thing instanceof ClassDeclaration) {
|
99 | return this.visitClassDeclaration(thing, parameters);
|
100 | } else if (thing instanceof RelationshipDeclaration) {
|
101 | return this.visitRelationshipDeclaration(thing, parameters);
|
102 | } else if (thing instanceof Field) {
|
103 | return this.visitField(thing, parameters);
|
104 | } else {
|
105 | throw new Error('Unrecognised ' + JSON.stringify(thing) );
|
106 | }
|
107 | }
|
108 |
|
109 | |
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | visitClassDeclaration(classDeclaration, parameters) {
|
117 | const jsonObj = parameters.jsonStack.pop();
|
118 | const resourceObj = parameters.resourceStack.pop();
|
119 |
|
120 | const properties = getAssignableProperties(jsonObj);
|
121 | validateProperties(properties, classDeclaration);
|
122 |
|
123 | properties.forEach((property) => {
|
124 | const value = jsonObj[property];
|
125 | parameters.jsonStack.push(value);
|
126 | const classProperty = classDeclaration.getProperty(property);
|
127 | resourceObj[property] = classProperty.accept(this,parameters);
|
128 | });
|
129 |
|
130 | return resourceObj;
|
131 | }
|
132 |
|
133 | |
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | visitField(field, parameters) {
|
141 | const jsonObj = parameters.jsonStack.pop();
|
142 | let result = null;
|
143 |
|
144 | if(field.isArray()) {
|
145 | result = [];
|
146 | for(let n=0; n < jsonObj.length; n++) {
|
147 | const jsonItem = jsonObj[n];
|
148 | result.push(this.convertItem(field,jsonItem, parameters));
|
149 | }
|
150 | }
|
151 | else {
|
152 | result = this.convertItem(field,jsonObj, parameters);
|
153 | }
|
154 |
|
155 | return result;
|
156 | }
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 | convertItem(field, jsonItem, parameters) {
|
166 | let result = null;
|
167 |
|
168 | if(!field.isPrimitive() && !field.isTypeEnum()) {
|
169 | let typeName = jsonItem.$class;
|
170 | if(!typeName) {
|
171 |
|
172 |
|
173 |
|
174 | typeName = field.getFullyQualifiedTypeName();
|
175 | }
|
176 |
|
177 |
|
178 | const classDeclaration = parameters.modelManager.getType(typeName);
|
179 |
|
180 |
|
181 | let subResource = null;
|
182 |
|
183 |
|
184 | if(!classDeclaration.isConcept()) {
|
185 | subResource = parameters.factory.newResource(classDeclaration.getNamespace(),
|
186 | classDeclaration.getName(), jsonItem[classDeclaration.getIdentifierFieldName()] );
|
187 | }
|
188 | else {
|
189 |
|
190 | subResource = parameters.factory.newConcept(classDeclaration.getNamespace(),
|
191 | classDeclaration.getName() );
|
192 | }
|
193 |
|
194 | result = subResource;
|
195 | parameters.resourceStack.push(subResource);
|
196 | parameters.jsonStack.push(jsonItem);
|
197 | classDeclaration.accept(this, parameters);
|
198 | }
|
199 | else {
|
200 | result = this.convertToObject(field,jsonItem);
|
201 | }
|
202 |
|
203 | return result;
|
204 | }
|
205 |
|
206 | |
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 | convertToObject(field, json) {
|
214 | let result = null;
|
215 |
|
216 | switch(field.getType()) {
|
217 | case 'DateTime':
|
218 | if (Moment.isMoment(json)) {
|
219 | result = json;
|
220 | } else {
|
221 | result = new Moment.parseZone(json);
|
222 | }
|
223 | break;
|
224 | case 'Integer':
|
225 | case 'Long':
|
226 | result = this.ergo ? parseInt(json.nat) : parseInt(json);
|
227 | break;
|
228 | case 'Double':
|
229 | result = parseFloat(json);
|
230 | break;
|
231 | case 'Boolean':
|
232 | result = (json === true || json === 'true');
|
233 | break;
|
234 | case 'String':
|
235 | result = json.toString();
|
236 | break;
|
237 | default: {
|
238 |
|
239 | if (this.ergo) {
|
240 |
|
241 | let current = json.data;
|
242 | while (!current.left) {
|
243 | current = current.right;
|
244 | }
|
245 | result = current.left;
|
246 | } else {
|
247 | result = json;
|
248 | }
|
249 | }
|
250 | }
|
251 | return result;
|
252 | }
|
253 |
|
254 | |
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | visitRelationshipDeclaration(relationshipDeclaration, parameters) {
|
262 | const jsonObj = parameters.jsonStack.pop();
|
263 | let result = null;
|
264 |
|
265 | let typeFQN = relationshipDeclaration.getFullyQualifiedTypeName();
|
266 | let defaultNamespace = ModelUtil.getNamespace(typeFQN);
|
267 | if(!defaultNamespace) {
|
268 | defaultNamespace = relationshipDeclaration.getNamespace();
|
269 | }
|
270 | let defaultType = ModelUtil.getShortName(typeFQN);
|
271 |
|
272 | if(relationshipDeclaration.isArray()) {
|
273 | result = [];
|
274 | for(let n=0; n < jsonObj.length; n++) {
|
275 | let jsonItem = jsonObj[n];
|
276 | if (typeof jsonItem === 'string') {
|
277 | result.push(Relationship.fromURI(parameters.modelManager, jsonItem, defaultNamespace, defaultType ));
|
278 | } else {
|
279 | if (!this.acceptResourcesForRelationships) {
|
280 | throw new Error('Invalid JSON data. Found a value that is not a string: ' + jsonObj + ' for relationship ' + relationshipDeclaration);
|
281 | }
|
282 |
|
283 |
|
284 | if(!jsonItem.$class) {
|
285 | throw new Error('Invalid JSON data. Does not contain a $class type identifier: ' + jsonItem + ' for relationship ' + relationshipDeclaration );
|
286 | }
|
287 |
|
288 | const classDeclaration = parameters.modelManager.getType(jsonItem.$class);
|
289 |
|
290 |
|
291 | let subResource = parameters.factory.newResource(classDeclaration.getNamespace(),
|
292 | classDeclaration.getName(), jsonItem[classDeclaration.getIdentifierFieldName()] );
|
293 | parameters.jsonStack.push(jsonItem);
|
294 | parameters.resourceStack.push(subResource);
|
295 | classDeclaration.accept(this, parameters);
|
296 | result.push(subResource);
|
297 | }
|
298 | }
|
299 | }
|
300 | else {
|
301 | if (typeof jsonObj === 'string') {
|
302 | result = Relationship.fromURI(parameters.modelManager, jsonObj, defaultNamespace, defaultType );
|
303 | } else {
|
304 | if (!this.acceptResourcesForRelationships) {
|
305 | throw new Error('Invalid JSON data. Found a value that is not a string: ' + jsonObj + ' for relationship ' + relationshipDeclaration);
|
306 | }
|
307 |
|
308 |
|
309 | if(!jsonObj.$class) {
|
310 | throw new Error('Invalid JSON data. Does not contain a $class type identifier: ' + jsonObj + ' for relationship ' + relationshipDeclaration );
|
311 | }
|
312 |
|
313 | const classDeclaration = parameters.modelManager.getType(jsonObj.$class);
|
314 |
|
315 |
|
316 | let subResource = parameters.factory.newResource(classDeclaration.getNamespace(),
|
317 | classDeclaration.getName(), jsonObj[classDeclaration.getIdentifierFieldName()] );
|
318 | parameters.jsonStack.push(jsonObj);
|
319 | parameters.resourceStack.push(subResource);
|
320 | classDeclaration.accept(this, parameters);
|
321 | result = subResource;
|
322 | }
|
323 | }
|
324 | return result;
|
325 | }
|
326 | }
|
327 |
|
328 | module.exports = JSONPopulator;
|