UNPKG

8.36 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 EventDeclaration = require('./introspect/eventdeclaration');
18const ConceptDeclaration = require('./introspect/conceptdeclaration');
19const EnumDeclaration = require('./introspect/enumdeclaration');
20const Globalize = require('./globalize');
21const JSONGenerator = require('./serializer/jsongenerator');
22const JSONPopulator = require('./serializer/jsonpopulator');
23const Typed = require('./model/typed');
24const ResourceValidator = require('./serializer/resourcevalidator');
25const TransactionDeclaration = require('./introspect/transactiondeclaration');
26const TypedStack = require('./serializer/typedstack');
27
28const baseDefaultOptions = {
29 validate: true,
30 ergo: false
31};
32
33/**
34 * Serialize Resources instances to/from various formats for long-term storage
35 * (e.g. on the blockchain).
36 *
37 * @class
38 * @memberof module:concerto-core
39 */
40class Serializer {
41
42 /**
43 * Create a Serializer.
44 * @param {Factory} factory - The Factory to use to create instances
45 * @param {ModelManager} modelManager - The ModelManager to use for validation etc.
46 */
47 constructor(factory,modelManager) {
48
49 if(!factory) {
50 throw new Error(Globalize.formatMessage('serializer-constructor-factorynull'));
51 } else if(!modelManager) {
52 throw new Error(Globalize.formatMessage('serializer-constructor-modelmanagernull'));
53 }
54
55 this.factory = factory;
56 this.modelManager = modelManager;
57 this.defaultOptions = Object.assign({}, baseDefaultOptions);
58 }
59
60 /**
61 * Set the default options for the serializer.
62 * @param {Object} newDefaultOptions The new default options for the serializer.
63 */
64 setDefaultOptions(newDefaultOptions) {
65 // Combine the specified default options with the base default
66 this.defaultOptions = Object.assign({}, baseDefaultOptions, newDefaultOptions);
67 }
68
69 /**
70 * <p>
71 * Convert a {@link Resource} to a JavaScript object suitable for long-term
72 * peristent storage.
73 * </p>
74 * @param {Resource} resource - The instance to convert to JSON
75 * @param {Object} [options] - the optional serialization options.
76 * @param {boolean} [options.validate] - validate the structure of the Resource
77 * with its model prior to serialization (default to true)
78 * @param {boolean} [options.convertResourcesToRelationships] - Convert resources that
79 * are specified for relationship fields into relationships, false by default.
80 * @param {boolean} [options.permitResourcesForRelationships] - Permit resources in the
81 * place of relationships (serializing them as resources), false by default.
82 * @param {boolean} [options.deduplicateResources] - Generate $id for resources and
83 * if a resources appears multiple times in the object graph only the first instance is
84 * serialized in full, subsequent instances are replaced with a reference to the $id
85 * @param {boolean} [options.convertResourcesToId] - Convert resources that
86 * are specified for relationship fields into their id, false by default.
87 * @return {Object} - The Javascript Object that represents the resource
88 * @throws {Error} - throws an exception if resource is not an instance of
89 * Resource or fails validation.
90 */
91 toJSON(resource, options) {
92 // correct instance type
93 if(!(resource instanceof Typed)) {
94 throw new Error(Globalize.formatMessage('serializer-tojson-notcobject'));
95 }
96
97 const parameters = {};
98 parameters.stack = new TypedStack(resource);
99 parameters.modelManager = this.modelManager;
100 parameters.seenResources = new Set();
101 parameters.dedupeResources = new Set();
102 const classDeclaration = this.modelManager.getType( resource.getFullyQualifiedType() );
103
104 // validate the resource against the model
105 options = options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
106 if(options.validate) {
107 const validator = new ResourceValidator(options);
108 classDeclaration.accept(validator, parameters);
109 }
110
111 const generator = new JSONGenerator(
112 options.convertResourcesToRelationships === true,
113 options.permitResourcesForRelationships === true,
114 options.deduplicateResources === true,
115 options.convertResourcesToId === true,
116 options.ergo === true
117 );
118
119 parameters.stack.clear();
120 parameters.stack.push(resource);
121
122 // this performs the conversion of the resouce into a standard JSON object
123 let result = classDeclaration.accept(generator, parameters);
124 return result;
125 }
126
127 /**
128 * Create a {@link Resource} from a JavaScript Object representation.
129 * The JavaScript Object should have been created by calling the
130 * {@link Serializer#toJSON toJSON} API.
131 *
132 * The Resource is populated based on the JavaScript object.
133 *
134 * @param {Object} jsonObject The JavaScript Object for a Resource
135 * @param {Object} options - the optional serialization options
136 * @param {boolean} options.acceptResourcesForRelationships - handle JSON objects
137 * in the place of strings for relationships, defaults to false.
138 * @param {boolean} options.validate - validate the structure of the Resource
139 * with its model prior to serialization (default to true)
140 * @return {Resource} The new populated resource
141 */
142 fromJSON(jsonObject, options) {
143
144 if(!jsonObject.$class) {
145 throw new Error('Invalid JSON data. Does not contain a $class type identifier.');
146 }
147
148 const classDeclaration = this.modelManager.getType(jsonObject.$class);
149
150 // default the options.
151 options = options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
152
153 // create a new instance, using the identifier field name as the ID.
154 let resource;
155 if (classDeclaration instanceof TransactionDeclaration) {
156 resource = this.factory.newTransaction( classDeclaration.getNamespace(),
157 classDeclaration.getName(),
158 jsonObject[classDeclaration.getIdentifierFieldName()] );
159 } else if (classDeclaration instanceof EventDeclaration) {
160 resource = this.factory.newEvent( classDeclaration.getNamespace(),
161 classDeclaration.getName(),
162 jsonObject[classDeclaration.getIdentifierFieldName()] );
163 } else if (classDeclaration instanceof ConceptDeclaration) {
164 resource = this.factory.newConcept( classDeclaration.getNamespace(),
165 classDeclaration.getName() );
166 } else if (classDeclaration instanceof EnumDeclaration) {
167 throw new Error('Attempting to create an ENUM declaration is not supported.');
168 } else {
169 resource = this.factory.newResource( classDeclaration.getNamespace(),
170 classDeclaration.getName(),
171 jsonObject[classDeclaration.getIdentifierFieldName()] );
172 }
173
174 // populate the resource based on the jsonObject
175 // by walking the classDeclaration
176 const parameters = {};
177 parameters.jsonStack = new TypedStack(jsonObject);
178 parameters.resourceStack = new TypedStack(resource);
179 parameters.modelManager = this.modelManager;
180 parameters.factory = this.factory;
181 const populator = new JSONPopulator(options.acceptResourcesForRelationships === true, options.ergo === true);
182 classDeclaration.accept(populator, parameters);
183
184 // validate the resource against the model
185 if(options.validate) {
186 resource.validate();
187 }
188
189 return resource;
190 }
191}
192
193module.exports = Serializer;