From 744c17f93a010c58ffced54b43542493d0d680c7 Mon Sep 17 00:00:00 2001 From: Dan Selman Date: Wed, 26 Jul 2017 17:54:04 +0100 Subject: [PATCH 1/5] added serialization option to support Jackson, updated Java codegen, allow serialization options to be passed for HTTP post --- packages/composer-common/api.txt | 2 +- packages/composer-common/changelog.txt | 3 +- .../lib/codegen/fromcto/java/javavisitor.js | 55 ++++++- .../composer-common/lib/codegen/jsonwriter.js | 2 +- packages/composer-common/lib/serializer.js | 7 +- .../lib/serializer/jsongenerator.js | 129 +++++++++------ .../test/serializer/complex/complex.js | 95 +++++++++++ .../complex/vehicle-lifecycle/base.cto | 69 ++++++++ .../complex/vehicle-lifecycle/business.cto | 39 +++++ .../vehicle-lifecycle/manufacturer.cto | 75 +++++++++ .../complex/vehicle-lifecycle/odm.cto | 67 ++++++++ .../complex/vehicle-lifecycle/vda.cto | 150 ++++++++++++++++++ .../complex/vehicle-lifecycle/vehicle.cto | 51 ++++++ packages/composer-runtime/lib/api.js | 6 +- packages/composer-runtime/test/api.js | 6 +- 15 files changed, 687 insertions(+), 69 deletions(-) create mode 100644 packages/composer-common/test/serializer/complex/complex.js create mode 100644 packages/composer-common/test/serializer/complex/vehicle-lifecycle/base.cto create mode 100644 packages/composer-common/test/serializer/complex/vehicle-lifecycle/business.cto create mode 100644 packages/composer-common/test/serializer/complex/vehicle-lifecycle/manufacturer.cto create mode 100644 packages/composer-common/test/serializer/complex/vehicle-lifecycle/odm.cto create mode 100644 packages/composer-common/test/serializer/complex/vehicle-lifecycle/vda.cto create mode 100644 packages/composer-common/test/serializer/complex/vehicle-lifecycle/vehicle.cto diff --git a/packages/composer-common/api.txt b/packages/composer-common/api.txt index 3699aacb1f..c36928966f 100644 --- a/packages/composer-common/api.txt +++ b/packages/composer-common/api.txt @@ -126,7 +126,7 @@ class SecurityException extends BaseException { } class Serializer { + void constructor(Factory,ModelManager) - + Object toJSON(Resource,Object,boolean,boolean,boolean) throws Error + + Object toJSON(Resource,Object,boolean,boolean,boolean,boolean) throws Error + Resource fromJSON(Object,Object,boolean) } class ValidationException extends BaseException { diff --git a/packages/composer-common/changelog.txt b/packages/composer-common/changelog.txt index 9ee60d4a7b..1944dad18f 100644 --- a/packages/composer-common/changelog.txt +++ b/packages/composer-common/changelog.txt @@ -11,9 +11,10 @@ # # Note that the latest public API is documented using JSDocs and is available in api.txt. # -Version 0.10.1 {fe6fa1850003a7c5d47bf43e3ab8d628} 2017-07-24 +Version 0.10.1 {d65ff67e642f64b0b7d577a2b63d961e} 2017-07-24 - Added InvalidQueryException, BaseFileException - Added IdCard to composer-common package +- Added option to Serializer.toJSON to deduplicate resources Version 0.9.2 {60327250ee4f059647020f8aee5ed67b} 2017-07-06 - Added includeOptionalFields option to Factory.newXXX functions diff --git a/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js b/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js index d17e569695..ef190893c2 100644 --- a/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js +++ b/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js @@ -88,6 +88,33 @@ class JavaVisitor { * @private */ visitModelManager(modelManager, parameters) { + + parameters.fileWriter.openFile( 'org/hyperledger/composer/system/Resource.java'); + parameters.fileWriter.writeLine(0, '// this code is generated and should not be modified'); + parameters.fileWriter.writeLine(0, 'package org.hyperledger.composer.system;'); + parameters.fileWriter.writeLine(0, 'import com.fasterxml.jackson.annotation.*;'); + + parameters.fileWriter.writeLine(0, ` +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "$class") +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "$id") +public abstract class Resource +{ + public abstract String getID(); + private String $id; + + @JsonProperty("$id") + public String get$id() { + return $id; + } + @JsonProperty("$id") + public void set$id(String i) { + $id = i; + } + +} + `); + parameters.fileWriter.closeFile(); + modelManager.getModelFiles().forEach((modelFile) => { modelFile.accept(this,parameters); }); @@ -122,11 +149,7 @@ class JavaVisitor { parameters.fileWriter.writeLine(0, '// this code is generated and should not be modified'); parameters.fileWriter.writeLine(0, 'package ' + clazz.getModelFile().getNamespace() + ';'); parameters.fileWriter.writeLine(0, ''); - - if(!clazz.isSystemType()) { - parameters.fileWriter.writeLine(0, 'import org.hyperledger.composer.system.*;'); - parameters.fileWriter.writeLine(0, 'import com.fasterxml.jackson.annotation.*;'); - } + parameters.fileWriter.writeLine(0, 'import org.hyperledger.composer.system.*;'); } /** @@ -177,6 +200,11 @@ class JavaVisitor { this.startClassFile(classDeclaration, parameters); + classDeclaration.getModelFile().getImports().forEach((imported) => { + parameters.fileWriter.writeLine(0, 'import ' + imported + ';' ); + }); + parameters.fileWriter.writeLine(0, ''); + let isAbstract = ''; if( classDeclaration.isAbstract() ) { isAbstract = 'abstract '; @@ -186,14 +214,27 @@ class JavaVisitor { } let superType = ''; + + if(classDeclaration.isSystemCoreType()) { + superType = ' extends org.hyperledger.composer.system.Resource'; + } + if(classDeclaration.getSuperType()) { superType = ' extends ' + ModelUtil.getShortName(classDeclaration.getSuperType()); } parameters.fileWriter.writeLine(0, 'public ' + isAbstract + 'class ' + classDeclaration.getName() + superType + ' {' ); - // add the magic $class property - parameters.fileWriter.writeLine(1, 'public String $class;' ); + // add the getID abstract type + if(classDeclaration.getIdentifierFieldName()) { + parameters.fileWriter.writeLine(1, ` + // the accessor for the identifying field + public String getID() { + return ${classDeclaration.getIdentifierFieldName()}; + } +` + ); + } classDeclaration.getOwnProperties().forEach((property) => { property.accept(this,parameters); diff --git a/packages/composer-common/lib/codegen/jsonwriter.js b/packages/composer-common/lib/codegen/jsonwriter.js index bfee61ac64..2a8bfb4821 100644 --- a/packages/composer-common/lib/codegen/jsonwriter.js +++ b/packages/composer-common/lib/codegen/jsonwriter.js @@ -112,7 +112,7 @@ class JSONWriter extends Writer { * @param {string} value - the value */ writeKeyStringValue(key,value) { - this.writeComma(); + // this.writeComma(); this.writeKey(key); this.writeStringValue(value); this.firstItem = false; diff --git a/packages/composer-common/lib/serializer.js b/packages/composer-common/lib/serializer.js index a0488b0bb8..8d07eff8c4 100644 --- a/packages/composer-common/lib/serializer.js +++ b/packages/composer-common/lib/serializer.js @@ -65,6 +65,9 @@ class Serializer { * 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. + * @param {boolean} options.deduplicateResources - Generate $id for resources and + * if a resources appears multiple times in the object graph only the first instance is + * serialized in full, subsequent instances are replaced with a reference to the $id * @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. @@ -79,6 +82,7 @@ class Serializer { parameters.stack = new TypedStack(resource); parameters.modelManager = this.modelManager; parameters.seenResources = new Set(); + parameters.dedupeResources = new Set(); const classDeclaration = this.modelManager.getType( resource.getFullyQualifiedType() ); // validate the resource against the model @@ -93,7 +97,8 @@ class Serializer { const generator = new JSONGenerator( options.convertResourcesToRelationships === true, - options.permitResourcesForRelationships === true + options.permitResourcesForRelationships === true, + options.deduplicateResources === true ); const writer = new JSONWriter(); parameters.writer = writer; diff --git a/packages/composer-common/lib/serializer/jsongenerator.js b/packages/composer-common/lib/serializer/jsongenerator.js index 5575f6ef9d..1339fe4e84 100644 --- a/packages/composer-common/lib/serializer/jsongenerator.js +++ b/packages/composer-common/lib/serializer/jsongenerator.js @@ -18,6 +18,7 @@ const ClassDeclaration = require('../introspect/classdeclaration'); const Field = require('../introspect/field'); const RelationshipDeclaration = require('../introspect/relationshipdeclaration'); const Resource = require('../model/resource'); +const Identifiable = require('../model/identifiable'); const Typed = require('../model/typed'); const Concept = require('../model/concept'); const ModelUtil = require('../modelutil'); @@ -42,10 +43,14 @@ class JSONGenerator { * are specified for relationship fields into relationships, false by default. * @param {boolean} [permitResourcesForRelationships] Permit resources in the * place of relationships (serializing them as resources), false by default. + * @param {boolean} [deduplicateResources] If resources appear several times + * in the object graph only the first instance is serialized, with only the $id + * written for subsequent instances, false by default. */ - constructor(convertResourcesToRelationships, permitResourcesForRelationships) { + constructor(convertResourcesToRelationships, permitResourcesForRelationships, deduplicateResources) { this.convertResourcesToRelationships = convertResourcesToRelationships; this.permitResourcesForRelationships = permitResourcesForRelationships; + this.deduplicateResources = deduplicateResources; } /** @@ -63,7 +68,7 @@ class JSONGenerator { } else if (thing instanceof Field) { return this.visitField(thing, parameters); } else { - throw new Error('Unrecognised ' + JSON.stringify(thing) ); + throw new Error('Unrecognised ' + JSON.stringify(thing)); } } @@ -75,24 +80,47 @@ class JSONGenerator { * @private */ visitClassDeclaration(classDeclaration, parameters) { - parameters.writer.openObject(); - parameters.writer.writeKeyStringValue('$class', classDeclaration.getFullyQualifiedName()); const obj = parameters.stack.pop(); - if(!((obj instanceof Resource) || (obj instanceof Concept))) { - throw new Error('Expected a Resource or a Concept, but found ' + obj ); + if (!((obj instanceof Resource) || (obj instanceof Concept))) { + throw new Error('Expected a Resource or a Concept, but found ' + obj); } - const properties = classDeclaration.getProperties(); - for(let n=0; n < properties.length; n++) { - const property = properties[n]; - const value = obj[property.getName()]; - if(!Util.isNull(value)) { - parameters.stack.push(value); - property.accept(this,parameters); + + let writeFields = true; + let id = null; + + if (obj instanceof Identifiable && this.deduplicateResources) { + id = obj.toURI(); + if( parameters.dedupeResources.has(id)) { + writeFields = false; + parameters.writer.writeStringValue( id ); + } + else { + parameters.dedupeResources.add(id); + } + } + + if (writeFields) { + parameters.writer.openObject(); + parameters.writer.writeKeyStringValue('$class', classDeclaration.getFullyQualifiedName()); + + if(this.deduplicateResources && id) { + parameters.writer.writeKeyStringValue('$id', id ); + } + + const properties = classDeclaration.getProperties(); + for (let n = 0; n < properties.length; n++) { + const property = properties[n]; + const value = obj[property.getName()]; + if (!Util.isNull(value)) { + parameters.stack.push(value); + property.accept(this, parameters); + } } + + parameters.writer.closeObject(); } - parameters.writer.closeObject(); return null; } @@ -106,26 +134,23 @@ class JSONGenerator { visitField(field, parameters) { const obj = parameters.stack.pop(); parameters.writer.writeKey(field.getName()); - if(field.isArray()) { + if (field.isArray()) { parameters.writer.openArray(); - for(let n=0; n < obj.length; n++) { + for (let n = 0; n < obj.length; n++) { const item = obj[n]; - if(!field.isPrimitive() && !ModelUtil.isEnum(field)) { + if (!field.isPrimitive() && !ModelUtil.isEnum(field)) { parameters.writer.writeComma(); parameters.stack.push(item, Typed); const classDecl = parameters.modelManager.getType(item.getFullyQualifiedType()); classDecl.accept(this, parameters); - } - else { - parameters.writer.writeArrayValue(this.convertToJSON(field,item)); + } else { + parameters.writer.writeArrayValue(this.convertToJSON(field, item)); } } parameters.writer.closeArray(); - } - else if(field.isPrimitive() || ModelUtil.isEnum(field)) { - parameters.writer.writeValue(this.convertToJSON(field,obj)); - } - else { + } else if (field.isPrimitive() || ModelUtil.isEnum(field)) { + parameters.writer.writeValue(this.convertToJSON(field, obj)); + } else { parameters.stack.push(obj); const classDeclaration = parameters.modelManager.getType(obj.getFullyQualifiedType()); classDeclaration.accept(this, parameters); @@ -142,19 +167,22 @@ class JSONGenerator { * @return {string} the text representation */ convertToJSON(field, obj) { - switch(field.getType()) { - case 'DateTime': { - return `"${obj.toISOString()}"`; - } + switch (field.getType()) { + case 'DateTime': + { + return `"${obj.toISOString()}"`; + } case 'Integer': case 'Long': case 'Double': - case 'Boolean':{ - return `${obj.toString()}`; - } - default: { - return JSON.stringify(obj.toString()); - } + case 'Boolean': + { + return `${obj.toString()}`; + } + default: + { + return JSON.stringify(obj.toString()); + } } } @@ -169,14 +197,14 @@ class JSONGenerator { const obj = parameters.stack.pop(); parameters.writer.writeKey(relationshipDeclaration.getName()); - if(relationshipDeclaration.isArray()) { + if (relationshipDeclaration.isArray()) { parameters.writer.openArray(); - for(let n=0; n < obj.length; n++) { + for (let n = 0; n < obj.length; n++) { const item = obj[n]; if (this.permitResourcesForRelationships && item instanceof Resource) { let fqi = item.getFullyQualifiedIdentifier(); if (parameters.seenResources.has(fqi)) { - let relationshipText = this.getRelationshipText(relationshipDeclaration, item ); + let relationshipText = this.getRelationshipText(relationshipDeclaration, item); parameters.writer.writeStringValue(relationshipText); } else { parameters.seenResources.add(fqi); @@ -187,16 +215,15 @@ class JSONGenerator { parameters.seenResources.delete(fqi); } } else { - let relationshipText = this.getRelationshipText(relationshipDeclaration, item ); + let relationshipText = this.getRelationshipText(relationshipDeclaration, item); parameters.writer.writeArrayStringValue(relationshipText); } } parameters.writer.closeArray(); - } - else if (this.permitResourcesForRelationships && obj instanceof Resource) { + } else if (this.permitResourcesForRelationships && obj instanceof Resource) { let fqi = obj.getFullyQualifiedIdentifier(); if (parameters.seenResources.has(fqi)) { - let relationshipText = this.getRelationshipText(relationshipDeclaration, obj ); + let relationshipText = this.getRelationshipText(relationshipDeclaration, obj); parameters.writer.writeStringValue(relationshipText); } else { parameters.seenResources.add(fqi); @@ -206,24 +233,24 @@ class JSONGenerator { parameters.seenResources.delete(fqi); } } else { - let relationshipText = this.getRelationshipText(relationshipDeclaration, obj ); + let relationshipText = this.getRelationshipText(relationshipDeclaration, obj); parameters.writer.writeStringValue(relationshipText); } return null; } /** - * Returns the persistent format for a relationship. - * @param {RelationshipDeclaration} relationshipDeclaration - the relationship being persisted - * @param {Identifiable} relationshipOrResource - the relationship or the resource - * @returns {string} the text to use to persist the relationship - */ + * Returns the persistent format for a relationship. + * @param {RelationshipDeclaration} relationshipDeclaration - the relationship being persisted + * @param {Identifiable} relationshipOrResource - the relationship or the resource + * @returns {string} the text to use to persist the relationship + */ getRelationshipText(relationshipDeclaration, relationshipOrResource) { - if(relationshipOrResource instanceof Resource) { + if (relationshipOrResource instanceof Resource) { const allowRelationships = this.convertResourcesToRelationships || this.permitResourcesForRelationships; - if(!allowRelationships) { - throw new Error('Did not find a relationship for ' + relationshipDeclaration.getFullyQualifiedTypeName() + ' found ' + relationshipOrResource ); + if (!allowRelationships) { + throw new Error('Did not find a relationship for ' + relationshipDeclaration.getFullyQualifiedTypeName() + ' found ' + relationshipOrResource); } } return relationshipOrResource.toURI(); diff --git a/packages/composer-common/test/serializer/complex/complex.js b/packages/composer-common/test/serializer/complex/complex.js new file mode 100644 index 0000000000..ce50a7a1bc --- /dev/null +++ b/packages/composer-common/test/serializer/complex/complex.js @@ -0,0 +1,95 @@ +/* + * 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 Factory = require('../../../lib/factory'); +const ModelManager = require('../../../lib/modelmanager'); +const Serializer = require('../../../lib/serializer'); +const fs = require('fs'); + + +let chai = require('chai'); +chai.should(); +const sinon = require('sinon'); + +describe('Test generating deduplicated JSON for complex models', () => { + + let modelManager; + let factory; + let sandbox; + let serializer; + + before(() => { + modelManager = new ModelManager(); + + const base = fs.readFileSync('./test/serializer/complex/vehicle-lifecycle/base.cto', 'utf8'); + const business = fs.readFileSync('./test/serializer/complex/vehicle-lifecycle/business.cto', 'utf8'); + const vl = fs.readFileSync('./test/serializer/complex/vehicle-lifecycle/vehicle.cto', 'utf8'); + const odm = fs.readFileSync('./test/serializer/complex/vehicle-lifecycle/odm.cto', 'utf8'); + const manufacturer = fs.readFileSync('./test/serializer/complex/vehicle-lifecycle/manufacturer.cto', 'utf8'); + const dvla = fs.readFileSync('./test/serializer/complex/vehicle-lifecycle/vda.cto', 'utf8'); + + modelManager.addModelFiles([base,business,vl,manufacturer,dvla,odm]); + factory = new Factory(modelManager); + serializer = new Serializer(factory, modelManager); + }); + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('#convertToJSON', () => { + + it('should generate deduplicated JSON for a complex model', () => { + + const wrapper = factory.newResource( 'org.acme.vehicle.lifecycle', 'TransactionWrapper', 'dummy'); + wrapper.transaction = factory.newTransaction('org.vda', 'PrivateVehicleTransfer'); + wrapper.transaction.seller = factory.newResource('composer.base', 'Person', 'dan'); + wrapper.transaction.seller.firstName = 'Dan'; + wrapper.transaction.seller.lastName = 'Selman'; + wrapper.transaction.seller.gender = 'MALE'; + wrapper.transaction.seller.nationalities = ['French', 'UK']; + wrapper.transaction.seller.contactDetails = factory.newConcept('composer.base', 'ContactDetails'); + wrapper.transaction.seller.contactDetails.email = 'dan@acme.org'; + wrapper.transaction.buyer = factory.newResource('composer.base', 'Person', 'anthony'); + wrapper.transaction.vehicle = factory.newResource('org.vda', 'Vehicle', '156478954'); + wrapper.transaction.vehicle.vehicleDetails = factory.newConcept( 'org.vda', 'VehicleDetails'); + wrapper.transaction.vehicle.vehicleDetails.make = 'Ford'; + wrapper.transaction.vehicle.vehicleDetails.modelType = 'Mustang'; + wrapper.transaction.vehicle.vehicleDetails.colour = 'Red'; + wrapper.transaction.vehicle.vehicleStatus = 'ACTIVE'; + wrapper.transaction.vehicle.owner = factory.newRelationship( 'composer.base', 'Person', 'dan'); + + const logEntry = factory.newConcept('org.vda', 'VehicleTransferLogEntry'); + logEntry.vehicle = wrapper.transaction.vehicle; + logEntry.buyer = wrapper.transaction.buyer; + logEntry.seller = wrapper.transaction.seller; + logEntry.timestamp = new Date(); + wrapper.transaction.vehicle.logEntries= [logEntry]; + + const obj = serializer.toJSON(wrapper, {deduplicateResources: true, permitResourcesForRelationships: true}); + + // check that the resources have been deduplicated + // and the $id attribute has been added + obj.$id.should.equal('resource:org.acme.vehicle.lifecycle.TransactionWrapper#dummy'); + obj.transaction.vehicle.logEntries[0].buyer.should.equal('resource:composer.base.Person#anthony'); + obj.transaction.vehicle.logEntries[0].seller.should.equal('resource:composer.base.Person#dan'); + }); + }); +}); diff --git a/packages/composer-common/test/serializer/complex/vehicle-lifecycle/base.cto b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/base.cto new file mode 100644 index 0000000000..a71eb6d2ad --- /dev/null +++ b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/base.cto @@ -0,0 +1,69 @@ +/* + * 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. + */ + +/** + * A library of standard reusable types + */ +namespace composer.base + +enum Gender { + o MALE + o FEMALE + o OTHER +} + +participant Person identified by ssn { + o String ssn + o String title optional + o String firstName optional + o String lastName optional + o String[] middleNames optional + o Gender gender optional + o String[] nationalities optional + o ContactDetails contactDetails optional + o BirthDetails birthDetails optional + o DeathDetails deathDetails optional +} + +concept ContactDetails { + o String email optional + o String mobilePhone optional + o String homePhone optional + o Address address optional +} + +concept BirthDetails { + o DateTime dateOfBirth optional + o String placeOfBirth optional +} + +concept DeathDetails { + o DateTime dateOfDeath optional + o String placeOfDeath optional +} + +/** + * A concept for a simple street address + */ +concept Address { + o String city optional + o String country optional + o String locality optional + o String region optional + o String street optional + o String street2 optional + o String street3 optional + o String postalCode optional + o String postOfficeBoxNumber optional +} diff --git a/packages/composer-common/test/serializer/complex/vehicle-lifecycle/business.cto b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/business.cto new file mode 100644 index 0000000000..d149c78bd4 --- /dev/null +++ b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/business.cto @@ -0,0 +1,39 @@ +/* + * 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. + */ +namespace composer.business + +import composer.base.Person +import composer.base.Address + +/** + * An abstract participant type in this business network + */ +abstract participant Business { + o Address headquarters optional + o String name optional + --> Manager[] managers optional +} + +abstract participant Employee extends Person { + --> Business employer + --> Manager manager optional + o DateTime startDate optional + o String employmentStatus optional + o String department optional + o String jobRole optional +} + +abstract participant Manager extends Employee { + --> Employee[] directReports optional +} diff --git a/packages/composer-common/test/serializer/complex/vehicle-lifecycle/manufacturer.cto b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/manufacturer.cto new file mode 100644 index 0000000000..ffc07b8d0f --- /dev/null +++ b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/manufacturer.cto @@ -0,0 +1,75 @@ +/* + * 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. + */ + +/** + * Order model for vehicle lifecycle + */ +namespace org.acme.vehicle.lifecycle.manufacturer + +import org.vda.VehicleDetails +import composer.business.Business +import composer.base.Person + + +/** + * Status of an order + */ +enum OrderStatus { + o PLACED + o SCHEDULED_FOR_MANUFACTURE + o VIN_ASSIGNED + o OWNER_ASSIGNED + o DELIVERED +} + +/** + * A manufacturer of vehicles + */ +participant Manufacturer identified by companyId extends Business { + o String companyId +} + +/** + * An order for a vehicle to be fulfilled by a manufacturer + * and dispatched to an orderer (Person). + */ +asset Order identified by orderId { + o String orderId + o VehicleDetails vehicleDetails + o OrderStatus orderStatus + --> Manufacturer manufacturer + --> Person orderer + o UpdateOrderStatus[] statusUpdates optional //TODO (LG): Unit test this +} + +/** + * Transaction to create an order + */ +transaction PlaceOrder { + o String orderId + o VehicleDetails vehicleDetails + --> Manufacturer manufacturer + --> Person orderer +} + +/** + * Transaction to update the status of an order + */ +transaction UpdateOrderStatus { + o OrderStatus orderStatus + o String vin optional + o String v5c optional + o String numberPlate optional + --> Order order +} \ No newline at end of file diff --git a/packages/composer-common/test/serializer/complex/vehicle-lifecycle/odm.cto b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/odm.cto new file mode 100644 index 0000000000..dd69df5f95 --- /dev/null +++ b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/odm.cto @@ -0,0 +1,67 @@ +/* + * 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. + */ + +/** + * ODM specific classes + */ +namespace com.ibm.rules + +asset RuleAppCurrentVersion identified by ruleAppName +{ + // ruleapp name should be of the form / + o String ruleAppName + o String ruleapp_version + o String ruleset_version +} + +asset RuleApp identified by ruleAppId +{ + o String ruleAppId + // ruleapp name should be of the form / + o String ruleAppName + o String ruleapp_version + o String ruleset_version + o String archive +} + +asset Xom identified by xomId +{ + o String xomId + o String xomName + o String xom_version + o String libraryName + o String library_version + o String xom +} + + +transaction RuleAppUpdated +{ + o String resDeployerURL + // ruleapp name should be of the form / + o String ruleAppName + o String ruleapp_version + o String ruleset_version + o String archive + o String managedXomURI +} + +transaction XomUpdated +{ + o String resDeployerURL + o String xomName + o String libraryName + o String library_version + o String xom +} diff --git a/packages/composer-common/test/serializer/complex/vehicle-lifecycle/vda.cto b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/vda.cto new file mode 100644 index 0000000000..7de3125de3 --- /dev/null +++ b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/vda.cto @@ -0,0 +1,150 @@ +/* + * 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. + */ + +/** + * Model based on the DVLA vehicle definition and registration process + */ +namespace org.vda + +import composer.base.Person + +/** + * Based on DVLA document V355/1 + */ +enum TaxClass { + o PRIVATE_LIGHT_GOODS + o PETROL_CAR + o DIESEL_CAR + o ALTERNATIVE_FUEL_CAR + o LIGHT_GOODS_VEHICLE + o EURO4_LIGHT_GOODS_VEHICLE + o EURO5_LIGHT_GOODS_VEHICLE + o HEAVY_GOODS_VEHICLE + o PRIVATE_HEAVY_GOODS_VEHICLE + o SPECIAL_TYPES + o HAULAGE_VEHICLES + o BUS + o MOTORCYCLE + o ELECTRIC_MOTOCYCLE + o SPECIAL_VEHICLES + o SMALL_ISLAND_VEHICLES + o RECOVERY_VEHICLE + o SPECIAL_CONCESSIONARY + o EMERGENCY_VEHICLE + o EXCEMPT_VEHICLE +} + +concept VehicleDetails { + o String make + o String modelType + o String colour + o String vin optional + o String modelVariant optional + o String modelVersion optional + o String bodyType optional + o TaxClass taxationClass optional + o Integer revenueWeight optional + o Integer cylinderCapacity optional + o Double co2 optional // g/km + o String typeOfFuel optional + o Integer numberOfSeats optional + o Integer numberOfStandingPlaces optional + o String wheelPlan optional + o String vehicleCategory optional + o String typeApprovalNumber optional + o Double maxNetPower optional // kW + o String engineNumber optional + o Double maxPermissibleMass optional + o Double massInService optional + o Double powerWeightRatio optional + o TrailerDetails trailerDetails optional + o SoundDetails soundDetails optional + o ExhaustEmissions exhaustEmissions optional +} + +concept TrailerDetails { + o Double maxPermissibleTowableMassBraked + o Double maxPermissibleTowableMassUnbraked +} + +concept SoundDetails { + o Double stationary + o Double engineSpeed + o Double driveBy +} + +concept ExhaustEmissions { + o Double co + o Double hc + o Double nox + o Double hc_plus_nox + o Double particulates +} + +concept VehicleTransferLogEntry { + --> Vehicle vehicle + --> Person buyer + --> Person seller optional + o DateTime timestamp +} + +/** + * Based on the DVLA V62 document + */ +transaction ApplicationForVehicleRegistrationCertificate { + o VehicleDetails vehicleDetails + --> Person keeper + o String dvlaFleetNumber optional + o String driversLicenseNumber optional + o Long mileage optional + o String previousPostCode optional +} + +abstract transaction VehicleTransaction { + --> Vehicle vehicle +} + + +/** + * DVLA V5C + */ +transaction PrivateVehicleTransfer extends VehicleTransaction { + --> Person seller + --> Person buyer + o String specialNotes optional +} + +enum VehicleStatus { + o ACTIVE + o OFF_THE_ROAD + o SCRAPPED +} + +asset Vehicle identified by vin { + o String vin + o VehicleDetails vehicleDetails + o VehicleStatus vehicleStatus + --> Person owner optional + o String numberPlate optional + o String suspiciousMessage optional + o VehicleTransferLogEntry[] logEntries optional +} + +transaction ScrapVehicle extends VehicleTransaction { + o VehicleTransaction[] logEntries optional +} + +transaction UpdateSuspicious extends VehicleTransaction { + o String message +} diff --git a/packages/composer-common/test/serializer/complex/vehicle-lifecycle/vehicle.cto b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/vehicle.cto new file mode 100644 index 0000000000..97c7a8632c --- /dev/null +++ b/packages/composer-common/test/serializer/complex/vehicle-lifecycle/vehicle.cto @@ -0,0 +1,51 @@ +/* + * 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. + */ + +/** + * Vehicle model for vehicle lifecycle + */ +namespace org.acme.vehicle.lifecycle + +import composer.base.Person +import composer.business.Business +import org.vda.PrivateVehicleTransfer + +participant PrivateOwner identified by email extends Person { + o String email +} + +participant Company identified by companyId extends Business { + o String companyId +} + +participant Regulator extends Company { +} + +participant AuctionHouse extends Company { +} + +participant ScrapMerchant extends Company { +} + +transaction SetupDemo { +} + +transaction TestDemo { +} + +// TODO: to be replaced by a generic concept pointing to a Transaction +asset TransactionWrapper identified by wrapperId { + o String wrapperId + o PrivateVehicleTransfer transaction +} \ No newline at end of file diff --git a/packages/composer-runtime/lib/api.js b/packages/composer-runtime/lib/api.js index 18f2e5fb63..d23e78fbe2 100644 --- a/packages/composer-runtime/lib/api.js +++ b/packages/composer-runtime/lib/api.js @@ -215,16 +215,14 @@ class Api { * @method module:composer-runtime#post * @param {string} url The URL to post the data to * @param {Typed} typed The typed instance to be posted. The instance will be serialized to JSON. + * @param {object} options The options that are passed to Serializer.toJSON * @return {Promise} A promise. The promise is resolved with a HttpResponse * that represents the result of the HTTP POST. * @public */ - this.post = function post(url, typed) { + this.post = function post(url, typed, options) { const method = 'post'; LOG.entry(method, url, typed); - const options = {}; - options.convertResourcesToRelationships = true; - options.permitResourcesForRelationships = true; const data = serializer.toJSON(typed, options); LOG.debug(method, typed.getFullyQualifiedType(), data); diff --git a/packages/composer-runtime/test/api.js b/packages/composer-runtime/test/api.js index 96e9185946..971242f67e 100644 --- a/packages/composer-runtime/test/api.js +++ b/packages/composer-runtime/test/api.js @@ -183,16 +183,16 @@ describe('Api', () => { }); it('should make an POST request using the HTTP service', () => { - return api.post('url', transaction) + return api.post('url', transaction, {options: true}) .should.eventually.have.property('foo') .then(() => { - sinon.assert.calledWith(spy, transaction, { convertResourcesToRelationships: true, permitResourcesForRelationships: true, validate: true }); + sinon.assert.calledWith(spy, transaction, { options: true, validate: true }); sinon.assert.calledOnce(mockHTTPService.post); sinon.assert.calledWith(mockHTTPService.post, 'url', { $class: 'org.doge.DogeTransaction', timestamp: '1987-04-12T00:00:00.000Z', transactionId: 'doge1' - }); + } ); }); }); }); From 563d2869376a1116760583073a7e07628bd9f4b6 Mon Sep 17 00:00:00 2001 From: Dan Selman Date: Wed, 26 Jul 2017 17:58:03 +0100 Subject: [PATCH 2/5] Updated changelog after merge --- packages/composer-common/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/composer-common/changelog.txt b/packages/composer-common/changelog.txt index 1944dad18f..67bffc8e6a 100644 --- a/packages/composer-common/changelog.txt +++ b/packages/composer-common/changelog.txt @@ -11,7 +11,7 @@ # # Note that the latest public API is documented using JSDocs and is available in api.txt. # -Version 0.10.1 {d65ff67e642f64b0b7d577a2b63d961e} 2017-07-24 +Version 0.10.1 {d1fd512551ff5bb30b31f05f6817966e} 2017-07-24 - Added InvalidQueryException, BaseFileException - Added IdCard to composer-common package - Added option to Serializer.toJSON to deduplicate resources From 8464a83950c14bd1a0fe6e1cb77bf02116f06436 Mon Sep 17 00:00:00 2001 From: Dan Selman Date: Wed, 26 Jul 2017 18:34:14 +0100 Subject: [PATCH 3/5] updated system test --- packages/composer-systests/systest/data/post.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/composer-systests/systest/data/post.js b/packages/composer-systests/systest/data/post.js index 6837627c9d..75fe40a9b8 100644 --- a/packages/composer-systests/systest/data/post.js +++ b/packages/composer-systests/systest/data/post.js @@ -43,7 +43,7 @@ function onSampleTransaction(sampleTransaction) { function handlePost(postTransaction) { var url = 'https://composer-node-red.mybluemix.net/compute'; - return post( url, postTransaction) + return post( url, postTransaction, {permitResourcesForRelationships: true, deduplicateResources: true}) .then(function (result) { // alert(JSON.stringify(result)); postTransaction.asset.value = 'Count is ' + result.body.sum; From 37131b50526e8f84340ed18c79fe76a71b1281f4 Mon Sep 17 00:00:00 2001 From: Dan Selman Date: Wed, 26 Jul 2017 20:11:06 +0100 Subject: [PATCH 4/5] Added annotation on concepts to ignore --- .../lib/codegen/fromcto/java/javavisitor.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js b/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js index ef190893c2..860279797e 100644 --- a/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js +++ b/packages/composer-common/lib/codegen/fromcto/java/javavisitor.js @@ -203,7 +203,12 @@ public abstract class Resource classDeclaration.getModelFile().getImports().forEach((imported) => { parameters.fileWriter.writeLine(0, 'import ' + imported + ';' ); }); - parameters.fileWriter.writeLine(0, ''); + + if(classDeclaration.isConcept()) { + parameters.fileWriter.writeLine(0, 'import com.fasterxml.jackson.annotation.JsonIgnoreProperties;'); + parameters.fileWriter.writeLine(0, ''); + parameters.fileWriter.writeLine(0, '@JsonIgnoreProperties({"$class"})'); + } let isAbstract = ''; if( classDeclaration.isAbstract() ) { From ee30ff5b50c667b9d17de4742781f156511b7697 Mon Sep 17 00:00:00 2001 From: Dan Selman Date: Wed, 26 Jul 2017 20:12:39 +0100 Subject: [PATCH 5/5] removed dead comment --- packages/composer-common/lib/codegen/jsonwriter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/composer-common/lib/codegen/jsonwriter.js b/packages/composer-common/lib/codegen/jsonwriter.js index 2a8bfb4821..a895d6f6c5 100644 --- a/packages/composer-common/lib/codegen/jsonwriter.js +++ b/packages/composer-common/lib/codegen/jsonwriter.js @@ -112,7 +112,6 @@ class JSONWriter extends Writer { * @param {string} value - the value */ writeKeyStringValue(key,value) { - // this.writeComma(); this.writeKey(key); this.writeStringValue(value); this.firstItem = false;