UNPKG

@warp-works/core

Version:

Core library for WarpWorks

738 lines (656 loc) 26.5 kB
// const debug = require('debug')('W2:models:entity'); const Promise = require('bluebird'); const authorization = require('./../authorization'); const Base = require('./base'); const BasicProperty = require('./basic-property'); const entityOverview = require('./entity-overview'); const Enumeration = require('./enumeration'); const Relationship = require('./relationship'); const utils = require('./../utils'); const WarpWorksError = require('./../error'); const views = require('./views'); class Entity extends Base { constructor(domain, id, name, desc, parentClass, isRootEntity, isRootInstance) { super("Entity", domain, id, name, desc); this.isRootEntity = isRootEntity; this.isRootInstance = isRootInstance; this.isAbstract = false; this.namePlural = name + "s"; this.entityType = this.ENTITY_TYPES.Document; if (isRootEntity) { // Create relationship to rootInstance this.setRootEntityStatus(true); } // Inheritance this.parentClass = parentClass ? [parentClass] : null; // Child elements: this.basicProperties = []; this.enums = []; this.relationships = []; this.pageViews = []; this.tableViews = []; } // eslint-disable-next-line camelcase getParent_Domain() { return this.parent; } setRootEntityStatus(declareAsRootEntity) { if (this.isRootInstance) { throw new WarpWorksError("Can not convert RootInstance to RootEntity!"); } else if (!declareAsRootEntity) { throw new WarpWorksError("Currently not supported, sorry - TBD!"); } else { if (this.isRootEntity) { return; } // Is already a root instance, ignore... var relName = this.namePlural.charAt(0).toUpperCase() + this.namePlural.slice(1); var rel = this.getDomain().getRootInstance().addNewRelationship(this, true, relName); rel.targetMax = '*'; this.isRootEntity = true; } } createNewDefaultViews() { // TBD - Workaround: Remove existing views this.tableViews = []; this.pageViews = []; this.createNewDefaultTableView(); this.createNewDefaultPageView(); this.createNewDefaultPortalView(); } createNewDefaultTableView() { // Create new default table view var newDefaultTableView = this.addNewTableView("DefaultTableView", ""); newDefaultTableView.setAsDefault(); var pos = 0; var properties = this.getBasicProperties(); for (var i in properties) { var property = properties[i]; var tableItem = newDefaultTableView.addNewTableItem(property.name, "Tooltip", property); tableItem.position = pos++; } } createNewDefaultPortalView() { var i; // Create new default page view var newDefaultPageView = this.addNewPageView("DefaultPortalView", ""); // First Tab: properties, enums and associations var assocs = this.getAssociations(); var properties = this.getBasicProperties(); var enums = this.getEnums(); var aggs = this.getAggregations(); var basicCount = properties.length + enums.length + assocs.length + aggs.length; var createdAtLeastOne = false; if (basicCount > 0) { var pos = 0; var item = null; var panel = newDefaultPageView.addNewPanel("Basics", "Properties, Enums, Associations and Aggregations"); panel.position = 0; for (i in properties) { var property = properties[i]; item = panel.addNewBasicPropertyPanelItem(property.name, "Tooltip for " + property.name, property); item.position = pos++; } createdAtLeastOne = properties.length > 0; if (createdAtLeastOne && enums.length > 0) { item = panel.addNewSeparatorPanelItem(); item.position = pos++; } for (i in enums) { var enumeration = enums[i]; item = panel.addNewEnumPanelItem(enumeration.name, "Tooltip for " + enumeration.name, enumeration); item.position = pos++; } createdAtLeastOne = createdAtLeastOne || enums.length > 0; if (createdAtLeastOne && assocs.length > 2) { item = panel.addNewSeparatorPanelItem(); item.position = pos++; } for (i in assocs) { var assoc = assocs[i]; if (assoc.name !== "ReadAccess" && assoc.name !== "WriteAccess") { item = panel.addNewRelationshipPanelItem(assoc.name, "Tooltip for " + assocs[i].name, assocs[i]); item.style = "CSV"; item.position = pos++; } } createdAtLeastOne = createdAtLeastOne || assocs.length > 0; if (createdAtLeastOne && aggs.length > 1) { item = panel.addNewSeparatorPanelItem(); item.position = pos++; } for (i in aggs) { var agg = aggs[i]; if (agg.name !== "Overview") { item = panel.addNewRelationshipPanelItem(agg.name, "Tooltip", aggs[i]); item.style = "CSV"; item.position = pos++; } } } } isDocument() { return this.entityType === this.ENTITY_TYPES.Document; }; /** * Validates if the given `user` has write access to this entity. * * @param {object} persistence - Persistence layer. * @param {object} instance - Entity instance. * @param {object} user - User to validate. See * {@link ./domain.js#authenticateUser|domain.authenticateUser} * for user data format. * @returns {Promise} - If `user` has write access to this entity. The * promise will resolve to a `boolean`. */ canBeEditedBy(persistence, instance, user) { if (!user) { return Promise.resolve(false); } return Promise.reduce( this.getAssociations().filter(authorization.isWriteAccessRelationship), (canWrite, association) => { if (canWrite) { return true; } return Promise.resolve() .then(() => association.getDocuments(persistence, instance)) .then(authorization.hasAnyRoles.bind(null, user.Roles)) .then((canWrite) => { if (canWrite) { return true; } return this.getParent(persistence, instance) .then((parent) => { if (parent) { return parent.entity.canBeEditedBy(persistence, parent.instance, user); } return false; }); }); }, false ); } getOverview(persistence, instance, firstLevelOnly) { return Promise.resolve() .then(() => this.getRelationships()) .then((relationships) => relationships.filter((relationship) => relationship.name === 'Overview')) .then((relationships) => { if (relationships && relationships.length) { return entityOverview( persistence, instance, relationships[relationships.length - 1], firstLevelOnly ? 0 : 3); } return null; }); } createNewDefaultPageView() { var i; var item; var property; // Create new default page view var newDefaultPageView = this.addNewPageView("DefaultPageView", ""); newDefaultPageView.setAsDefault(); newDefaultPageView.label = this.name; // First Tab: properties, enums and associations var assocs = this.getAssociations(); var properties = this.getBasicProperties(); var enums = this.getEnums(); var basicCount = properties.length + enums.length + assocs.length; var createdAtLeastOne = false; if (basicCount > 0) { var pos = 0; item = null; var propertyPanel = newDefaultPageView.addNewPanel("Basics", "Properties, Enums and Associations"); propertyPanel.position = 0; for (i in properties) { property = properties[i]; item = propertyPanel.addNewBasicPropertyPanelItem(property.name, "Tooltip for " + property.name, property); item.position = pos++; } createdAtLeastOne = properties.length > 0; if (createdAtLeastOne && enums.length) { item = propertyPanel.addNewSeparatorPanelItem(); item.position = pos++; } createdAtLeastOne = createdAtLeastOne || enums.length > 0; for (i in enums) { var enumeration = enums[i]; item = propertyPanel.addNewEnumPanelItem(enumeration.name, "Tooltip for " + enumeration.name, enumeration); item.position = pos++; } if (createdAtLeastOne && assocs.length) { item = propertyPanel.addNewSeparatorPanelItem(); item.position = pos++; createdAtLeastOne = true; } for (i in assocs) { item = propertyPanel.addNewRelationshipPanelItem(assocs[i].name, "Tooltip for " + assocs[i].name, assocs[i]); item.style = 'CSV'; item.position = pos++; } } // Next: one tab per relationship var aggs = this.getAggregations(); pos = basicCount > 0 ? 1 : 0; for (i in aggs) { var relationshipPanel = newDefaultPageView.addNewPanel(aggs[i].name, "Tooltip"); relationshipPanel.position = pos++; item = relationshipPanel.addNewRelationshipPanelItem(aggs[i].name, "Tooltip", aggs[i]); item.style = aggs[i].targetEntity[0].entityType === "Embedded" ? "Carousel" : "Table"; item.position = 0; } } canBeInstantiated() { if (this.isRootEntity || this.isRootInstance) { return true; } var parentAggs = this.getAllParentAggregations(); if (parentAggs && parentAggs.length) { return true; } if (this.hasParentClass()) { return this.getParentClass().canBeInstantiated(); } return false; } createTestDocument(createEmbeddedEntities, path) { var testDoc = {}; testDoc.type = this.name; testDoc.path = path; // Basic Properties var properties = this.getBasicProperties(); if (properties && properties.length) { properties.forEach(function(property) { testDoc[property.name] = property.getTestData(); }); } // Enums var enums = this.getEnums(); if (enums && enums.length) { enums.forEach(function(anEnum) { testDoc[anEnum.name] = anEnum.getTestData(); }); } // Embedded Documents if (createEmbeddedEntities) { var ObjectID = require('mongodb').ObjectID; testDoc._id = new ObjectID(); testDoc.embedded = []; var aggs = this.getAggregations(); if (aggs) { // Create dedicated object for each target relationship aggs.forEach(function(reln) { // Only add embedded entities: if (reln.getTargetEntity().isDocument()) { return; } // Determine average number of children var avg = reln.targetAverage; if (isNaN(avg)) { console.log("WARNING: Incomplete Quantity Model - Average for relationship '" + reln.name + "' not defined! Assuming AVG=1"); avg = 1; } var relnContainer = {}; relnContainer.parentRelnID = reln.id; relnContainer.parentRelnName = reln.name; relnContainer.entities = []; for (var i = 0; i < avg; i++) { var nextPath = path + reln.name + ':' + (i + 1) + "/"; var embeddedChild = reln.getTargetEntity().createTestDocument(true, nextPath); relnContainer.entities.push(embeddedChild); } testDoc.embedded.push(relnContainer); }); } } return testDoc; } hasParentClass() { return this.parentClass && this.parentClass.length && this.parentClass[0] != null; } getParentClass() { return this.parentClass[0]; } getBaseClass() { // BaseClass = Topmost, non-abstract class in the inheritance hierarchy var res = this; while (res.hasParentClass() && !res.getParentClass().isAbstract) { res = res.getParentClass(); } if (res.isAbstract) { return null; } return res; } setParentClass(pc) { this.parentClass = [pc]; } getBasicProperties(ignoreInheritedProperties) { if (!ignoreInheritedProperties && this.hasParentClass()) { return this.getParentClass().getBasicProperties().concat(this.basicProperties); } return this.basicProperties; } getEnums(ignoreInheritedEnums) { if (!ignoreInheritedEnums && this.hasParentClass()) { return this.getParentClass().getEnums().concat(this.enums); } return this.enums; } getPageView(viewName) { // Get the last items instead of the first one with `.find()` const foundPageViews = this.getPageViews(/*true*/).filter((pageView) => pageView.name === viewName); if (foundPageViews.length) { return foundPageViews[foundPageViews.length - 1]; } return this.getDefaultPageView(); } getPageViews(ignoreInheritedPageViews) { if (!ignoreInheritedPageViews && this.hasParentClass()) { return this.getParentClass().getPageViews().concat(this.pageViews); } return this.pageViews; } getDefaultPageView() { for (var idx = 0; idx < this.pageViews.length; idx++) { if (this.pageViews[idx].isDefault) { return this.pageViews[idx]; } } if (this.hasParentClass()) { return this.getParentClass().getDefaulPageViews(); } else { return null; } } getTableViews(ignoreInheritedTableViews) { if (!ignoreInheritedTableViews && this.hasParentClass()) { return this.getParentClass().getTableViews().concat(this.tableViews); } return this.tableViews; } getDefaultTableView() { for (var idx = 0; idx < this.tableViews.length; idx++) { if (this.tableViews[idx].isDefault) { return this.tableViews[idx]; } } if (this.hasParentClass()) { return this.getParentClass().getDefaulTableViews(); } else { return null; } } getRelationships(ignoreInheritedRelationships) { if (!ignoreInheritedRelationships && this.hasParentClass()) { return this.getParentClass().getRelationships().concat(this.relationships); } return this.relationships; } getRelationshipByChildName(name){ var relationships = this.getRelationships(); for (var rel in relationships){ if (relationships[rel].targetEntity[0].name === name ){ return relationships[rel] } // if u dont find anything return parentClass else { return getRelationshipByChildName(relentity.parentClass[0].name); } } return null; } getAggregations(ignoreInheritedAggregations) { const a = this.relationships .filter((relationship) => relationship.isAggregation) .map((relationship) => relationship); if (!ignoreInheritedAggregations && this.hasParentClass()) { return this.getParentClass().getAggregations().concat(a); } return a; } getAssociations(ignoreIngeritedAssociations) { var a = []; for (var i in this.relationships) { if (!this.relationships[i].isAggregation) { a.push(this.relationships[i]); } } if (!ignoreIngeritedAssociations && this.hasParentClass()) { return a.concat(this.getParentClass().getAssociations()); } return a; } // TBD: What about multiple levels of inheritance...? (eg C is B, B is A?) getAllDerivedEntities() { // Return all entities that inherit from this entity var domain = this.parent; var derivedEntities = []; for (var i in domain.entities) { var entity = domain.entities[i]; if (entity.hasParentClass()) { var parent = entity.getParentClass(); if (this.compareToMyID(parent.id)) { derivedEntities.push(entity); } } } return derivedEntities; } // TBD - support inheritance! getAllParentAggregations() { // Return all aggregations which link to this entity (returns the aggregation, not the parent entity!) var domain = this.parent; var parentAggs = []; var entities = domain.getEntities(); for (var i in entities) { var entity = entities[i]; var aggRels = entity.getAggregations(); for (var k in aggRels) { var rel = aggRels[k]; if (rel.hasTargetEntity() && this.compareToMyID(rel.getTargetEntity().id)) { parentAggs.push(rel); } } } return parentAggs; } getChildInstances(persistence,id) { return Promise.resolve(this.getDocuments(persistence, {parentID: id}, true)); } processLocalTemplateFunctions(template) { var children = [ // Without parent elements... ["BasicProperty", this.getBasicProperties(true)], ["Enumeration", this.getEnums(true)], ["Relationship", this.getRelationships(true)], ["Aggregation", this.getAggregations(true)], ["Association", this.getAssociations(true)], ["PageView", this.getPageViews(true)], ["TableView", this.getTableViews(true)], // ...and the same *with* parent elements: ["BasicProperty!", this.getBasicProperties(false)], ["Enumeration!", this.getEnums(false)], ["Relationship!", this.getRelationships(false)], ["Aggregation!", this.getAggregations(false)], ["Association!", this.getAssociations(false)], ["PageView!", this.getPageViews(false)], ["TableView!", this.getTableViews(false)] // Notice that the !-operator can be combined with the ?-operator // Example: {{Enumeration!?}}...{{Enumeration!}}...{{/Enumeration!}}...{{/Enumeration!?}} ]; template = this.processTemplateWithChildElements(template, children); return super.processLocalTemplateFunctions(template); } addNewBasicProperty(name, desc, propertyType) { var id = this.getDomain().createNewID(); var newBasicProperty = new BasicProperty(this, id, name, desc, propertyType); this.basicProperties.push(newBasicProperty); return newBasicProperty; } addNewEnum(name, desc) { var id = this.getDomain().createNewID(); var newEnum = new Enumeration(this, id, name, desc); this.enums.push(newEnum); return newEnum; } addNewRelationship(target, isAggregation, name) { var id = this.getDomain().createNewID(); if (!name) { name = target.namePlural; } var newRelationship = new Relationship(this, target, id, isAggregation, name); this.relationships.push(newRelationship); return newRelationship; } addNewPageView(name, desc) { var id = this.getDomain().createNewID(); var newPageView = new views.PageView(this, id, name, desc); this.pageViews.push(newPageView); return newPageView; } addNewTableView(name, desc) { var id = this.getDomain().createNewID(); var newTableView = new views.TableView(this, id, name, desc); this.tableViews.push(newTableView); return newTableView; } getAllElements(includeSelf) { var i; var r = []; if (includeSelf) { r = r.concat(this); } // Add children with no own children directly: r = r.concat(this.relationships); r = r.concat(this.basicProperties); // Children with children: for (i in this.enums) { r = r.concat(this.enums[i].getAllElements(true)); } for (i in this.pageViews) { r = r.concat(this.pageViews[i].getAllElements(true)); } for (i in this.tableViews) { r = r.concat(this.tableViews[i].getAllElements(true)); } return r; } createDocument(persistence, instance) { return persistence.save(this.getBaseClass().name, instance); } removeDocument(persistence, instance) { return persistence.remove(this.getBaseClass().name, instance); } updateDocument(persistence, document) { } toString(t) { var comma; var i; var isFirst; var j; var result = ""; if (!t) { return result; } var name = (this.isRootInstance ? '#' : '') + this.name; switch (t) { case Entity.TO_STRING_TYPES.PROPERTIES: result += this.hasParentClass() ? "(" + this.getParentClass().name + ")" : ""; if (this.enums.length || this.basicProperties.length) { result += ": "; } for (i in this.basicProperties) { result += (i > 0 ? ", " : "") + this.basicProperties[i].toString(); } if (this.enums.length) { result += ", "; } for (j in this.enums) { result += (j > 0 ? ", " : "") + this.enums[j].toString(); } return name + result; case Entity.TO_STRING_TYPES.AGGREGATIONS: isFirst = true; for (i in this.relationships) { if (this.relationships[i].isAggregation) { comma = isFirst ? "" : ", "; result += comma + this.relationships[i].toString(); isFirst = false; } } return result.length ? name + ": { " + result + " }" : ""; case Entity.TO_STRING_TYPES.ASSOCIATIONS: isFirst = true; for (i in this.relationships) { if (!this.relationships[i].isAggregation) { comma = isFirst ? "" : ", "; result += comma + this.relationships[i].toString(); isFirst = false; } } return result.length ? name + ": " + result : ""; } throw new WarpWorksError("Invalid option: " + t); } toJSON() { return { name: this.name, desc: this.desc, type: this.type, id: this.idToJSON(), isRootEntity: this.isRootEntity, isRootInstance: this.isRootInstance, isAbstract: this.isAbstract, entityType: this.entityType, namePlural: this.namePlural, parentClass: this.hasParentClass() ? [this.getParentClass().id] : [], basicProperties: utils.mapJSON(this.basicProperties), enums: utils.mapJSON(this.enums), relationships: utils.mapJSON(this.relationships), pageViews: utils.mapJSON(this.pageViews), tableViews: utils.mapJSON(this.tableViews) }; } createChildForInstance(instance, relationship) { return { type: relationship.getTargetEntity().name, parentID: instance.id, parentRelnID: relationship.id, parentRelnName: relationship.name, parentBaseClassID: this.id, parentBaseClassName: this.name }; } getRelationshipByName(relationshipName) { const relationships = this.getRelationships().filter((rel) => rel.name.toLowerCase() === relationshipName.toLowerCase()); return relationships.pop(); // Get latest one in case of inheritance. } getRelationshipByChildName(name){ var relationships = this.getRelationships(); for (var rel in relationships){ if (relationships[rel].targetEntity[0].name === name ){ return relationships[rel] } } // if u dont find anything return parentClass if (typeof(this.getDomain().getEntityByName(name).parentClass[0]) != "undefined"){ return this.getRelationshipByChildName(this.getDomain().getEntityByName(name).parentClass[0].name); } return null; } } Entity.TO_STRING_TYPES = { PROPERTIES: 'properties', AGGREGATIONS: 'aggregations', ASSOCIATIONS: 'associations' }; Entity.prototype.ENTITY_TYPES = { Document: "Document", Embedded: "Embedded" }; module.exports = Entity;