Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit edbe5ea

Browse files
authored
#580 Cannot create an asset (#634)
* UI nitpicks 11 and 12 * Order the projects retrieved from GitHub * Fix unit test * #417 stop UI from jumping around when updating * nitpicks 1 and 7 in issue #496 * nitpick 5 * abstract type in model changes
1 parent b7a3027 commit edbe5ea

File tree

11 files changed

+134
-40
lines changed

11 files changed

+134
-40
lines changed

packages/composer-common/lib/factory.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@
1616

1717
const debug = require('debug')('ibm-concerto');
1818
const Globalize = require('./globalize');
19+
1920
const InstanceGenerator = require('./serializer/instancegenerator');
2021
const ValueGeneratorFactory = require('./serializer/valuegenerator');
21-
const Relationship = require('./model/relationship');
22+
const ResourceValidator = require('./serializer/resourcevalidator');
23+
const TypedStack = require('./serializer/typedstack');
2224

25+
const Relationship = require('./model/relationship');
2326
const Resource = require('./model/resource');
2427
const ValidatedResource = require('./model/validatedresource');
25-
2628
const Concept = require('./model/concept');
2729
const ValidatedConcept = require('./model/validatedconcept');
2830

29-
const ResourceValidator = require('./serializer/resourcevalidator');
3031
const TransactionDeclaration = require('./introspect/transactiondeclaration');
31-
const TypedStack = require('./serializer/typedstack');
32+
3233
const uuid = require('uuid');
3334

35+
3436
/**
3537
* Use the Factory to create instances of Resource: transactions, participants
3638
* and assets.
@@ -84,7 +86,6 @@ class Factory {
8486
* @throws {ModelException} if the type is not registered with the ModelManager
8587
*/
8688
newResource(ns, type, id, options) {
87-
8889
if(!id || typeof(id) !== 'string') {
8990
let formatter = Globalize.messageFormatter('factory-newinstance-invalididentifier');
9091
throw new Error(formatter({
@@ -119,9 +120,12 @@ class Factory {
119120
}
120121

121122
let classDecl = modelFile.getType(type);
122-
123123
if(classDecl.isAbstract()) {
124-
throw new Error('Cannot create abstract type ' + classDecl.getFullyQualifiedName());
124+
let formatter = Globalize.messageFormatter('factory-newinstance-abstracttype');
125+
throw new Error(formatter({
126+
namespace: ns,
127+
type: type
128+
}));
125129
}
126130

127131
if(classDecl.isConcept()) {
@@ -131,10 +135,10 @@ class Factory {
131135
let newObj = null;
132136
options = options || {};
133137
if(options.disableValidation) {
134-
newObj = new Resource(this.modelManager,ns,type,id);
138+
newObj = new Resource(this.modelManager, ns, type, id);
135139
}
136140
else {
137-
newObj = new ValidatedResource(this.modelManager,ns,type,id, new ResourceValidator());
141+
newObj = new ValidatedResource(this.modelManager, ns, type, id, new ResourceValidator());
138142
}
139143
newObj.assignFieldDefaults();
140144

@@ -153,7 +157,6 @@ class Factory {
153157
// if we have an identifier, we set it now
154158
let idField = classDecl.getIdentifierFieldName();
155159
newObj[idField] = id;
156-
157160
debug('Factory.newResource created %s', id );
158161
return newObj;
159162
}
@@ -191,7 +194,11 @@ class Factory {
191194
let classDecl = modelFile.getType(type);
192195

193196
if(classDecl.isAbstract()) {
194-
throw new Error('Cannot create abstract type ' + classDecl.getFullyQualifiedName());
197+
let formatter = Globalize.messageFormatter('factory-newinstance-abstracttype');
198+
throw new Error(formatter({
199+
namespace: ns,
200+
type: type
201+
}));
195202
}
196203

197204
if(!classDecl.isConcept()) {

packages/composer-common/lib/serializer/instancegenerator.js

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
const ClassDeclaration = require('../introspect/classdeclaration');
1818
const EnumDeclaration = require('../introspect/enumdeclaration');
19+
const Introspector = require('../introspect/introspector');
1920
const Field = require('../introspect/field');
2021
const leftPad = require('left-pad');
2122
const ModelUtil = require('../modelutil');
2223
const RelationshipDeclaration = require('../introspect/relationshipdeclaration');
2324
const Util = require('../util');
2425
const ValueGeneratorFactory = require('./valuegenerator');
26+
const Globalize = require('../globalize');
2527

2628
/**
2729
* Generate sample instance data for the specified class declaration
@@ -80,6 +82,7 @@ class InstanceGenerator {
8082
* @private
8183
*/
8284
visitField(field, parameters) {
85+
8386
if (field.isArray()) {
8487
let result = [];
8588
for (let i = 0; i < 3; i++) {
@@ -121,9 +124,17 @@ class InstanceGenerator {
121124
let enumValues = classDeclaration.getOwnProperties();
122125
return enumValues[Math.floor(Math.random() * enumValues.length)].getName();
123126
}
124-
else if (classDeclaration.isConcept()) {
125-
let resource = parameters.factory.newConcept(classDeclaration.getModelFile().getNamespace(), classDeclaration.getName());
126-
parameters.stack.push(resource);
127+
else if (classDeclaration.isAbstract) {
128+
let newClassDecl = this.findExtendingLeafType(classDeclaration);
129+
if(newClassDecl !== null) {
130+
classDeclaration = newClassDecl;
131+
type = newClassDecl.getName();
132+
}
133+
}
134+
135+
if (classDeclaration.isConcept()) {
136+
let concept = parameters.factory.newConcept(classDeclaration.getModelFile().getNamespace(), classDeclaration.getName());
137+
parameters.stack.push(concept);
127138
return classDeclaration.accept(this, parameters);
128139
} else {
129140
let identifierFieldName = classDeclaration.getIdentifierFieldName();
@@ -136,6 +147,47 @@ class InstanceGenerator {
136147
}
137148
}
138149

150+
/**
151+
* Find a type that extends the provided abstract type and return it.
152+
* TODO: work out whether this has to be a leaf node or whether the closest type can be used
153+
* It depends really since the closest type will satisfy the model but whether it satisfies
154+
* any transaction code which attempts to use the generated resource is another matter.
155+
* @param {any} inputType the class declaration.
156+
* @return {any} the closest extending concrete class definition - null if none are found.
157+
*/
158+
findExtendingLeafType(inputType) {
159+
let modelManager = inputType.getModelFile().getModelManager();
160+
let returnType = null;
161+
if(inputType.isAbstract()) {
162+
let introspector = new Introspector(modelManager);
163+
let allClassDeclarations = introspector.getClassDeclarations();
164+
let contenders = [];
165+
allClassDeclarations.forEach((classDecl) => {
166+
let superType = classDecl.getSuperType();
167+
if(!classDecl.isAbstract() && (superType !== null)) {
168+
if(superType === inputType.getFullyQualifiedName()) {
169+
contenders.push(classDecl);
170+
}
171+
}
172+
});
173+
174+
if(contenders.length > 0) {
175+
returnType = contenders[0];
176+
} else {
177+
let formatter = Globalize.messageFormatter('instancegenerator-newinstance-noconcreteclass');
178+
throw new Error(formatter({
179+
type: inputType.getFullyQualifiedName()
180+
}));
181+
}
182+
} else {
183+
// we haven't been given an abstract type so just return what we were given.
184+
returnType = inputType;
185+
}
186+
return returnType;
187+
}
188+
189+
190+
139191
/**
140192
* Visitor design pattern
141193
* @param {RelationshipDeclaration} relationshipDeclaration - the object being visited

packages/composer-common/lib/serializer/resourcevalidator.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,6 @@ class ResourceValidator {
395395
if(!message) {
396396
message = '';
397397
}
398-
399-
console.log('[' + callSite + '] ' + message );
400398
}
401399
}
402400

packages/composer-common/messages/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,13 @@
3838
"factory-newinstance-typenotdeclaredinns": "Type {type} is not declared in namespace {namespace}",
3939
"factory-newinstance-missingidentifier": "Missing identifier for Type {type} in namespace {namespace}",
4040
"factory-newinstance-invalididentifier": "Invalid or missing identifier for Type {type} in namespace {namespace}",
41+
"factory-newinstance-abstracttype": "Cannot instantiate Abstract Type {type} in namespace {namespace}",
4142

4243
"factory-newrelationship-notregisteredwithmm": "ModelFile for namespace {namespace} has not been registered with the ModelManager",
4344
"factory-newrelationship-typenotdeclaredinns": "Type {type} is not declared in namespace {namespace}",
4445

46+
"instancegenerator-newinstance-noconcreteclass": "No concrete extending type for {type}",
47+
4548
"modelmanager-resolvetype-nonsfortype": "No registered namespace for type {type} in {context}",
4649
"modelmanager-resolvetype-notypeinnsforcontext": "No type {type} in namespace {namespace} for {context}",
4750

packages/composer-common/test/factory.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe('Factory', () => {
140140
it('should throw if concept is abstract', () => {
141141
(() => {
142142
factory.newConcept('org.acme.test', 'AbstractConcept');
143-
}).should.throw(/Cannot create abstract type org.acme.test.AbstractConcept/);
143+
}).should.throw(/Cannot instantiate Abstract Type AbstractConcept in namespace org.acme.test/);
144144
});
145145

146146
it('should create a new concept', () => {

packages/composer-common/test/models/model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ describe('Model Tests', function(){
6666
let factory = new Factory(modelManager);
6767

6868
// attempt to create an abstract asset
69-
assert.throws( function() {factory.newResource('org.acme.base', 'AbstractAsset', '123' );}, /.+Cannot create abstract type org.acme.base.AbstractAsset/, 'did not throw with expected message');
69+
assert.throws( function() {factory.newResource('org.acme.base', 'AbstractAsset', '123' );}, /.+Cannot instantiate Abstract Type AbstractAsset in namespace org.acme.base/, 'did not throw with expected message');
7070

7171
// create a new instance
7272
let resource = factory.newResource(

packages/composer-common/test/serializer/instancegenerator.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,38 @@ describe('InstanceGenerator', () => {
277277
resource.inheritedValue.should.be.a('string');
278278
});
279279

280+
it('should generate a concrete class for an abstract type if one is available', () => {
281+
let resource = test(`namespace org.acme.test
282+
abstract concept BaseConcept {
283+
o String inheritedValue
284+
}
285+
concept MyConcept extends BaseConcept {
286+
o String concreteConceptValue
287+
}
288+
asset MyAsset identified by id {
289+
o String id
290+
o BaseConcept aConcept
291+
}`);
292+
resource.aConcept.$type.should.match(/^MyConcept$/);
293+
});
294+
295+
it('should throw an error when trying to generate a resource from a model that uses an Abstract type with no concrete Implementing type', () => {
296+
try {
297+
test(`namespace org.acme.test
298+
abstract concept BaseConcept {
299+
o String inheritedValue
300+
}
301+
asset MyAsset identified by id {
302+
o String id
303+
o BaseConcept aConcept
304+
}`);
305+
} catch (error) {
306+
error.should.match(/^Error: No concrete extending type for org.acme.test.BaseConcept$/);
307+
}
308+
});
309+
310+
311+
280312
});
281313

282314
});

packages/composer-playground/src/app/resource/resource.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ <h2>In registry: <b>{{registryID}}</b></h2>
1717
<div class="resource-bound">
1818
<codemirror [(ngModel)]="resourceDefinition" [config]="codeConfig" (ngModelChange)="onDefinitionChanged()" width="100%" height="100%">
1919
</codemirror>
20-
<div class="resource-error-text" ng-if="defitionError!=null">
21-
<p>{{defitionError}}</p>
20+
<div class="resource-error-text" ng-if="definitionError!=null">
21+
<p>{{definitionError}}</p>
2222
</div>
2323
</div>
2424
</section>
@@ -29,7 +29,7 @@ <h2>In registry: <b>{{registryID}}</b></h2>
2929
<button type="button" class="secondary" (click)="activeModal.close();">
3030
<span>Cancel</span>
3131
</button>
32-
<button type="button" class="primary" (click)="addOrUpdateResource()" [disabled]="defitionError!=null || actionInProgress ">
32+
<button type="button" class="primary" (click)="addOrUpdateResource()" [disabled]="definitionError!=null || actionInProgress ">
3333
<div *ngIf="!actionInProgress">
3434
<span>{{resourceAction}}</span>
3535
</div>

packages/composer-playground/src/app/resource/resource.component.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ resource-modal {
3131

3232
.resource-error-text{
3333
min-height: 30px;
34+
color: $error-colour-1;
3435
max-width: 600px;
3536
}
3637
}

packages/composer-playground/src/app/resource/resource.component.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ describe('ResourceComponent', () => {
217217
};
218218
component['resourceDeclaration'] = mockClassDeclaration;
219219
component['generateResource']();
220-
should.exist(component['defitionError']);
220+
should.exist(component['definitionError']);
221221
});
222222
});
223223

@@ -288,7 +288,7 @@ describe('ResourceComponent', () => {
288288

289289
component['addOrUpdateResource']();
290290
tick();
291-
should.exist(component['defitionError']);
291+
should.exist(component['definitionError']);
292292
component['actionInProgress'].should.be.false;
293293
}));
294294
});
@@ -304,13 +304,13 @@ describe('ResourceComponent', () => {
304304
mockSerializer.fromJSON.should.be.called;
305305
mockSerializer.fromJSON.should.be.calledWith({'$class': 'org.acme'});
306306
mockResource.validate.should.be.called;
307-
should.not.exist(component['defitionError']);
307+
should.not.exist(component['definitionError']);
308308
});
309309

310310
it('should set definitionError', () => {
311311
component['resourceDefinition'] = 'will error';
312312
component['onDefinitionChanged']();
313-
should.exist(component['defitionError']);
313+
should.exist(component['definitionError']);
314314
});
315315
});
316316

0 commit comments

Comments
 (0)