package com.eas.model; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.eas.client.CallbackAdapter; import com.eas.client.IdGenerator; import com.eas.client.metadata.Field; import com.eas.client.metadata.Fields; import com.eas.client.metadata.Parameter; import com.eas.client.metadata.Parameters; import com.eas.client.queries.Query; import com.eas.core.Callable; import com.eas.core.Cancellable; 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.JsArrayString; import com.google.gwt.core.client.Scheduler; import com.google.gwt.event.shared.HandlerRegistration; /** * * @author mg */ public class Entity implements HasPublished { protected static final String PENDING_ASSUMPTION_FAILED_MSG = "pending assigned to null without pending.cancel() call."; public static final String QUERY_REQUIRED = "All model entities must have a query"; // runtime protected JavaScriptObject onRequeried; protected JavaScriptObject snapshotConsumer; protected JavaScriptObject snapshotProducer; protected JavaScriptObject lastSnapshot = JavaScriptObject.createArray(); protected JavaScriptObject jsPublished; protected HandlerRegistration cursorListener; protected Cancellable pending; protected boolean valid; protected String title; protected String name; protected String entityId = String.valueOf((long) IdGenerator.genId()); protected String queryName; protected Model model; protected Query query; protected Set<Relation> inRelations = new HashSet<>(); protected Set<Relation> outRelations = new HashSet<>(); public Entity() { super(); } public Entity(Model aModel) { this(); model = aModel; } public Entity(String aQueryName) { this(); queryName = aQueryName; } public void putOrmScalarDefinition(String aName, Fields.OrmDef aDefinition) { if (aName != null && !aName.isEmpty() && aDefinition != null) { Map<String, Fields.OrmDef> defs = getFields().getOrmScalarDefinitions(); if (!defs.containsKey(aName)) { getFields().putOrmScalarDefinition(aName, aDefinition); } else { Logger.getLogger(Entity.class.getName()).log( Level.FINE, "ORM property " + aName + " redefinition attempt on entity " + name != null && !name.isEmpty() ? name : "" + " " + title != null && !title.isEmpty() ? "[" + title + "]" : "" + "."); } } } public Map<String, Fields.OrmDef> getOrmScalarDefinitions() { return getFields().getOrmScalarDefinitions(); } public void putOrmCollectionDefinition(String aName, Fields.OrmDef aDefinition) { if (aName != null && !aName.isEmpty() && aDefinition != null) { Map<String, Fields.OrmDef> defs = getFields().getOrmCollectionsDefinitions(); if (!defs.containsKey(aName)) { getFields().putOrmCollectionDefinition(aName, aDefinition); } else { Logger.getLogger(Entity.class.getName()).log( Level.FINE, "ORM property " + aName + " redefinition attempt on entity " + name != null && !name.isEmpty() ? name : "" + " " + title != null && !title.isEmpty() ? "[" + title + "]" : "" + "."); } } } public Map<String, Fields.OrmDef> getOrmCollectionsDefinitions() { return getFields().getOrmCollectionsDefinitions(); } private static native JavaScriptObject publishFacade(Entity nEntity, JavaScriptObject aTarget)/*-{ Object.defineProperty(aTarget, 'elementClass', { get : function() { return nEntity.@com.eas.model.Entity::getElementClass()(); }, set : function(aValue) { nEntity.@com.eas.model.Entity::setElementClass(Lcom/google/gwt/core/client/JavaScriptObject;)(aValue); } }); Object.defineProperty(aTarget, 'onRequeried', { get : function() { return nEntity.@com.eas.model.Entity::getOnRequeried()(); }, set : function(aValue) { nEntity.@com.eas.model.Entity::setOnRequeried(Lcom/google/gwt/core/client/JavaScriptObject;)(aValue); } }); Object.defineProperty(aTarget, 'enqueueUpdate', { value : function(aParams) { nEntity.@com.eas.model.Entity::enqueueUpdate(Lcom/google/gwt/core/client/JavaScriptObject;)(aParams); } }); Object.defineProperty(aTarget, 'executeUpdate', { value : function(onSuccess, onFailure) { nEntity.@com.eas.model.Entity::executeUpdate(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(onSuccess, onFailure); } }); Object.defineProperty(aTarget, 'execute', { value : function(onSuccess, onFailure) { nEntity.@com.eas.model.Entity::execute(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(onSuccess, onFailure); } }); Object.defineProperty(aTarget, 'query', { value : function(params, onSuccess, onFailure) { nEntity.@com.eas.model.Entity::query(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(params, onSuccess, onFailure); } }); Object.defineProperty(aTarget, 'requery', { value : function(onSuccess, onFailure) { nEntity.@com.eas.model.Entity::requery(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(onSuccess, onFailure); } }); Object.defineProperty(aTarget, 'append', { value : function(aData) { nEntity.@com.eas.model.Entity::append(Lcom/google/gwt/core/client/JavaScriptObject;)(aData); } }); Object.defineProperty(aTarget, 'update', { value : function(params, onSuccess, onFailure) { nEntity.@com.eas.model.Entity::update(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(params, onSuccess, onFailure); } }); }-*/; public Fields getFields() { if (query != null) { return query.getFields(); } else { return null; } } public Model getModel() { return model; } public void setModel(Model aValue) { model = aValue; } public String getEntityId() { return entityId; } public void setEntityId(String aValue) { entityId = aValue; } public String getTitle() { String ltitle = title; if (ltitle == null || ltitle.isEmpty()) { try { Query lquery = getQuery(); ltitle = lquery.getTitle(); } catch (Exception ex) { Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex); ltitle = ""; } setTitle(ltitle); } return ltitle; } public void setTitle(String aValue) { title = aValue; } public String getName() { return name; } public void setName(String aValue) { name = aValue; } public String getQueryName() { return queryName; } public void setQueryName(String aValue) { if (queryName == null ? aValue != null : !queryName.equals(aValue)) { setQuery(null); } queryName = aValue; } public Query getQuery() throws Exception { assert query != null : "Missing definition of entity '" + queryName + "'"; return query; } public void setQuery(Query aValue) { query = aValue; } public boolean removeOutRelation(Relation aRelation) { return outRelations.remove(aRelation); } public boolean removeInRelation(Relation aRelation) { return inRelations.remove(aRelation); } public boolean addOutRelation(Relation aRelation) { if (!(aRelation instanceof ReferenceRelation)) return outRelations.add(aRelation); else return false; } public boolean addInRelation(Relation aRelation) { if (!(aRelation instanceof ReferenceRelation)) return inRelations.add(aRelation); else return false; } public Set<Relation> getInRelations() { return inRelations; } public Set<Relation> getOutRelations() { return outRelations; } public Set<Relation> getInOutRelations() { Set<Relation> lInOutRelations = new HashSet<Relation>(); lInOutRelations.addAll(inRelations); lInOutRelations.addAll(outRelations); return lInOutRelations; } protected boolean isTagValid(String aTagName) { return true; } public JavaScriptObject getSnapshotConsumer() { return snapshotConsumer; } public void setSnapshotConsumer(JavaScriptObject aValue) { snapshotConsumer = aValue; } public JavaScriptObject getSnapshotProducer() { return snapshotProducer; } public void setSnapshotProducer(JavaScriptObject aValue) { snapshotProducer = aValue; } public void takeSnapshot() { if (snapshotProducer != null) { lastSnapshot = (JavaScriptObject) snapshotProducer.<Utils.JsObject> cast().call(null, null); } } public void applyLastSnapshot() { applySnapshot(lastSnapshot); } public void applySnapshot(JavaScriptObject aValue) { lastSnapshot = aValue; // Apply aValue as a snapshot. Be aware of change log! if (snapshotConsumer != null) {// snapshotConsumer is null in designer snapshotConsumer.<Utils.JsObject> cast().call(null, aValue, true); } } private static class CancellableContainer { public Cancellable future; } protected void refreshRowset(final Callback<JavaScriptObject, String> aCallback) throws Exception { final CancellableContainer f = new CancellableContainer(); f.future = query.execute(new CallbackAdapter<JavaScriptObject, String>() { @Override public void doWork(JavaScriptObject aResult) throws Exception { if (pending == f.future) { // Apply aRowset as a snapshot. Be aware of change log! applySnapshot(aResult); valid = true; pending = null; model.terminateProcess(Entity.this, null); if (onRequeried != null) { try { Utils.JsObject event = JavaScriptObject.createObject().cast(); event.setJs("source", jsPublished); onRequeried.<Utils.JsObject> cast().call(jsPublished, event); } catch (Exception ex) { Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex); } } if (aCallback != null) { aCallback.onSuccess(jsPublished); } } } @Override public void onFailure(String aMessage) { if (pending == f.future) { valid = true; pending = null; model.terminateProcess(Entity.this, aMessage); if (aCallback != null) { aCallback.onFailure(aMessage); } } } }); pending = f.future; } public void validateQuery() throws Exception { if (query == null) { setQuery(model.client.getCachedAppQuery(queryName)); } } public Entity copy() throws Exception { assert model != null : "Entities can't exist without a model"; Entity copied = new Entity(model); assign(copied); return copied; } public JavaScriptObject getOnRequeried() { return onRequeried; } public void setOnRequeried(JavaScriptObject aValue) { onRequeried = aValue; } public boolean isPending() { return pending != null; } public void requery(final JavaScriptObject aOnSuccess, final JavaScriptObject aOnFailure) throws Exception { requery(new CallbackAdapter<JavaScriptObject, String>() { @Override protected void doWork(JavaScriptObject result) throws Exception { if (aOnSuccess != null) { aOnSuccess.<Utils.JsObject> cast().apply(jsPublished, JavaScriptObject.createArray()); } } @Override public void onFailure(String reason) { if (aOnFailure != null) { aOnFailure.<Utils.JsObject> cast().call(jsPublished, reason); } } }); } public void query(JavaScriptObject aParams, final JavaScriptObject aOnSuccess, final JavaScriptObject aOnFailure) throws Exception { Query copied = query.copy(); if (aParams != null) { Utils.JsObject params = aParams.cast(); JsArrayString keys = params.keys(); for (int i = 0; i < keys.length(); i++) { String key = keys.get(i); Object pValue = params.getJava(key); Parameter p = copied.getParameters().get(key); if (p != null) { p.setValue(Utils.toJava(pValue)); } } } copied.execute(new CallbackAdapter<JavaScriptObject, String>() { @Override protected void doWork(JavaScriptObject result) throws Exception { if (aOnSuccess != null) { aOnSuccess.<Utils.JsObject> cast().call(jsPublished, result); } } @Override public void onFailure(String reason) { if (aOnFailure != null) { aOnFailure.<Utils.JsObject> cast().call(jsPublished, reason); } } }); } public void requery(final Callback<JavaScriptObject, String> aCallback) throws Exception { if (model != null) { invalidate(); execute(aCallback); } } public void append(JavaScriptObject aData) throws Exception { if (snapshotConsumer != null) { snapshotConsumer.<Utils.JsObject> cast().call(null, aData, false); } } public void enqueueUpdate(JavaScriptObject aParams) throws Exception { Utils.JsObject changeLog = model.getChangeLog().<Utils.JsObject> cast(); Query copied = query.copy(); if (aParams != null) { Utils.JsObject params = aParams.cast(); JsArrayString keys = params.keys(); for (int i = 0; i < keys.length(); i++) { String key = keys.get(i); Object pValue = params.getJava(key); Parameter p = copied.getParameters().get(key); if (p != null) { p.setValue(Utils.toJava(pValue)); } } } changeLog.setSlot(changeLog.length(), copied.prepareCommand()); } /** * Legacy method. * @param onSuccess * @param onFailure * @throws Exception */ public void executeUpdate(final JavaScriptObject onSuccess, final JavaScriptObject onFailure) throws Exception { Utils.JsObject changeLog = JavaScriptObject.createArray(1).cast(); changeLog.setSlot(0, query.prepareCommand()); model.client.requestCommit(changeLog, new CallbackAdapter<Void, String>() { @Override protected void doWork(Void aVoid) throws Exception { if (onSuccess != null) Utils.invokeJsFunction(onSuccess); } @Override public void onFailure(String aReason) { try { if (onFailure != null) Utils.executeScriptEventVoid(jsPublished, onFailure, aReason); } catch (Exception ex) { Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex); } } }); } /** * Executes a dml query of the entity. Uses first argument JavaScript object as parameters. * @param aOnSuccess * @param aOnFailure * @throws Exception */ public void update(final JavaScriptObject aParams, final JavaScriptObject aOnSuccess, final JavaScriptObject aOnFailure) throws Exception { Query copied = query.copy(); if (aParams != null) { Utils.JsObject params = aParams.cast(); JsArrayString keys = params.keys(); for (int i = 0; i < keys.length(); i++) { String key = keys.get(i); Object pValue = params.getJava(key); Parameter p = copied.getParameters().get(key); if (p != null) { p.setValue(Utils.toJava(pValue)); } } } Utils.JsObject changeLog = JavaScriptObject.createArray(1).cast(); changeLog.setSlot(0, copied.prepareCommand()); model.client.requestCommit(changeLog, new CallbackAdapter<Void, String>() { @Override protected void doWork(Void aVoid) throws Exception { if (aOnSuccess != null) Utils.invokeJsFunction(aOnSuccess); } @Override public void onFailure(String aReason) { try { if (aOnFailure != null) Utils.executeScriptEventVoid(jsPublished, aOnFailure, aReason); } catch (Exception ex) { Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex); } } }); } public JavaScriptObject getChangeLog() { return model.getChangeLog(); } public void execute(final JavaScriptObject onSuccess, final JavaScriptObject onFailure) throws Exception { execute(new CallbackAdapter<JavaScriptObject, String>() { @Override protected void doWork(JavaScriptObject aResult) 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(Entity.class.getName()).log(Level.SEVERE, null, ex); } } } }); } public void execute(final Callback<JavaScriptObject, String> aCallback) throws Exception { if (model != null) { if (getOutRelations().isEmpty()) { internalExecute(aCallback); } else { model.inNewProcess(new Callable() { @Override public void call() throws Exception { internalExecute(null); } }, new Callback<JavaScriptObject, String>() { @Override public void onSuccess(JavaScriptObject result) { if(aCallback != null){ aCallback.onSuccess(jsPublished); } } @Override public void onFailure(String reason) { if(aCallback != null){ aCallback.onFailure(reason); } } }); } } } protected void internalExecute(final Callback<JavaScriptObject, String> aCallback) throws Exception { assert query != null : QUERY_REQUIRED; bindQueryParameters(); if (isValid()) { if (aCallback != null) { Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { aCallback.onSuccess(null); } }); } } else { // Requery if query parameters values have been changed while // bindQueryParameters() call // or we are forced to refresh the data via requery() call. silentUnpend(); refreshRowset(aCallback); assert pending != null || (aCallback == null && model.process == null); // filtering will be done while processing onRequeried event in // ApplicationEntity code } } public void unpend() { if (pending != null) { pending.cancel(); pending = null; } } protected void silentUnpend() { Model.RequeryProcess lprocess = model.process; model.process = null; try { unpend(); } finally { model.process = lprocess; } } protected void internalExecuteChildren(boolean refresh) throws Exception { Set<Relation> rels = getOutRelations(); if (rels != null) { Set<Entity> toExecute = new HashSet<>(); for (Relation outRel : rels) { if (outRel != null) { Entity rEntity = outRel.getRightEntity(); if (rEntity != null) { toExecute.add(rEntity); } } } model.executeEntities(refresh, toExecute); } } protected void internalExecuteChildren(boolean refresh, int aOnlyFieldIndex) throws Exception { Set<Relation> rels = getOutRelations(); if (rels != null) { Field onlyField = getFields().get(aOnlyFieldIndex); Set<Entity> toExecute = new HashSet<>(); for (Relation outRel : rels) { if (outRel != null) { Entity rEntity = outRel.getRightEntity(); if (rEntity != null && outRel.getLeftField() == onlyField) { toExecute.add(rEntity); } } } model.executeEntities(refresh, toExecute); } } public void bindQueryParameters() throws Exception { Query selfQuery = getQuery(); Parameters selfParameters = selfQuery.getParameters(); boolean parametersModified = false; Set<Relation> inRels = getInRelations(); if (inRels != null && !inRels.isEmpty()) { for (Relation relation : inRels) { if (relation != null && relation.isRightParameter()) { Entity leftEntity = relation.getLeftEntity(); if (leftEntity != null) { Object pValue = null; if (relation.isLeftField()) { // There might be entities - parameters values // sources, with no // data in theirs rowsets, so we can't bind query // parameters to proper values. In the // such case we initialize parameters values with // null JavaScriptObject leftRowset = leftEntity.getPublished(); if (leftRowset != null && leftRowset.<JsObject> cast().getJs("cursor") != null) { JavaScriptObject jsCursor = leftRowset.<JsObject> cast().getJs("cursor"); pValue = jsCursor.<JsObject> cast().getJava(relation.getLeftField().getName()); } else { pValue = null; } } else { Parameter leftParameter = relation.getLeftParameter(); if (leftParameter != null) { pValue = leftParameter.getValue(); if (pValue == null) { pValue = leftParameter.getDefaultValue(); } } else { Logger.getLogger(Entity.class.getName()).log( Level.SEVERE, "Parameter of left query must present (Relation points to query parameter in entity: " + getTitle() + " [" + getEntityId() + "], but query parameter is absent)"); } } Parameter selfPm = relation.getRightParameter(); if (selfPm != null) { Object selfValue = selfPm.getValue(); if ((selfValue == null && pValue != null) || (selfValue != null && !selfValue.equals(pValue))) { selfPm.setValue(pValue); } } } else { Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, "Relation has no left entity"); } } } } for (int i = 1; i <= selfParameters.getFieldsCount(); i++) { Parameter param = selfParameters.get(i); if (param.isModified()) { parametersModified = true; param.setModified(false); } } if (parametersModified) { invalidate(); } } protected void resignOnCursor() { if (cursorListener != null) { cursorListener.removeHandler(); cursorListener = null; } JavaScriptObject jsCursor = jsPublished.<JsObject> cast().getJs("cursor"); if (jsCursor != null) { cursorListener = Utils.listenPath(jsCursor, "", new Utils.OnChangeHandler() { @Override public void onChange(JavaScriptObject anEvent) { try { internalExecuteChildren(false); } catch (Exception ex) { Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex); } } }); } } protected void assign(Entity appTarget) throws Exception { appTarget.setEntityId(entityId); appTarget.setQueryName(queryName); appTarget.setTitle(title); appTarget.setName(name); appTarget.setOnRequeried(onRequeried); } public void accept(ModelVisitor visitor) { visitor.visit(this); } public boolean isValid() { return valid; } public void invalidate() { valid = false; } public void setPublished(JavaScriptObject aPublished) { if (jsPublished != aPublished) { jsPublished = aPublished; if (jsPublished != null) { publishFacade(this, jsPublished); Utils.listenPath(jsPublished, "cursor", new Utils.OnChangeHandler() { @Override public void onChange(JavaScriptObject anEvent) { try { resignOnCursor(); internalExecuteChildren(false); } catch (Exception ex) { Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex); } } }); } } } @Override public JavaScriptObject getPublished() { return jsPublished; } public JavaScriptObject getElementClass() { return getFields().getInstanceConstructor(); } public void setElementClass(JavaScriptObject aValue) { getFields().setInstanceConstructor(aValue); } }