UNPKG

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