package com.eas.model; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.eas.client.AppClient; import com.eas.client.CallbackAdapter; import com.eas.client.metadata.Field; import com.eas.client.metadata.Fields; import com.eas.core.Callable; import com.eas.core.HasPublished; import com.eas.core.Utils; import com.eas.core.Utils.JsObject; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; /** * @author mg */ public class Model implements HasPublished { protected AppClient client; protected Set<Relation> relations = new HashSet<Relation>(); protected Set<ReferenceRelation> referenceRelations = new HashSet<ReferenceRelation>(); protected Map<String, Entity> entities = new HashMap<String, Entity>(); protected JsObject changeLog = JavaScriptObject.createArray().cast(); // protected RequeryProcess process; protected JavaScriptObject jsPublished; public static class RequeryProcess { public Map<Entity, String> errors = new HashMap<Entity, String>(); public Callback<JavaScriptObject, String> callback; public RequeryProcess(Callback<JavaScriptObject, String> aCallback) { super(); callback = aCallback; assert callback != null : "aCallback argument is required."; } protected String assembleErrors() { if (errors != null && !errors.isEmpty()) { StringBuilder sb = new StringBuilder(); for (Entity entity : errors.keySet()) { if (sb.length() > 0) sb.append("\n"); sb.append(errors.get(entity)).append(" (").append(entity.getName()).append("[ ").append(entity.getTitle()).append("])"); } return sb.toString(); } return null; } public void cancel() throws Exception { callback.onFailure("Canceled"); } public void success() { callback.onSuccess(null); } public void failure() { callback.onFailure(assembleErrors()); } public void end() { if (errors.isEmpty()) { success(); } else { failure(); } } } public static class SimpleEntry implements Entry<Entity, Integer> { protected Entity key; protected Integer value; private SimpleEntry(Entity aKey, Integer aValue) { super(); key = aKey; value = aValue; } @Override public Entity getKey() { return key; } @Override public Integer getValue() { return value; } @Override public Integer setValue(Integer aValue) { Integer oldValue = value; value = aValue; return oldValue; } } public Model copy() throws Exception { Model copied = new Model(client); for (Entity entity : entities.values()) { copied.addEntity(entity.copy()); } for (Relation relation : relations) { Relation rcopied = relation.copy(); resolveCopiedRelation(rcopied, copied); copied.addRelation(rcopied); } for (ReferenceRelation relation : referenceRelations) { ReferenceRelation rcopied = (ReferenceRelation) relation.copy(); resolveCopiedRelation(rcopied, copied); copied.addRelation(rcopied); } return copied; } protected void resolveCopiedRelation(Relation aRelation, Model aModel) throws Exception { if (aRelation.getLeftEntity() != null) { aRelation.setLeftEntity(aModel.getEntityById(aRelation.getLeftEntity().getEntityId())); } if (aRelation.getRightEntity() != null) { aRelation.setRightEntity(aModel.getEntityById(aRelation.getRightEntity().getEntityId())); } if (aRelation.getLeftField() != null) { if (aRelation.getLeftEntity() != null) { if (aRelation.isLeftParameter() && aRelation.getLeftEntity().getQueryName() != null) { aRelation.setLeftField(aRelation.getLeftEntity().getQuery().getParameters().get(aRelation.getLeftField().getName())); } else { aRelation.setLeftField(aRelation.getLeftEntity().getFields().get(aRelation.getLeftField().getName())); } } else { aRelation.setLeftField(null); } } if (aRelation.getRightField() != null) { if (aRelation.getRightEntity() != null) { if (aRelation.isRightParameter() && aRelation.getRightEntity().getQueryName() != null) { aRelation.setRightField(aRelation.getRightEntity().getQuery().getParameters().get(aRelation.getRightField().getName())); } else { aRelation.setRightField(aRelation.getRightEntity().getFields().get(aRelation.getRightField().getName())); } } else { aRelation.setRightField(null); } } } public void checkRelationsIntegrity() { List<Relation> toDel = new ArrayList<Relation>(); for (Relation rel : relations) { if (rel.getLeftEntity() == null || (rel.getLeftField() == null && rel.getLeftParameter() == null) || rel.getRightEntity() == null || (rel.getRightField() == null && rel.getRightParameter() == null)) { toDel.add(rel); } } for (Relation rel : toDel) { removeRelation(rel); } checkReferenceRelationsIntegrity(); } protected void checkReferenceRelationsIntegrity() { List<ReferenceRelation> toDel = new ArrayList<ReferenceRelation>(); for (ReferenceRelation rel : referenceRelations) { if (rel.getLeftEntity() == null || (rel.getLeftField() == null && rel.getLeftParameter() == null) || rel.getRightEntity() == null || (rel.getRightField() == null && rel.getRightParameter() == null)) { toDel.add(rel); } } for (ReferenceRelation rel : toDel) { referenceRelations.remove(rel); } } /** * Base model constructor. */ protected Model() { super(); } /** * Constructor of datamodel. Used in designers. * * @param aClient * C instance all queries to be sent to. * @see AppClient */ public Model(AppClient aClient) { this(); client = aClient; } public AppClient getClient() { return client; } public void setClient(AppClient aValue) { client = aValue; } public Set<ReferenceRelation> getReferenceRelations() { return Collections.unmodifiableSet(referenceRelations); } public boolean isPending() { for (Entity entity : entities.values()) { if (entity.isPending()) return true; } return false; } public void addRelation(Relation aRel) { if (aRel instanceof ReferenceRelation) { referenceRelations.add((ReferenceRelation) aRel); } else { relations.add(aRel); Entity lEntity = aRel.getLeftEntity(); Entity rEntity = aRel.getRightEntity(); if (lEntity != null && rEntity != null) { lEntity.addOutRelation(aRel); rEntity.addInRelation(aRel); } } } public Set<Relation> collectRelationsByEntity(Entity aEntity) { return aEntity.getInOutRelations(); } @Override public JavaScriptObject getPublished() { return jsPublished; } @Override public void setPublished(JavaScriptObject aValue) { if (jsPublished != aValue) { jsPublished = aValue; publish(); } } private void publish() { try { publishTopLevelFacade(jsPublished, this); publishEntities(); } catch (Exception ex) { Logger.getLogger(Model.class.getName()).log(Level.SEVERE, null, ex); } } private void publishEntities() throws Exception { assert jsPublished != null : "JavaScript facade object has to be already installed while publishing rowsets facades."; validateQueries(); // for (Entity entity : entities.values()) { if (entity.getName() != null && !entity.getName().isEmpty()) { JavaScriptObject publishedEntity = publishEntity(entity); entity.setPublished(publishedEntity); jsPublished.<JsObject> cast().inject(entity.getName(), publishedEntity, true, false); } } // for (ReferenceRelation aRelation : referenceRelations) { String scalarPropertyName = aRelation.getScalarPropertyName(); String collectionPropertyName = aRelation.getCollectionPropertyName(); if (scalarPropertyName != null && !scalarPropertyName.isEmpty()) { aRelation.getLeftEntity().putOrmScalarDefinition( scalarPropertyName, new Fields.OrmDef(aRelation.getLeftField().getName(), scalarPropertyName, collectionPropertyName, ormPropertiesDefiner.scalar( aRelation.getRightEntity().getPublished(), aRelation.getRightField().getName(), aRelation.getLeftField().getName()))); } if (collectionPropertyName != null && !collectionPropertyName.isEmpty()) { aRelation.getRightEntity().putOrmCollectionDefinition( collectionPropertyName, new Fields.OrmDef(collectionPropertyName, scalarPropertyName, ormPropertiesDefiner.collection( aRelation.getLeftEntity().getPublished(), aRelation.getRightField().getName(), aRelation.getLeftField().getName()))); } } } protected native JavaScriptObject publishEntity(Entity nEntity)/*-{ var B = @com.eas.core.Predefine::boxing; var Logger = @com.eas.core.Predefine::logger; var M = @com.eas.model.JsModel::managed; var Orderer = @com.eas.model.JsModel::orderer; function Insert(aEntityName){ this.kind = 'insert'; this.entity = aEntityName; this.data = {}; } function Delete(aEntityName){ this.kind = 'delete'; this.entity = aEntityName; this.keys = {}; } function Update(aEntityName){ this.kind = 'update'; this.entity = aEntityName; this.keys = {}; this.data = {}; } function fireSelfScalarsOppositeCollectionsChanges(aSubject, aChange, nFields) { var expandingsOldValues = aChange.beforeState.selfScalarsOldValues; var ormDefs = nFields.@com.eas.client.metadata.Fields::forEachOrmScalarExpandings(Ljava/lang/String;Lcom/eas/core/Utils$JsObject;)(aChange.propertyName, function(ormDef){ var ormDefName = ormDef.@com.eas.client.metadata.Fields.OrmDef::getName()(); if (ormDefName) { var ormDefOppositeName = ormDef.@com.eas.client.metadata.Fields.OrmDef::getOppositeName()(); var expandingOldValue = expandingsOldValues[ormDefName]; var expandingNewValue = aSubject[ormDefName]; @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(aSubject, {source: aChange.source, propertyName: ormDefName, oldValue: expandingOldValue, newValue: expandingNewValue}); if (ormDefOppositeName) { if (expandingOldValue) { @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(expandingOldValue, {source: expandingOldValue, propertyName: ormDefOppositeName}); } if (expandingNewValue) { @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(expandingNewValue, {source: expandingNewValue, propertyName: ormDefOppositeName}); } } } }); } function prepareSelfScalarsChanges(aSubject, aChange, nFields) { var oldScalarValues = []; nFields.@com.eas.client.metadata.Fields::forEachOrmScalarExpandings(Ljava/lang/String;Lcom/eas/core/Utils$JsObject;)(aChange.propertyName, function(ormDef){ var ormDefName = ormDef.@com.eas.client.metadata.Fields.OrmDef::getName()(); if (ormDef && ormDefName) { oldScalarValues[ormDefName] = aSubject[ormDefName]; } }); return oldScalarValues; } function fireOppositeScalarsSelfCollectionsChanges(aSubject, aChange, nFields) { var oppositeScalarsFirerers = aChange.beforeState.oppositeScalarsFirerers; if (oppositeScalarsFirerers) { oppositeScalarsFirerers.forEach(function (aFirerer) { aFirerer(); }); } var collectionsDefs = nFields.@com.eas.client.metadata.Fields::getOrmCollectionsDefinitions()().@java.util.Map::entrySet()(); if (collectionsDefs) { var collectionsDefsIt = collectionsDefs.@java.util.Set::iterator()(); while(collectionsDefsIt.@java.util.Iterator::hasNext()()){ var aEntry = collectionsDefsIt.@java.util.Iterator::next()(); var collectionName = aEntry.@java.util.Map.Entry::getKey()(); var ormDef = aEntry.@java.util.Map.Entry::getValue()(); var collection = aSubject[collectionName]; collection.forEach(function (item) { @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(item, {source: item, propertyName: ormDef.@com.eas.client.metadata.Fields.OrmDef::getOppositeName()()}); }); }; collectionsDefsIt = collectionsDefs.@java.util.Set::iterator()(); while(collectionsDefsIt.@java.util.Iterator::hasNext()()) { aEntry = collectionsDefsIt.@java.util.Iterator::next()(); var collectionName = aEntry.@java.util.Map.Entry::getKey()(); @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(aSubject, {source: aSubject, propertyName: collectionName}); }; } } function prepareOppositeScalarsChanges(aSubject, nFields) { var firerers = []; var collectionsDefs = nFields.@com.eas.client.metadata.Fields::getOrmCollectionsDefinitions()().@java.util.Map::entrySet()(); var collectionsDefsIt = collectionsDefs.@java.util.Set::iterator()(); while(collectionsDefsIt.@java.util.Iterator::hasNext()()){ var aEntry = collectionsDefsIt.@java.util.Iterator::next()(); var collectionName = aEntry.@java.util.Map.Entry::getKey()(); var ormDef = aEntry.@java.util.Map.Entry::getValue()(); var collection = aSubject[collectionName]; collection.forEach(function (item) { var ormDefOppositeName = ormDef.@com.eas.client.metadata.Fields.OrmDef::getOppositeName()(); if (ormDefOppositeName) { firerers.push(function () { @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(item, {source: item, propertyName: ormDefOppositeName}); }); } }); }; return firerers; } function fireOppositeScalarsChanges(aSubject, nFields) { var collected = prepareOppositeScalarsChanges(aSubject, nFields); collected.forEach(function (aFirerer) { aFirerer(); }); } function fireOppositeCollectionsChanges(aSubject, nFields) { var scalarsDefs = nFields.@com.eas.client.metadata.Fields::getOrmScalarDefinitions()().@java.util.Map::entrySet()(); var scalarsDefsIt = scalarsDefs.@java.util.Set::iterator()(); while(scalarsDefsIt.@java.util.Iterator::hasNext()()){ var aEntry = scalarsDefsIt.@java.util.Iterator::next()(); var scalarName = aEntry.@java.util.Map.Entry::getKey()(); if (scalarName) { var ormDef = aEntry.@java.util.Map.Entry::getValue()(); var scalar = aSubject[scalarName]; var ormDefOppositeName = ormDef.@com.eas.client.metadata.Fields.OrmDef::getOppositeName()(); if (scalar && ormDefOppositeName) { @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(scalar, {source: scalar, propertyName: ormDefOppositeName}); } } }; } function generateChangeLogKeys(nKeys, nFields, propName, aSubject, oldValue) { if (nFields) { for (var i = 1; i <= nFields.@com.eas.client.metadata.Fields::getFieldsCount()(); i++) { var nField = nFields.@com.eas.client.metadata.Fields::get(I)(i); if (nField.@com.eas.client.metadata.Field::isPk()()) { var fieldName = nField.@com.eas.client.metadata.Field::getName()(); var value = aSubject[fieldName]; // Some tricky processing of primary keys modification case ... if (fieldName == propName) { value = oldValue; } nKeys[fieldName] = value; } } } } var justInserted = null; var justInsertedChange = null; var orderers = {}; var published = []; var changeLog = nEntity.@com.eas.model.Entity::getChangeLog()(); var _onChange = null; function managedOnChange(aSubject, aChange) { var nField = noFields[aChange.propertyName]; if (!tryToComplementInsert(aSubject, aChange)) { var updateChange = new Update(nEntity.@com.eas.model.Entity::getQueryName()()); generateChangeLogKeys(updateChange.keys, nFields, aChange.propertyName, aSubject, aChange.oldValue); updateChange.data[aChange.propertyName] = aChange.newValue; changeLog.push(updateChange); } Object.keys(orderers).forEach(function (aOrdererKey) { var aOrderer = orderers[aOrdererKey]; if (aOrderer.inKeys(aChange.propertyName)) { aOrderer.add(aChange.source); } }); @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(aSubject, aChange); fireSelfScalarsOppositeCollectionsChanges(aSubject, aChange, nFields);// Expanding change if (nField && nField.@com.eas.client.metadata.Field::isPk()()) { fireOppositeScalarsSelfCollectionsChanges(aSubject, aChange, nFields); } if(_onChange){ try{ _onChange(aChange); }catch(e){ Logger.severe(e); } } } function managedBeforeChange(aSubject, aChange) { var oldScalars = prepareSelfScalarsChanges(aSubject, aChange, nFields); var oppositeScalarsFirerers = prepareOppositeScalarsChanges(aSubject, nFields); Object.keys(orderers).forEach(function (aOrdererKey) { var aOrderer = orderers[aOrdererKey]; if (aOrderer.inKeys(aChange.propertyName)) { aOrderer['delete'](aChange.source); } }); return {selfScalarsOldValues: oldScalars, oppositeScalarsFirerers: oppositeScalarsFirerers}; } function tryToComplementInsert(aSubject, aChange) { var complemented = false; var nField = noFields[aChange.propertyName]; if (aSubject === justInserted && !nField.@com.eas.client.metadata.Field::isNullable()()) { var met = false; var iData = justInsertedChange.data; for (var d in iData) { var iv = iData[d]; if (d == aChange.propertyName) { met = true; break; } } if (!met) { iData[aChange.propertyName] = aChange.newValue; complemented = true; } } return complemented; } function javaOrmDefsToJs(aOrmDefs){ var jsDefs = {}; var aOrmDefsIt = aOrmDefs.@java.util.Map::entrySet()().@java.util.Set::iterator()(); while(aOrmDefsIt.@java.util.Iterator::hasNext()()) { var defsEntry = aOrmDefsIt.@java.util.Iterator::next()(); var ormDef = defsEntry.@java.util.Map.Entry::getValue()(); var jsDef = ormDef.@com.eas.client.metadata.Fields.OrmDef::getJsDef()(); jsDefs[defsEntry.@java.util.Map.Entry::getKey()()] = jsDef; } return jsDefs; } function acceptInstance(aSubject, aReassignOrmValues) { Object.keys(noFields).forEach(function(aFieldName){ if(typeof aSubject[aFieldName] === 'undefined') aSubject[aFieldName] = null; }); var scalarJsDefs = javaOrmDefsToJs(nFields.@com.eas.client.metadata.Fields::getOrmScalarDefinitions()()); var sd; if(aReassignOrmValues){ var scalarsContainer = {}; for(sd in scalarJsDefs){ if(typeof aSubject[sd] !== 'undefined'){ scalarsContainer[sd] = aSubject[sd]; aSubject[sd] = null; } } } var collectionsJsDefs = javaOrmDefsToJs(nFields.@com.eas.client.metadata.Fields::getOrmCollectionsDefinitions()()); var cd; if(aReassignOrmValues){ var collectionsContainer = {}; for(cd in collectionsJsDefs){ if(typeof aSubject[cd] !== 'undefined'){ collectionsContainer[cd] = aSubject[cd]; aSubject[cd] = null; } } } M.manageObject(aSubject, managedOnChange, managedBeforeChange); @com.eas.core.Utils::listenable(Lcom/google/gwt/core/client/JavaScriptObject;)(aSubject); // ORM mutable scalar and collection properties for(sd in scalarJsDefs){ Object.defineProperty(aSubject, sd, scalarJsDefs[sd]); if(aReassignOrmValues && typeof scalarsContainer[sd] !== 'undefined'){ aSubject[sd] = scalarsContainer[sd]; } } for(cd in collectionsJsDefs){ Object.defineProperty(aSubject, cd, collectionsJsDefs[cd]); if(aReassignOrmValues && typeof collectionsContainer[cd] !== 'undefined'){ var definedCollection = aSubject[cd]; var savedCollection = collectionsContainer[cd]; if(definedCollection && savedCollection && savedCollection.length > 0){ for(var pi = 0; pi < savedCollection.length; pi++) definedCollection.push(savedCollection[pi]); } } } } var _onInserted = null; var _onDeleted = null; var _onScrolled = null; M.manageArray(published, { spliced: function (added, deleted) { added.forEach(function (aAdded) { justInserted = aAdded; justInsertedChange = new Insert(nEntity.@com.eas.model.Entity::getQueryName()()); for (var nf = 0; nf < nnFields.@java.util.List::size()(); nf++) { var nField = nnFields.@java.util.List::get(I)(nf); var nFieldName = nField.@com.eas.client.metadata.Field::getName()(); if (!aAdded[nFieldName] && nField.@com.eas.client.metadata.Field::isPk()()) { aAdded[nFieldName] = B.boxAsJs(nField.@com.eas.client.metadata.Field::generateValue()()); } } for (var na in aAdded) { var nField = noFields[na]; if (nField) { var v = aAdded[na]; justInsertedChange.data[na] = v; } } changeLog.push(justInsertedChange); for (var aOrdererKey in orderers) { var aOrderer = orderers[aOrdererKey]; aOrderer.add(aAdded); } acceptInstance(aAdded, true); fireOppositeScalarsChanges(aAdded, nFields); fireOppositeCollectionsChanges(aAdded, nFields); }); deleted.forEach(function (aDeleted) { if (aDeleted === justInserted) { justInserted = null; justInsertedChange = null; } var deleteChange = new Delete(nEntity.@com.eas.model.Entity::getQueryName()()); generateChangeLogKeys(deleteChange.keys, nFields, null, aDeleted, null); changeLog.push(deleteChange); for (var aOrdererKey in orderers) { var aOrderer = orderers[aOrdererKey]; aOrderer['delete'](aDeleted); } fireOppositeScalarsChanges(aDeleted, nFields); fireOppositeCollectionsChanges(aDeleted, nFields); @com.eas.core.Utils::unlistenable(Lcom/google/gwt/core/client/JavaScriptObject;)(aDeleted); M.unmanageObject(aDeleted); }); if (_onInserted && added.length > 0) { try { _onInserted({source: published, items: added}); } catch (e) { Logger.severe(e); } } if (_onDeleted && deleted.length > 0) { try { _onDeleted({source: published, items: deleted}); } catch (e) { Logger.severe(e); } } @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(published, {source: published, propertyName: 'length'}); }, scrolled: function (aSubject, oldCursor, newCursor) { if (_onScrolled) { try { _onScrolled({source: published, propertyName: 'cursor', oldValue: oldCursor, newValue: newCursor}); } catch (e) { Logger.severe(e); } } @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(published, {source: published, propertyName: 'cursor', oldValue: oldCursor, newValue: newCursor}); } }); var pSchema = {}; Object.defineProperty(published, "schema", { value: pSchema }); var pkFieldName = ''; var nQuery = nEntity.@com.eas.model.Entity::getQuery()(); var nFields = nQuery.@com.eas.client.queries.Query::getFields()(); var nnFields = nFields.@com.eas.client.metadata.Fields::toCollection()(); var noFields = {}; // schema var pSchemaLengthMet = false; var fieldsCount = nnFields.@java.util.List::size()(); for (var n = 0; n < fieldsCount; n++) { (function () { var nField = nnFields.@java.util.List::get(I)(n); var nFieldName = nField.@com.eas.client.metadata.Field::getName()(); if("length" == nFieldName){ pSchemaLengthMet = true; } noFields[nFieldName] = nField; if (nField.@com.eas.client.metadata.Field::isPk()()) pkFieldName = nFieldName; var pField = @com.eas.client.metadata.Field::publishFacade(Lcom/eas/client/metadata/Field;)(nField); var schemaDesc = { value: pField }; if (!pSchema[nFieldName]) { Object.defineProperty(pSchema, nFieldName, schemaDesc); } else { var eTitle = nEntity.@com.eas.model.Entity::getTitle()() ? " [" + nEntity.@com.eas.model.Entity::getTitle()() + "]" : ""; throw "Duplicated field name found: " + nFieldName + " in entity " + nEntity.@com.eas.model.Entity::getName()() + eTitle; } Object.defineProperty(pSchema, n, schemaDesc); })(); } if (!pSchemaLengthMet) { Object.defineProperty(pSchema, "length", {value: fieldsCount}); } // entity.params.p1 syntax var nParameters = nEntity.@com.eas.model.Entity::getQuery()().@com.eas.client.queries.Query::getParameters()(); var ncParameters = nParameters.@com.eas.client.metadata.Parameters::toCollection()(); var pParams = {}; for (var p = 0; p < ncParameters.@java.util.List::size()(); p++) { (function () { var nParameter = ncParameters.@java.util.List::get(I)(p); var pDesc = { get: function () { return B.boxAsJs(nParameter.@com.eas.client.metadata.Parameter::getJsValue()()); }, set: function (aValue) { nParameter.@com.eas.client.metadata.Parameter::setJsValue(Ljava/lang/Object;)(B.boxAsJava(aValue)); } }; Object.defineProperty(pParams, nParameter.@com.eas.client.metadata.Parameter::getName()(), pDesc); Object.defineProperty(pParams, p, pDesc); })(); } Object.defineProperty(published, 'params', {value: pParams}); // entity.params.schema.p1 syntax var pParamsSchema = @com.eas.client.metadata.Fields::publishFacade(Lcom/eas/client/metadata/Fields;)(nParameters); if (!pParams.schema) Object.defineProperty(pParams, 'schema', {value: pParamsSchema}); Object.defineProperty(published, 'find', {value: function (aCriteria) { if(typeof aCriteria === 'function' && Array.prototype.find){ return Array.prototype.find.call(published, aCriteria); }else{ var keys = Object.keys(aCriteria); keys = keys.sort(); var ordererKey = keys.join(' | '); var orderer = orderers[ordererKey]; if (!orderer) { orderer = new Orderer(keys); published.forEach(function (item) { orderer.add(item); }); orderers[ordererKey] = orderer; } var found = orderer.find(aCriteria); return found; } }}); Object.defineProperty(published, 'findByKey', {value: function (aKeyValue) { var criteria = {}; criteria[pkFieldName] = aKeyValue; var found = published.find(criteria); return found.length > 0 ? found[0] : null; }}); Object.defineProperty(published, 'findById', {value: function (aKeyValue) { Logger.warning('findById() is deprecated. Use findByKey() instead.'); return published.findByKey(aKeyValue); }}); var toBeDeletedMark = '-platypus-to-be-deleted-mark'; Object.defineProperty(published, 'remove', {value: function (toBeDeleted) { toBeDeleted = toBeDeleted.forEach ? toBeDeleted : [toBeDeleted]; toBeDeleted.forEach(function (anInstance) { anInstance[toBeDeletedMark] = true; }); for (var d = published.length - 1; d >= 0; d--) { if (published[d][toBeDeletedMark]) { published.splice(d, 1); } } toBeDeleted.forEach(function (anInstance) { delete anInstance[toBeDeletedMark]; }); }}); Object.defineProperty(published, 'onScroll', { get: function () { return _onScrolled; }, set: function (aValue) { _onScrolled = aValue; } }); Object.defineProperty(published, 'onInsert', { get: function () { return _onInserted; }, set: function (aValue) { _onInserted = aValue; } }); Object.defineProperty(published, 'onDelete', { get: function () { return _onDeleted; }, set: function (aValue) { _onDeleted = aValue; } }); Object.defineProperty(published, 'onChange', { get: function () { return _onChange; }, set: function (aValue) { _onChange = aValue; } }); nEntity.@com.eas.model.Entity::setSnapshotConsumer(Lcom/google/gwt/core/client/JavaScriptObject;)(function (aSnapshot, aFreshData) { if(aFreshData){ Array.prototype.splice.call(published, 0, published.length); } var instanceCtor = nEntity.@com.eas.model.Entity::getElementClass()(); for (var s = 0; s < aSnapshot.length; s++) { var snapshotInstance = aSnapshot[s]; var accepted; if (instanceCtor) { accepted = new instanceCtor(); } else { accepted = {}; } for (var sp in snapshotInstance) { accepted[sp] = snapshotInstance[sp]; } Array.prototype.push.call(published, accepted); acceptInstance(accepted, false); } orderers = {}; published.cursor = published.length > 0 ? published[0] : null; @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(published, {source: published, propertyName: 'length'}); published.forEach(function(aItem){ fireOppositeScalarsChanges(aItem, nFields); fireOppositeCollectionsChanges(aItem, nFields); }); }); nEntity.@com.eas.model.Entity::setSnapshotProducer(Lcom/google/gwt/core/client/JavaScriptObject;)(function(){ var snapshot = []; var snapshotFields = Object.keys(noFields); published.forEach(function (aItem) { var cloned = {}; snapshotFields.forEach(function (aFieldName) { var typeOfField = typeof aItem[aFieldName]; if (typeOfField === 'undefined' || typeOfField === 'function') cloned[aFieldName] = null; else cloned[aFieldName] = aItem[aFieldName]; }); snapshot.push(cloned); }); return snapshot; }); @com.eas.core.Utils::listenable(Lcom/google/gwt/core/client/JavaScriptObject;)(published); return published; }-*/; private static DefinitionsContainer ormPropertiesDefiner = DefinitionsContainer.init(); private static final class DefinitionsContainer extends JavaScriptObject { protected DefinitionsContainer() { } public native static DefinitionsContainer init()/*-{ var Logger = @com.eas.core.Predefine::logger; var M = @com.eas.model.JsModel::managed; return { scalarDef : function(targetEntity, targetFieldName, sourceFieldName) { var _self = this; _self.enumerable = false; _self.configurable = true; _self.get = function() { var criterion = {}; criterion[targetFieldName] = this[sourceFieldName]; var found = targetEntity.find(criterion); return found && found.length == 1 ? found[0] : null; }; _self.set = function(aValue) { this[sourceFieldName] = aValue ? aValue[targetFieldName] : null; }; }, collectionDef : function(sourceEntity, targetFieldName, sourceFieldName) { var _self = this; _self.enumerable = false; _self.configurable = true; _self.get = function() { var criterion = {}; var targetKey = this[targetFieldName]; criterion[sourceFieldName] = targetKey; var found = sourceEntity.find(criterion); M.manageArray(found, { spliced: function (added, deleted) { added.forEach(function (item) { item[sourceFieldName] = targetKey; }); deleted.forEach(function (item) { item[sourceFieldName] = null; }); @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(found, {source: found, propertyName: 'length'}); }, scrolled: function (aSubject, oldCursor, newCursor) { @com.eas.core.Utils::fire(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(found, {source: found, propertyName: 'cursor', oldValue: oldCursor, newValue: newCursor}); } }); @com.eas.core.Utils::listenable(Lcom/google/gwt/core/client/JavaScriptObject;)(found); return found; }; } } }-*/; public native JavaScriptObject scalar(JavaScriptObject targetEntity, String targetFieldName, String sourceFieldName)/*-{ var constr = this.scalarDef; return new constr(targetEntity, targetFieldName, sourceFieldName); }-*/; public native JavaScriptObject collection(JavaScriptObject sourceEntity, String targetFieldName, String sourceFieldName)/*-{ var constr = this.collectionDef; return new constr(sourceEntity, targetFieldName, sourceFieldName); }-*/; } public native static void publishTopLevelFacade(JavaScriptObject aTarget, Model aModel) throws Exception/*-{ var Logger = @com.eas.core.Predefine::logger; var publishedModel = aTarget; Object.defineProperty(publishedModel, "createQuery", { get : function() { return function(aQueryId) { Logger.warning("createQuery deprecated call detected. Use loadEntity() instead."); return aModel.@com.eas.model.Model::jsLoadEntity(Ljava/lang/String;)(aQueryId); } } }); Object.defineProperty(publishedModel, "loadEntity", { get : function() { return function(aQueryId) { return aModel.@com.eas.model.Model::jsLoadEntity(Ljava/lang/String;)(aQueryId); } } }); Object.defineProperty(publishedModel, "save", { get : function() { return function(onScuccess, onFailure) { aModel.@com.eas.model.Model::save(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(onScuccess, onFailure); } } }); Object.defineProperty(publishedModel, "revert", { get : function() { return function() { aModel.@com.eas.model.Model::revert()(); } } }); Object.defineProperty(publishedModel, "requery", { get : function() { return function(onSuccess, onFailure) { aModel.@com.eas.model.Model::requery(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(onSuccess, onFailure); } } }); Object.defineProperty(publishedModel, "execute", { get : function() { return function(onSuccess, onFailure) { aModel.@com.eas.model.Model::execute(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(onSuccess, onFailure); } } }); Object.defineProperty(publishedModel, "unwrap", { get : function() { return function() { return aModel; } } }); Object.defineProperty(publishedModel, "modified", { get : function() { return aModel.@com.eas.model.Model::isModified()(); } }); Object.defineProperty(publishedModel, "pending", { get : function() { return aModel.@com.eas.model.Model::isPending()(); } }); }-*/; public void removeRelation(Relation aRelation) { relations.remove(aRelation); Entity lEntity = aRelation.getLeftEntity(); Entity rEntity = aRelation.getRightEntity(); if (lEntity != null && rEntity != null) { lEntity.removeOutRelation(aRelation); rEntity.removeInRelation(aRelation); } } public void addEntity(Entity aEntity) { entities.put(aEntity.getEntityId(), aEntity); aEntity.setModel(this); } public boolean removeEntity(Entity aEnt) { if (aEnt != null) { return (removeEntity(aEnt.getEntityId()) != null); } return false; } public Entity removeEntity(String aEntId) { Entity lent = entities.get(aEntId); if (lent != null) { entities.remove(aEntId); } return lent; } public Map<String, Entity> getEntities() { return entities; } public Entity getEntityById(String aId) { return entities.get(aId); } public void setEntities(Map<String, Entity> aValue) { entities = aValue; } public Set<Relation> getRelations() { return relations; } public void setRelations(Set<Relation> aRelations) { relations = aRelations; } public void executeEntities(boolean refresh, Set<Entity> toExecute) throws Exception { if (refresh) { for (Entity entity : toExecute) { entity.invalidate(); } } for (Entity entity : toExecute) { entity.internalExecute(null); } } private Set<Entity> rootEntities() { final Set<Entity> rootEntities = new HashSet<>(); for (Entity entity : entities.values()) { if (entity.getInRelations().isEmpty()) rootEntities.add(entity); } return rootEntities; } public RequeryProcess getProcess() { return process; } public void setProcess(RequeryProcess aValue) { process = aValue; } public void terminateProcess(Entity aSource, String aErrorMessage) { if (process != null) { if (aErrorMessage != null) { process.errors.put(aSource, aErrorMessage); } if (!isPending()) { RequeryProcess pr = process; process = null; pr.end(); } } } public boolean isTypeSupported(int type) throws Exception { return true; } public boolean isNamePresent(String aName, Collection<Entity> aEntities, Entity toExclude, Field field2Exclude) { if (aEntities != null && aName != null) { for (Entity ent : aEntities) { if (ent != null && ent != toExclude) { String lName = ent.getName(); if (lName != null && aName.equals(lName)) { return true; } } } } return false; } public JavaScriptObject getChangeLog() { return changeLog; } public void accept(ModelVisitor visitor) { visitor.visit(this); } public boolean isModified() throws Exception { return changeLog.length() > 0; } public void save(final JavaScriptObject onSuccess, final JavaScriptObject onFailure) { Scheduler.get().scheduleDeferred(new ScheduledCommand() { // Scheduling is needed because of asynchronous nature of Object.observe's callback calling process. @Override public void execute() { try { client.requestCommit(changeLog, new CallbackAdapter<Void, String>() { @Override protected void doWork(Void aVoid) throws Exception { commited(); if (onSuccess != null) Utils.invokeJsFunction(onSuccess); } @Override public void onFailure(String aReason) { try { rolledback(); if (onFailure != null) Utils.executeScriptEventVoid(jsPublished, onFailure, aReason); } catch (Exception ex) { Logger.getLogger(Model.class.getName()).log(Level.SEVERE, null, ex); } } }); } catch (Exception ex) { Logger.getLogger(Model.class.getName()).log(Level.SEVERE, null, ex); } } }); } public void revert() throws Exception { changeLog.splice(0, changeLog.length()); for(Entity e : entities.values()){ e.applyLastSnapshot(); } } public void commited() throws Exception { changeLog.splice(0, changeLog.length()); for(Entity e : entities.values()){ e.takeSnapshot(); } } public void rolledback() throws Exception { Logger.getLogger(Model.class.getName()).log(Level.SEVERE, "rolled back"); } public void requery(final JavaScriptObject onSuccess, final JavaScriptObject onFailure) throws Exception { requery(new CallbackAdapter<JavaScriptObject, String>() { @Override protected void doWork(JavaScriptObject aRowset) throws Exception { if (onSuccess != null) Utils.invokeJsFunction(onSuccess); } @Override public void onFailure(String reason) { if (onFailure != null) { try { Utils.executeScriptEventVoid(jsPublished, onFailure, reason); } catch (Exception ex) { Logger.getLogger(Model.class.getName()).log(Level.SEVERE, null, ex); } } } }); } public void requery(Callback<JavaScriptObject, String> aCallback) throws Exception { inNewProcess(new Callable(){ @Override public void call() throws Exception { revert(); executeEntities(true, rootEntities()); } }, aCallback); } public void execute(final JavaScriptObject onSuccess, final JavaScriptObject onFailure) throws Exception { execute(new CallbackAdapter<JavaScriptObject, String>() { @Override protected void doWork(JavaScriptObject aRowset) throws Exception { if (onSuccess != null) Utils.invokeJsFunction(onSuccess); } @Override public void onFailure(String reason) { if (onFailure != null) { try { Utils.executeScriptEventVoid(jsPublished, onFailure, reason); } catch (Exception ex) { Logger.getLogger(Model.class.getName()).log(Level.SEVERE, null, ex); } } } }); } public void execute(Callback<JavaScriptObject, String> aCallback) throws Exception { inNewProcess(new Callable(){ @Override public void call() throws Exception { executeEntities(false, rootEntities()); } }, aCallback); } public void inNewProcess(Callable aAction, Callback<JavaScriptObject, String> aCallback) throws Exception { if (process != null) { process.cancel(); process = null; } if (aCallback != null) { process = new RequeryProcess(aCallback); } aAction.call(); if (!isPending() && process != null) { process.end(); process = null; } } public void validateQueries() throws Exception { for (Entity entity : entities.values()) { entity.validateQuery(); } } protected static final String USER_DATASOURCE_NAME = "userEntity"; public Object jsLoadEntity(String aQueryName) throws Exception { if (client == null) { throw new NullPointerException("Null client detected while creating an entity"); } Entity entity = new Entity(this); entity.setName(USER_DATASOURCE_NAME); entity.setQueryName(aQueryName); entity.validateQuery(); // addEntity(entity); To avoid memory leaks you should not add the // entity to the model! return publishEntity(entity); } }