/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const Resource = require('./model/resource');
const Globalize = require('./globalize');
const JSONGenerator = require('./serializer/jsongenerator');
const JSONPopulator = require('./serializer/jsonpopulator');
const ResourceValidator = require('./serializer/resourcevalidator');
const TypedStack = require('./serializer/typedstack');
const JSONWriter = require('./codegen/jsonwriter');
/**
* Serialize Resources instances to/from various formats for long-term storage
* (e.g. on the blockchain).
* <p><a href="./diagrams/serializer.svg"><img src="./diagrams/serializer.svg" style="width:100%;"/></a></p>
* @class
* @memberof module:composer-common
*/
class Serializer {
/**
* Create a Serializer.
* <strong>Note: Only to be called by framework code. Applications should
* retrieve instances from {@link Composer}</strong>
* </p>
* @param {Factory} factory - The Factory to use to create instances
* @param {ModelManager} modelManager - The ModelManager to use for validation etc.
*/
constructor(factory,modelManager) {
if(!factory) {
throw new Error(Globalize.formatMessage('serializer-constructor-factorynull'));
} else if(!modelManager) {
throw new Error(Globalize.formatMessage('serializer-constructor-modelmanagernull'));
}
this.factory = factory;
this.modelManager = modelManager;
}
/**
* <p>
* Convert a {@link Resource} to a JavaScript object suitable for long-term
* peristent storage.
* </p>
* @param {Resource} resource - The instance to convert to JSON
* @param {Object} options - the optional serialization options.
* @param {boolean} options.validate - validate the structure of the Resource
* with its model prior to serialization (default to true)
* @param {boolean} options.convertResourcesToRelationships - Convert resources that
* are specified for relationship fields into relationships, false by default.
* @param {boolean} options.permitResourcesForRelationships - Permit resources in the
* place of relationships (serializing them as resources), false by default.
* @return {Object} - The Javascript Object that represents the resource
* @throws {Error} - throws an exception if resource is not an instance of
* Resource or fails validation.
*/
toJSON(resource, options) {
// correct instance type
if(!(resource instanceof Resource)) {
throw new Error(Globalize.formatMessage('serializer-tojson-notcobject'));
}
const parameters = {};
parameters.stack = new TypedStack(resource);
parameters.modelManager = this.modelManager;
parameters.seenResources = new Set();
const classDeclaration = this.modelManager.getType( resource.getFullyQualifiedType() );
if(!classDeclaration) {
throw new Error( 'Failed to find type ' + resource.getFullyQualifiedType() + ' in ModelManager.' );
}
// validate the resource against the model
options = options || { validate: true };
if(options.validate === true) {
const validator = new ResourceValidator();
classDeclaration.accept(validator, parameters);
}
const generator = new JSONGenerator(
options.convertResourcesToRelationships === true,
options.permitResourcesForRelationships === true
);
const writer = new JSONWriter();
parameters.writer = writer;
parameters.stack.clear();
parameters.stack.push(resource);
// this writes the JSON into the parameters.writer
classDeclaration.accept(generator, parameters);
const jsonText = parameters.writer.getBuffer();
try {
return JSON.parse(jsonText);
}
catch(err) {
throw new Error( 'Generated invalid JSON: ' + jsonText );
}
}
/**
* Create a {@link Resource} from a JavaScript Object representation.
* The JavaScript Object should have been created by calling the
* {@link Serializer#toJSON toJSON} API.
*
* The Resource is populated based on the JavaScript object.
*
* @param {Object} jsonObject The JavaScript Object for a Resource
* @param {Object} options - the optional serialization options
* @param {boolean} options.acceptResourcesForRelationships - handle JSON objects
* in the place of strings for relationships, defaults to false.
* @return {Resource} The new populated resource
*/
fromJSON(jsonObject, options) {
if(!jsonObject.$class) {
throw new Error('Invalid JSON data. Does not contain a $class type identifier.');
}
const classDeclaration = this.modelManager.getType(jsonObject.$class);
if(!classDeclaration) {
throw new Error( 'Failed to find type ' + jsonObject.$class + ' in ModelManager.' );
}
// default the options.
options = options || {};
// create a new instance, using the identifier field name as the ID.
let resource = this.factory.newInstance( classDeclaration.getModelFile().getNamespace(),
classDeclaration.getName(), jsonObject[classDeclaration.getIdentifierFieldName()] );
// populate the resource based on the jsonObject
// by walking the classDeclaration
const parameters = {};
parameters.jsonStack = new TypedStack(jsonObject);
parameters.resourceStack = new TypedStack(resource);
parameters.modelManager = this.modelManager;
parameters.factory = this.factory;
const populator = new JSONPopulator(options.acceptResourcesForRelationships === true);
classDeclaration.accept(populator, parameters);
return resource;
}
}
module.exports = Serializer;