package com.servoy.j2db.server.ngclient; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.WeakHashMap; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.mozilla.javascript.Function; import org.mozilla.javascript.Scriptable; import org.sablo.BaseWebObject; import org.sablo.Container; import org.sablo.WebComponent; import org.sablo.specification.PropertyDescription; import org.sablo.specification.WebObjectApiDefinition; import org.sablo.specification.WebObjectSpecification.PushToServerEnum; import org.sablo.specification.property.BrowserConverterContext; import org.sablo.specification.property.IPropertyConverterForBrowser; import org.sablo.specification.property.IPropertyType; import org.sablo.specification.property.types.TypesRegistry; import com.servoy.base.util.ITagResolver; import com.servoy.j2db.BasicFormController; import com.servoy.j2db.dataprocessing.IDataAdapter; import com.servoy.j2db.dataprocessing.IModificationListener; import com.servoy.j2db.dataprocessing.IRecord; import com.servoy.j2db.dataprocessing.IRecordInternal; import com.servoy.j2db.dataprocessing.ModificationEvent; import com.servoy.j2db.dataprocessing.TagResolver; import com.servoy.j2db.persistence.AggregateVariable; import com.servoy.j2db.persistence.ColumnWrapper; import com.servoy.j2db.persistence.IDataProvider; import com.servoy.j2db.persistence.IDataProviderLookup; import com.servoy.j2db.persistence.Relation; import com.servoy.j2db.persistence.RepositoryException; import com.servoy.j2db.persistence.ScriptMethod; import com.servoy.j2db.query.QueryAggregate; import com.servoy.j2db.scripting.FormScope; import com.servoy.j2db.scripting.GlobalScope; import com.servoy.j2db.scripting.IExecutingEnviroment; import com.servoy.j2db.scripting.ScopesScope; import com.servoy.j2db.server.ngclient.component.EventExecutor; import com.servoy.j2db.server.ngclient.property.DataproviderConfig; import com.servoy.j2db.server.ngclient.property.IDataLinkedPropertyValue; import com.servoy.j2db.server.ngclient.property.IFindModeAwarePropertyValue; import com.servoy.j2db.server.ngclient.property.types.DataproviderTypeSabloValue; import com.servoy.j2db.server.ngclient.property.types.IDataLinkedType.TargetDataLinks; import com.servoy.j2db.server.ngclient.property.types.NGConversions; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.ScopesUtils; import com.servoy.j2db.util.ServoyJSONObject; import com.servoy.j2db.util.Utils; public class DataAdapterList implements IModificationListener, ITagResolver, IDataAdapterList { // properties that are interested in a specific dataproviderID chaning protected final Map<String, List<IDataLinkedPropertyValue>> dataProviderToLinkedComponentProperty = new HashMap<>(); // dataProviderID -> [(comp, propertyName)] // all data-linked properties - contains 'dataProviderToLinkedComponentProperty' as well as other ones that are interested in any DP change protected final List<IDataLinkedPropertyValue> allComponentPropertiesLinkedToData = new ArrayList<>(); // [(comp, propertyName), ...] protected final List<IFindModeAwarePropertyValue> findModeAwareProperties = new ArrayList<>(); private final IWebFormController formController; private final EventExecutor executor; private final WeakHashMap<IWebFormController, String> visibleChildForms = new WeakHashMap<>(); private final Map<String, String> uninitializedVisibleChildForms = new HashMap<String, String>(); private final ArrayList<IWebFormController> parentRelatedForms = new ArrayList<IWebFormController>(); private IRecordInternal record; private boolean findMode = false; private boolean settingRecord; private boolean isFormScopeListener; private boolean isGlobalScopeListener; public DataAdapterList(IWebFormController formController) { this.formController = formController; this.executor = new EventExecutor(formController); } public final INGApplication getApplication() { return formController.getApplication(); } public final IWebFormController getForm() { return formController; } @Override public Object executeEvent(WebComponent webComponent, String event, int eventId, Object[] args) { Object jsRetVal = executor.executeEvent(webComponent, event, eventId, args); return NGConversions.INSTANCE.convertRhinoToSabloComponentValue(jsRetVal, null, null, webComponent); // TODO why do handlers not have complete definitions in spec - just like apis? - we don't know types here } @Override public Object executeInlineScript(String script, JSONObject args, JSONArray appendingArgs) { String decryptedScript = HTMLTagsConverter.decryptInlineScript(script, args); if (appendingArgs != null && decryptedScript.endsWith("()")) { ArrayList<Object> javaArguments = new ArrayList<Object>(); Object argObj = null; BrowserConverterContext dataConverterContext = new BrowserConverterContext((WebFormUI)formController.getFormUI(), PushToServerEnum.allow); for (int i = 0; i < appendingArgs.length(); i++) { try { argObj = ServoyJSONObject.jsonNullToNull(appendingArgs.get(i)); if (argObj instanceof JSONObject) { String typeHint = ((JSONObject)argObj).optString("svyType", null); //$NON-NLS-1$ if (typeHint != null) { IPropertyType< ? > propertyType = TypesRegistry.getType(typeHint); if (propertyType instanceof IPropertyConverterForBrowser< ? >) { javaArguments.add(((IPropertyConverterForBrowser< ? >)propertyType).fromJSON(argObj, null, null /* * TODO this shouldn't be null! Make this better - maybe parse the type or just instantiate a property description * if we don't want full support for what can be defined in spec file as a type */, dataConverterContext, null)); continue; } } } } catch (JSONException e) { Debug.error(e); } javaArguments.add(argObj); } String functionName = decryptedScript.substring(0, decryptedScript.length() - 2); int startIdx = functionName.lastIndexOf('.'); String noPrefixFunctionName = functionName.substring(startIdx > -1 ? startIdx + 1 : 0, functionName.length()); Scriptable scope = null; Function f = null; if (functionName.startsWith("forms.")) { FormScope formScope = formController.getFormScope(); f = formScope.getFunctionByName(noPrefixFunctionName); if (f != null && f != Scriptable.NOT_FOUND) { scope = formScope; } } else if (functionName.startsWith("entity.")) { scope = (Scriptable)formController.getFoundSet(); f = (Function)scope.getPrototype().get(noPrefixFunctionName, scope); } else { ScriptMethod scriptMethod = formController.getApplication().getFlattenedSolution().getScriptMethod(functionName); if (scriptMethod != null) { scope = formController.getApplication().getScriptEngine().getScopesScope().getGlobalScope(scriptMethod.getScopeName()); } if (scope != null) { f = ((GlobalScope)scope).getFunctionByName(noPrefixFunctionName); } } try { return formController.getApplication().getScriptEngine().executeFunction(f, scope, scope, javaArguments.toArray(), false, false); } catch (Exception ex) { Debug.error(ex); return null; } } return decryptedScript != null ? formController.eval(decryptedScript) : null; } private static boolean containsForm(IWebFormUI parent, IWebFormUI child) { Object childParentContainer = child.getParentContainer(); if (childParentContainer instanceof WebFormComponent) { Container p = ((WebFormComponent)childParentContainer).getParent(); if (p instanceof IWebFormUI) { if (p.equals(parent)) { return true; } else return containsForm(parent, (IWebFormUI)p); } } return false; } public void updateRelatedVisibleForms(List<Pair<String, String>> oldForms, List<Pair<String, String>> newForms) { if (oldForms != null) { for (Pair<String, String> oldForm : oldForms) { if (!newForms.contains(oldForm)) { IWebFormController fc = getApplication().getFormManager().getForm(oldForm.getLeft()); List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); fc.notifyVisible(false, invokeLaterRunnables); Utils.invokeLater(getApplication(), invokeLaterRunnables); removeVisibleChildForm(fc, true); } } } if (newForms != null) { for (Pair<String, String> newVisibleForm : newForms) { if (!oldForms.contains(newVisibleForm)) { IWebFormController newFormController = getApplication().getFormManager().getForm(newVisibleForm.getLeft()); addVisibleChildForm(newFormController, newVisibleForm.getRight(), true); if (newVisibleForm.getRight() != null) { newFormController.loadRecords(record != null ? record.getRelatedFoundSet(newVisibleForm.getRight(), ((BasicFormController)newFormController).getDefaultSortColumns()) : null); } updateParentContainer(newFormController, newVisibleForm.getRight(), formController.isFormVisible()); List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); newFormController.notifyVisible(formController.isFormVisible(), invokeLaterRunnables); Utils.invokeLater(getApplication(), invokeLaterRunnables); } else { oldForms.remove(newVisibleForm); } } } } public void addUninitializedRelatedForm(String formName, String relationName) { uninitializedVisibleChildForms.put(formName, relationName); } public void addVisibleChildForm(IWebFormController form, String relation, boolean shouldUpdateParentFormController) { if (shouldUpdateParentFormController) { form.setParentFormController(formController); } else { form.getFormUI().getDataAdapterList().addParentRelatedForm(getForm()); } if (relation != null) { for (Entry<IWebFormController, String> relatedFormEntry : visibleChildForms.entrySet()) { IWebFormController relatedForm = relatedFormEntry.getKey(); String relatedFormRelation = relatedFormEntry.getValue(); if (relatedFormRelation != null) { if (relatedFormRelation.startsWith(relation) && relatedFormRelation.length() > relation.length()) { if (!containsForm(form.getFormUI(), relatedForm.getFormUI())) { form.getFormUI().getDataAdapterList().addVisibleChildForm(relatedForm, relatedFormRelation.substring(relation.length() + 1), false); } } else if (relation.startsWith(relatedFormRelation) && relation.length() > relatedFormRelation.length()) { if (!containsForm(relatedForm.getFormUI(), form.getFormUI())) { relatedForm.getFormUI().getDataAdapterList().addVisibleChildForm(form, relation.substring(relatedFormRelation.length() + 1), false); } } } } if (!isGlobalScopeListener) { Relation relationObj = formController.getApplication().getFlattenedSolution().getRelation(relation); if (relationObj != null && relationObj.isGlobal()) { formController.getApplication().getScriptEngine().getScopesScope().getModificationSubject().addModificationListener(this); isGlobalScopeListener = true; } } } visibleChildForms.put(form, relation); } public void removeVisibleChildForm(IWebFormController form, boolean firstLevel) { if (firstLevel) { form.setParentFormController(null); } else { form.getFormUI().getDataAdapterList().removeParentRelatedForm(getForm()); } if (!firstLevel) { uninitializedVisibleChildForms.remove(form.getName()); } if (visibleChildForms.containsKey(form)) { visibleChildForms.remove(form); for (Object relWFC : form.getFormUI().getDataAdapterList().getParentRelatedForms().toArray()) { ((IWebFormController)relWFC).getFormUI().getDataAdapterList().removeVisibleChildForm(form, false); } } } public Map<IWebFormController, String> getRelatedForms() { return visibleChildForms; } public void addParentRelatedForm(IWebFormController form) { if (parentRelatedForms.indexOf(form) == -1) parentRelatedForms.add(form); } public void removeParentRelatedForm(IWebFormController form) { parentRelatedForms.remove(form); } public List<IWebFormController> getParentRelatedForms() { return parentRelatedForms; } private void setupModificationListener(String dataprovider) { if (!isFormScopeListener && (isFormDataprovider(dataprovider) || dataprovider == null)) { formController.getFormScope().getModificationSubject().addModificationListener(this); isFormScopeListener = true; } if (!isGlobalScopeListener && (isGlobalDataprovider(dataprovider) || dataprovider == null)) { formController.getApplication().getScriptEngine().getScopesScope().getModificationSubject().addModificationListener(this); isGlobalScopeListener = true; } } private String[] getAllDependantDataproviders(String[] dataproviders) { ArrayList<String> returnDP = new ArrayList<>(); for (String dp : dataproviders) { if (dp != null && !ScopesUtils.isVariableScope(dp)) { int idx = dp.lastIndexOf('.'); if (idx != -1) //handle related fields { Relation[] relations = getApplication().getFlattenedSolution().getRelationSequence(dp.substring(0, idx)); if (relations != null) { for (int i = 0; i < relations.length; i++) { Relation relation = relations[i]; // add first level of normal relations and all global if (relation.isValid() && (relation.isGlobal() || i == 0)) { try { IDataProvider[] dps = relation.getPrimaryDataProviders(getApplication().getFlattenedSolution()); for (IDataProvider idp : dps) { returnDP.add(idp.getDataProviderID()); } } catch (RepositoryException ex) { Debug.error(ex); } } } continue; } } } returnDP.add(dp); } return returnDP.toArray(new String[returnDP.size()]); } public void addDataLinkedProperty(IDataLinkedPropertyValue propertyValue, TargetDataLinks targetDataLinks) { if (targetDataLinks == TargetDataLinks.NOT_LINKED_TO_DATA || targetDataLinks == null) return; String[] dataproviders = targetDataLinks.dataProviderIDs; if (dataproviders == null) { dataproviders = new String[] { null }; } else { dataproviders = getAllDependantDataproviders(dataproviders); } for (String dpID : dataproviders) { List<IDataLinkedPropertyValue> allLinksOfDP = dataProviderToLinkedComponentProperty.get(dpID); if (allLinksOfDP == null) { allLinksOfDP = new ArrayList<>(); dataProviderToLinkedComponentProperty.put(dpID, allLinksOfDP); } if (!allLinksOfDP.contains(propertyValue)) allLinksOfDP.add(propertyValue); if (formController != null) setupModificationListener(dpID); // see if we need to listen to global/form scope changes } allComponentPropertiesLinkedToData.add(propertyValue); } public void removeDataLinkedProperty(IDataLinkedPropertyValue propertyValue) { Iterator<List<IDataLinkedPropertyValue>> it = dataProviderToLinkedComponentProperty.values().iterator(); while (it.hasNext()) { List<IDataLinkedPropertyValue> x = it.next(); if (x.remove(propertyValue)) { if (x.size() == 0) it.remove(); } } // TODO keep track & unregister when needed global/form scope listeners: so undo setupModificationListener(dpID); allComponentPropertiesLinkedToData.remove(propertyValue); } public void addFindModeAwareProperty(IFindModeAwarePropertyValue propertyValue) { findModeAwareProperties.add(propertyValue); } public void removeFindModeAwareProperty(IFindModeAwarePropertyValue propertyValue) { findModeAwareProperties.remove(propertyValue); } public void componentDisposed(WebFormComponent component) { // TODO remove modification listeners for form/global scopes if needed... } public void setRecord(IRecord record, boolean fireChangeEvent) { // if this is not a change in the record that it should not be needed that it should be pushed again. // because all types should just listen to the right stuff. if (shouldIgnoreRecordChange(this.record, record)) return; if (settingRecord) { if (record != this.record) { throw new IllegalStateException("Record " + record + " is being set on DAL when record: " + this.record + " is being processed"); } return; } try { settingRecord = true; if (this.record != null) { this.record.removeModificationListener(this); } this.record = (IRecordInternal)record; if (this.record != null) { pushChangedValues(null, fireChangeEvent); this.record.addModificationListener(this); } } finally { settingRecord = false; } if (uninitializedVisibleChildForms.size() > 0) { //a record is set, we must initialize related forms, legacy behavior from sc/wc for (String formName : uninitializedVisibleChildForms.keySet()) { addVisibleChildForm(getApplication().getFormManager().getForm(formName), uninitializedVisibleChildForms.get(formName), true); } uninitializedVisibleChildForms.clear(); } for (IWebFormController form : visibleChildForms.keySet()) { if (visibleChildForms.get(form) != null) { form.loadRecords( record != null ? record.getRelatedFoundSet(visibleChildForms.get(form), ((BasicFormController)form).getDefaultSortColumns()) : null); } } } protected boolean shouldIgnoreRecordChange(IRecord oldRecord, IRecord newRecord) { if (oldRecord == newRecord) return true; return false; } public IRecordInternal getRecord() { return record; } protected boolean isFormDataprovider(String dataprovider) { if (dataprovider == null) return false; FormScope fs = formController.getFormScope(); return fs.has(dataprovider, fs); } protected boolean isGlobalDataprovider(String dataprovider) { if (dataprovider == null) return false; ScopesScope ss = formController.getApplication().getScriptEngine().getScopesScope(); Pair<String, String> scope = ScopesUtils.getVariableScope(dataprovider); if (scope.getLeft() != null) { GlobalScope gs = ss.getGlobalScope(scope.getLeft()); return gs != null && gs.has(scope.getRight(), gs); } return false; } private void pushChangedValues(String dataProvider, boolean fireChangeEvent) { boolean isFormDP = isFormDataprovider(dataProvider); boolean isGlobalDP = isGlobalDataprovider(dataProvider); boolean changed = false; if (dataProvider == null) { // announce to all - we don't know exactly what changed; maybe all DPs changed for (IDataLinkedPropertyValue x : allComponentPropertiesLinkedToData.toArray( new IDataLinkedPropertyValue[allComponentPropertiesLinkedToData.size()])) { x.dataProviderOrRecordChanged(record, null, isFormDP, isGlobalDP, fireChangeEvent); } } else { List<IDataLinkedPropertyValue> interestedComponentProperties = dataProviderToLinkedComponentProperty.get(dataProvider); if (interestedComponentProperties == null) { interestedComponentProperties = dataProviderToLinkedComponentProperty.get(null); } else { List<IDataLinkedPropertyValue> listenToAllComponentProperties = dataProviderToLinkedComponentProperty.get(null); if (listenToAllComponentProperties != null && listenToAllComponentProperties.size() > 0) { interestedComponentProperties = new ArrayList<IDataLinkedPropertyValue>(interestedComponentProperties); for (IDataLinkedPropertyValue dataLink : listenToAllComponentProperties) { if (!interestedComponentProperties.contains(dataLink)) interestedComponentProperties.add(dataLink); } } } if (interestedComponentProperties != null) { for (IDataLinkedPropertyValue x : interestedComponentProperties) { x.dataProviderOrRecordChanged(record, dataProvider, isFormDP, isGlobalDP, fireChangeEvent); } } } if (fireChangeEvent && changed) { getApplication().getChangeListener().valueChanged(); } } @Override public void valueChanged(ModificationEvent e) { if (record != null && e != null && e.getName() != null) { for (Entry<IWebFormController, String> relatedFormEntry : visibleChildForms.entrySet()) { IWebFormController relatedForm = relatedFormEntry.getKey(); String relatedFormRelation = relatedFormEntry.getValue(); boolean depends = false; Relation[] relations = getApplication().getFlattenedSolution().getRelationSequence(relatedFormRelation); for (int r = 0; !depends && relations != null && r < relations.length; r++) { try { IDataProvider[] primaryDataProviders = relations[r].getPrimaryDataProviders(getApplication().getFlattenedSolution()); for (int p = 0; !depends && primaryDataProviders != null && p < primaryDataProviders.length; p++) { depends = e.getName().equals(primaryDataProviders[p].getDataProviderID()); } } catch (RepositoryException ex) { Debug.log(ex); } } if (depends) { relatedForm.loadRecords(record.getRelatedFoundSet(relatedFormRelation, ((BasicFormController)relatedForm).getDefaultSortColumns())); } } } if (getForm().isFormVisible()) { pushChangedValues(e.getName(), true); } } public void pushChanges(WebFormComponent webComponent, String beanProperty) { pushChanges(webComponent, beanProperty, webComponent.getProperty(beanProperty)); } /** * Get the dataProviderID from the runtime property. * NOTE: it's not taken directly from FormElement because 'beanProperty' might contain dots (a dataprovider nested somewhere in another property) - and BaseWebObject deals with that correctly. */ public String getDataProviderID(WebFormComponent webComponent, String beanProperty) { Object propertyValue = webComponent.getProperty(beanProperty); if (propertyValue instanceof DataproviderTypeSabloValue) return ((DataproviderTypeSabloValue)propertyValue).getDataProviderID(); return null; } public void pushChanges(WebFormComponent webComponent, String beanProperty, Object newValue) { // TODO should this all (svy-apply/push) move to DataProviderType client/server side implementation instead of specialized calls? String dataProviderID = getDataProviderID(webComponent, beanProperty); if (dataProviderID == null) { Debug.log( "apply called on a property that is not bound to a dataprovider: " + beanProperty + ", value: " + newValue + " of component: " + webComponent); return; } // Check security webComponent.checkPropertyProtection(beanProperty); if (newValue instanceof DataproviderTypeSabloValue) newValue = ((DataproviderTypeSabloValue)newValue).getValue(); // TODO should this always be tried? (Calendar field has no push for edit, because it doesn't use svyAutoApply) // but what if it was a global or form variable? if (record == null || record.startEditing()) { Object v; // if the value is a map, then it means, that a set of related properties needs to be updated, // ex. newValue = {"" : "image_data", "_filename": "pic.jpg", "_mimetype": "image/jpeg"} // will update property with "image_data", property_filename with "pic.jpg" and property_mimetype with "image/jpeg" if (newValue instanceof HashMap) { v = ((HashMap< ? , ? >)newValue).get(""); // defining value Iterator<Entry< ? , ? >> newValueIte = ((HashMap)newValue).entrySet().iterator(); while (newValueIte.hasNext()) { Entry< ? , ? > e = newValueIte.next(); if (!"".equals(e.getKey())) { com.servoy.j2db.dataprocessing.DataAdapterList.setValueObject(record, formController.getFormScope(), dataProviderID + e.getKey(), e.getValue()); } } } else { v = newValue; } Object oldValue = com.servoy.j2db.dataprocessing.DataAdapterList.setValueObject(record, formController.getFormScope(), dataProviderID, v); String onDataChange = ((DataproviderConfig)webComponent.getFormElement().getWebComponentSpec().getProperty( beanProperty).getConfig()).getOnDataChange(); if (onDataChange != null && !Utils.equalObjects(oldValue, v) && webComponent.hasEvent(onDataChange)) { JSONObject event = EventExecutor.createEvent(onDataChange, record.getParentFoundSet().getSelectedIndex()); Object returnValue = null; Exception exception = null; try { returnValue = webComponent.executeEvent(onDataChange, new Object[] { oldValue, v, event }); } catch (Exception e) { Debug.error("Error during onDataChange webComponent=" + webComponent, e); exception = e; } String onDataChangeCallback = ((DataproviderConfig)webComponent.getFormElement().getWebComponentSpec().getProperty( beanProperty).getConfig()).getOnDataChangeCallback(); if (onDataChangeCallback != null) { WebObjectApiDefinition call = new WebObjectApiDefinition(onDataChangeCallback); call.addParameter(new PropertyDescription("event", TypesRegistry.getType("object"))); call.addParameter(new PropertyDescription("returnValue", TypesRegistry.getType("object"))); call.addParameter(new PropertyDescription("exception", TypesRegistry.getType("object"))); webComponent.invokeApi(call, new Object[] { event, returnValue, exception == null ? null : exception.getMessage() }); } } } } public void startEdit(WebFormComponent webComponent, String property) { String dataProviderID = getDataProviderID(webComponent, property); if (dataProviderID == null) { Debug.log("startEdit called on a property that is not bound to a dataprovider: " + property + " of component: " + webComponent); return; } if (record != null && !ScopesUtils.isVariableScope(dataProviderID)) { int selectedIndex = record.getParentFoundSet().getSelectedIndex(); int rowIndex = record.getParentFoundSet().getRecordIndex(record); if (selectedIndex != rowIndex) { record.getParentFoundSet().setSelectedIndex(rowIndex); } record.startEditing(); } } public String getStringValue(String name) { String stringValue = TagResolver.formatObject(getValueObject(record, name), getApplication().getLocale(), getApplication().getSettings()); return processValue(stringValue, name, null); // TODO last param ,IDataProviderLookup, should be implemented } public static String processValue(String stringValue, String dataProviderID, IDataProviderLookup dataProviderLookup) { if (stringValue == null) { if ("selectedIndex".equals(dataProviderID) || isCountOrAvgOrSumAggregateDataProvider(dataProviderID, dataProviderLookup)) //$NON-NLS-1$ { return "0"; //$NON-NLS-1$ } } return stringValue; } // helper method; not static because needs form scope public Object getValueObject(IRecord recordToUse, String dataProviderId) { // return record.getValue(dataProviderId); return com.servoy.j2db.dataprocessing.DataAdapterList.getValueObject(recordToUse, formController.getFormScope(), dataProviderId); // needed for tagString processing (so not just record values but also variables) } public boolean isCountOrAvgOrSumAggregateDataProvider(IDataAdapter dataAdapter) { return isCountOrAvgOrSumAggregateDataProvider(dataAdapter.getDataProviderID(), null); } private static boolean isCountOrAvgOrSumAggregateDataProvider(String dataProvider, IDataProviderLookup dataProviderLookup) { try { if (dataProviderLookup == null) { return false; } IDataProvider dp = dataProviderLookup.getDataProvider(dataProvider); if (dp instanceof ColumnWrapper) { dp = ((ColumnWrapper)dp).getColumn(); } if (dp instanceof AggregateVariable) { int aggType = ((AggregateVariable)dp).getType(); return aggType == QueryAggregate.COUNT || aggType == QueryAggregate.AVG || aggType == QueryAggregate.SUM; } } catch (Exception ex) { Debug.error(ex); } return false; } @Override public void setFindMode(boolean findMode) { if (this.findMode != findMode) { this.findMode = findMode; ((BaseWebObject)formController.getFormUI()).setProperty("findmode", findMode); for (IFindModeAwarePropertyValue x : findModeAwareProperties) { x.findModeChanged(findMode); } } } public void notifyVisible(boolean b, List<Runnable> invokeLaterRunnables, Set<IWebFormController> childFormsThatWereAlreadyNotified) { HashMap<IWebFormController, String> childFormsCopy = new HashMap<IWebFormController, String>(visibleChildForms); for (IWebFormController relatedController : childFormsCopy.keySet()) { updateParentContainer(relatedController, childFormsCopy.get(relatedController), b); if (!childFormsThatWereAlreadyNotified.contains(relatedController)) relatedController.notifyVisible(b, invokeLaterRunnables); } } public boolean stopUIEditing(boolean looseFocus) { for (IWebFormController relatedController : visibleChildForms.keySet()) { if (!relatedController.stopUIEditing(looseFocus)) return false; } return true; } private void updateParentContainer(IWebFormController relatedController, String relationName, boolean visible) { if (((BasicFormController)relatedController).isDestroyed()) return; if (visible) { WebFormComponent parentContainer = null; Collection<WebComponent> components = formController.getFormUI().getComponents(); for (WebComponent component : components) { // legacy behavior Object tabs = component.getProperty("tabs"); if (tabs instanceof List && ((List)tabs).size() > 0) { List tabsList = (List)tabs; for (int i = 0; i < tabsList.size(); i++) { Map<String, Object> tab = (Map<String, Object>)tabsList.get(i); if (tab != null) { String relation = tab.get("relationName") != null ? tab.get("relationName").toString() : null; Object form = tab.get("containsFormId"); if (Utils.equalObjects(form, relatedController.getName()) && Utils.equalObjects(relation, relationName)) { parentContainer = (WebFormComponent)component; break; } } } } } relatedController.getFormUI().setParentContainer(parentContainer); } } public void destroy() { if (record != null) { setRecord(null, false); } if (formController != null && formController.getFormScope() != null) { formController.getFormScope().getModificationSubject().removeModificationListener(this); } if (formController != null && formController.getApplication() != null && formController.getApplication().getScriptEngine() != null) { IExecutingEnviroment er = formController.getApplication().getScriptEngine(); if (er.getScopesScope() != null) { er.getScopesScope().getModificationSubject().removeModificationListener(this); } } dataProviderToLinkedComponentProperty.clear(); allComponentPropertiesLinkedToData.clear(); findModeAwareProperties.clear(); parentRelatedForms.clear(); visibleChildForms.clear(); uninitializedVisibleChildForms.clear(); } }