/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2015 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.persistence; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.sablo.specification.PropertyDescription; import org.sablo.specification.WebComponentSpecProvider; import org.sablo.specification.WebObjectSpecification; import org.sablo.specification.property.CustomJSONArrayType; import org.sablo.specification.property.ICustomType; import org.sablo.specification.property.IPropertyType; import org.sablo.websocket.utils.PropertyUtils; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.ServoyJSONArray; import com.servoy.j2db.util.ServoyJSONObject; import com.servoy.j2db.util.UUID; /** * Common implementation for spec/property description based persists that helps working with JSON content and nested persists. * * @author acostescu */ public class WebObjectImpl extends WebObjectBasicImpl { private final Map<String, Object> persistMappedPropeties = new HashMap<String, Object>(); // value can be IChildWebObject or IChildWebObject[] (ChildWebComponents or WebCustomTypes) // TODO should we have a map that contains all values from the JSON not only the above for IChildWebObject - or at least get/set/clear/has should handle all json values, // not just persist mapped one (see commented out code in those methods) private boolean arePersistMappedPropetiesLoaded = false; private PropertyDescription pdUseGetterInstead; private Map<UUID, IPersist> persistMappedPropetiesByUUID = null; // cached just like in AbstractBase private boolean gettingTypeName; /** * This constructor is to be used if getTypeName is the name of a WebComponent. (so it can be used to get the component spec) */ public WebObjectImpl(IBasicWebObject webObject) { super(webObject); } public PropertyDescription getPropertyDescription() { // at the time WebComponent is created the resources project is not yet loaded nor is the typeName property set; so find it when it's needed in this case if (pdUseGetterInstead == null && !gettingTypeName) { gettingTypeName = true; try { pdUseGetterInstead = WebComponentSpecProvider.getInstance() != null ? WebComponentSpecProvider.getInstance().getWebComponentSpecification(getTypeName()) : null; } finally { gettingTypeName = false; } } return pdUseGetterInstead; } protected PropertyDescription getChildPropertyDescription(String propertyName) { if (getPropertyDescription() != null) return getPropertyDescription().getProperty(propertyName); return null; } public WebObjectImpl(IBasicWebObject webObject, Object specPD) { super(webObject); this.pdUseGetterInstead = (PropertyDescription)specPD; } @Override public void updatePersistMappedPropeties() { if (arePersistMappedPropetiesLoaded) { JSONObject old = getJson(); try { JSONObject entireModel = (old != null ? old : new ServoyJSONObject()); // we have to keep the same instance if possible cause otherwise com.servoy.eclipse.designer.property.UndoablePropertySheetEntry would set child but restore completely from parent when modifying a child value in case of nested properties Iterator<String> it = entireModel.keys(); // remove custom properties that were removed (be sure to keep any keys that do not map to custom properties or arrays of custom properties - for example ints, string arrays and so on) while (it.hasNext()) { String key = it.next(); if (isPersistMappedProperty(key) && !getPersistMappedProperties().containsKey(key)) entireModel.remove(key); } for (Map.Entry<String, Object> wo : getPersistMappedProperties().entrySet()) { if (wo.getValue() == null) entireModel.put(wo.getKey(), JSONObject.NULL); else if (wo.getValue() instanceof IChildWebObject) { entireModel.put(wo.getKey(), ((IChildWebObject)wo.getValue()).getFullJsonInFrmFile()); } else { ServoyJSONArray jsonArray = new ServoyJSONArray(); for (IChildWebObject wo1 : (IChildWebObject[])wo.getValue()) { jsonArray.put(wo1.getFullJsonInFrmFile()); } entireModel.put(wo.getKey(), jsonArray); } } setJsonInternal(entireModel); ((AbstractBase)webObject).flagChanged(); } catch (JSONException ex) { Debug.error(ex); } } } protected boolean isPersistMappedProperty(String key) { return isCustomJSONObjectOrArrayOfCustomJSONObject(key) || isComponentOrArrayOfComponent(key); } protected boolean isArrayOfCustomJSONObject(IPropertyType< ? > propertyType) { boolean arrayReturnType = PropertyUtils.isCustomJSONArrayPropertyType(propertyType); if (arrayReturnType) { PropertyDescription elementPD = (propertyType instanceof ICustomType< ? >) ? ((ICustomType< ? >)propertyType).getCustomJSONTypeDefinition() : null; if (elementPD != null && PropertyUtils.isCustomJSONObjectProperty(elementPD.getType())) { return true; } } return false; } /** * Return true if this property is handled as a custom property persist and false otherwise. * @param key the subproperty name. * @return true if this property is handled as a custom property persist and false otherwise. */ protected boolean isCustomJSONObjectOrArrayOfCustomJSONObject(String key) { if (getPropertyDescription() == null) return false; // typeName is not yet set; so normally typed properties are not yet accessed PropertyDescription childPd = getChildPropertyDescription(key); if (childPd != null) { IPropertyType< ? > propertyType = childPd.getType(); return PropertyUtils.isCustomJSONObjectProperty(propertyType) || isArrayOfCustomJSONObject(propertyType); } return false; } protected boolean isComponent(IPropertyType< ? > propertyType) { return ChildWebComponent.COMPONENT_PROPERTY_TYPE_NAME.equals(propertyType.getName()); } protected boolean isArrayOfComponent(IPropertyType< ? > propertyType) { boolean arrayReturnType = PropertyUtils.isCustomJSONArrayPropertyType(propertyType); if (arrayReturnType) { PropertyDescription elementPD = (propertyType instanceof ICustomType< ? >) ? ((ICustomType< ? >)propertyType).getCustomJSONTypeDefinition() : null; if (elementPD != null && ChildWebComponent.COMPONENT_PROPERTY_TYPE_NAME.equals(elementPD.getType().getName())) { return true; } } return false; } /** * Return true if this property is handled as a custom property persist and false otherwise. * @param key the subproperty name. * @return true if this property is handled as a custom property persist and false otherwise. */ protected boolean isComponentOrArrayOfComponent(String key) { if (getPropertyDescription() == null) return false; // typeName is not yet set; so normally typed properties are not yet accessed PropertyDescription childPd = getChildPropertyDescription(key); if (childPd != null) { IPropertyType< ? > propertyType = childPd.getType(); return isComponent(propertyType) || isArrayOfComponent(propertyType); } return false; } /** * Returns false if it can't set is as a json property. Then caller should set it as another standard persist property. */ @Override public boolean setProperty(String propertyName, Object val) { if (getPropertyDescription() != null) { PropertyDescription childPd = getChildPropertyDescription(propertyName); if (childPd == null && getPropertyDescription() instanceof WebObjectSpecification) childPd = ((WebObjectSpecification)getPropertyDescription()).getHandler(propertyName); if (childPd != null) { IPropertyType< ? > propertyType = childPd.getType(); if ((val == null && isPersistMappedProperty(propertyName)) || (val instanceof WebCustomType && PropertyUtils.isCustomJSONObjectProperty(propertyType)) || (val instanceof IChildWebObject[] && isArrayOfCustomJSONObject(propertyType)) || (val instanceof WebComponent && isComponent(propertyType)) || (val instanceof IChildWebObject[] && isArrayOfComponent(propertyType))) { if (getJson() == null) setJson(new ServoyJSONObject()); getPersistMappedProperties().put(propertyName, val); persistMappedPropetiesByUUID = null; updatePersistMappedPropeties(); return true; } else { // it is a json property defined in spec, but it's not mapping to a persist setOrRemoveJsonSubproperty(propertyName, convertFromJavaType(propertyName, val), false); return true; } } } return false; // typeName is not yet set (so normally typed properties are not yet accessed) or it's not a typed property } /** * Returns false if it can't clear this as json property (it is something else). Then caller should just clear it as another standard persist property. */ @Override public boolean clearProperty(String propertyName) { Map<String, Object> persistMappedProperties = getPersistMappedProperties(); if (persistMappedProperties.containsKey(propertyName)) { persistMappedProperties.remove(propertyName); persistMappedPropetiesByUUID = null; updatePersistMappedPropeties(); return true; } else if (getPropertyDescription() != null) { // IMPORTANT if we decide that this method shouldn't affect all json properties and we remove the following code, we have to update code // in CustomJSONObjectTypePropertyController.CustomJSONObjectPropertySource.defaultResetProperty(Object) because underlyingPropertySource.defaultResetProperty(id); // depends on this in the end (the same for WebComponentPropertySource) PropertyDescription childPd = getChildPropertyDescription(propertyName); if (childPd == null && getPropertyDescription() instanceof WebObjectSpecification) childPd = ((WebObjectSpecification)getPropertyDescription()).getHandler(propertyName); if (childPd != null) { // it is a json property defined in spec, but it's not mapping to a persist setOrRemoveJsonSubproperty(propertyName, null, true); } } return false; } @Override public boolean hasProperty(String propertyName) { boolean hasIt = getPersistMappedProperties().containsKey(propertyName); if (!hasIt && getPropertyDescription() != null) { PropertyDescription childPd = getChildPropertyDescription(propertyName); if (childPd != null) { // it is a json property defined in spec, but it's not mapping to a persist JSONObject json = getJson(); hasIt = (json != null && json.has(propertyName)); } } return hasIt; } @Override public Object getProperty(String propertyName) { Map<String, Object> ctp = getPersistMappedProperties(); if (ctp.containsKey(propertyName)) return ctp.get(propertyName); if (getPropertyDescription() != null) { PropertyDescription childPd = getChildPropertyDescription(propertyName); if (childPd == null && getPropertyDescription() instanceof WebObjectSpecification) childPd = ((WebObjectSpecification)getPropertyDescription()).getHandler(propertyName); if (childPd != null) { // it is a json property defined in spec, but it's not mapping to a persist JSONObject json = getJson(); Object value = json != null ? json.opt(propertyName) : null; value = convertToJavaType(childPd, value); if (value instanceof JSONArray) { PropertyDescription desc = null; if (childPd.getType() instanceof CustomJSONArrayType< ? , ? >) { desc = ((CustomJSONArrayType< ? , ? >)childPd.getType()).getCustomJSONTypeDefinition(); } JSONArray arr = (JSONArray)value; Object[] java_arr = new Object[arr.length()]; for (int i = 0; i < arr.length(); i++) { java_arr[i] = convertToJavaType(desc, arr.get(i)); } return java_arr; } return value; } } return null; } private Object convertToJavaType(PropertyDescription childPd, Object val) { Object value = val; IDesignValueConverter< ? > converter = null; if (value != null && childPd != null && (converter = getConverter(childPd)) != null) { value = converter.fromDesignValue(value, childPd); } return (val != JSONObject.NULL) ? value : null; } private IDesignValueConverter< ? > getConverter(PropertyDescription pd) { return (pd.getType() instanceof IDesignValueConverter< ? >) ? (IDesignValueConverter< ? >)pd.getType() : null; } @Override public List<IChildWebObject> getAllPersistMappedProperties() { ArrayList<IChildWebObject> allPersistMappedProperties = new ArrayList<IChildWebObject>(); for (Object wo : getPersistMappedProperties().values()) { if (wo != null) { if (wo.getClass().isArray()) { allPersistMappedProperties.addAll(Arrays.asList((IChildWebObject[])wo)); } else { allPersistMappedProperties.add((IChildWebObject)wo); } } // if (wo instanceof WebCustomType[]) // { // allCustomProperties.addAll(Arrays.asList((WebCustomType[])wo)); // } // else if (wo != null) // { // allCustomProperties.add((WebCustomType)wo); // } } return allPersistMappedProperties; } private Map<String, Object> getPersistMappedProperties() { if (!arePersistMappedPropetiesLoaded) { arePersistMappedPropetiesLoaded = true; // do this here rather then later to avoid stack overflows in case code below end up calling persist.getProperty() again if (getPropertyDescription() != null && getJson() != null) { JSONObject beanJSON = getJson(); try { for (String beanJSONKey : ServoyJSONObject.getNames(beanJSON)) { Object object = beanJSON.get(beanJSONKey); updatePersistMappedProperty(beanJSONKey, object); } } catch (JSONException e) { Debug.error(e); } } else arePersistMappedPropetiesLoaded = false; // maybe the solution is being activated as we speak and the property descriptions from resources project are not yet available... } return persistMappedPropeties; } protected void updatePersistMappedProperty(String beanJSONKey, Object object) { if (object != null && getChildPropertyDescription(beanJSONKey) != null) { PropertyDescription childPd = getChildPropertyDescription(beanJSONKey); IPropertyType< ? > propertyType = childPd.getType(); String simpleTypeName = PropertyUtils.getSimpleNameOfCustomJSONTypeProperty(propertyType); if (isPersistMappedProperty(beanJSONKey)) { if (ServoyJSONObject.isJavascriptUndefined(object)) { persistMappedPropeties.remove(beanJSONKey); persistMappedPropetiesByUUID = null; } else if (ServoyJSONObject.isJavascriptNull(object)) { persistMappedPropeties.put(beanJSONKey, null); persistMappedPropetiesByUUID = null; } else { boolean arrayReturnType = PropertyUtils.isCustomJSONArrayPropertyType(propertyType); if (!arrayReturnType) { if (isComponent(propertyType)) { ChildWebComponent childComponent = ChildWebComponent.createNewInstance(webObject, childPd, beanJSONKey, -1, false); persistMappedPropeties.put(beanJSONKey, childComponent); persistMappedPropetiesByUUID = null; } else if (PropertyUtils.isCustomJSONObjectProperty(propertyType)) { WebCustomType webCustomType = WebCustomType.createNewInstance(webObject, childPd, beanJSONKey, -1, false); webCustomType.setTypeName(simpleTypeName); persistMappedPropeties.put(beanJSONKey, webCustomType); persistMappedPropetiesByUUID = null; } } else if (object instanceof JSONArray) { PropertyDescription elementPD = (propertyType instanceof ICustomType< ? >) ? ((ICustomType< ? >)propertyType).getCustomJSONTypeDefinition() : null; if (elementPD != null) { ArrayList<IChildWebObject> persistMappedPropertyArray = new ArrayList<IChildWebObject>(); if (PropertyUtils.isCustomJSONObjectProperty(elementPD.getType())) { for (int i = 0; i < ((JSONArray)object).length(); i++) { WebCustomType webCustomType = WebCustomType.createNewInstance(webObject, elementPD, beanJSONKey, i, false); webCustomType.setTypeName(simpleTypeName); persistMappedPropertyArray.add(webCustomType); } } else if (isComponent(propertyType)) { for (int i = 0; i < ((JSONArray)object).length(); i++) { ChildWebComponent childComponent = ChildWebComponent.createNewInstance(webObject, elementPD, beanJSONKey, i, false); persistMappedPropertyArray.add(childComponent); } } persistMappedPropeties.put(beanJSONKey, persistMappedPropertyArray.toArray(new IChildWebObject[persistMappedPropertyArray.size()])); persistMappedPropetiesByUUID = null; } } else { Debug.error("Typed property value ('" + beanJSONKey + "') is not JSONArray although in spec it is defined as array... " + this + " - " + object); } } } } else { if (persistMappedPropeties.remove(beanJSONKey) != null) persistMappedPropetiesByUUID = null; } } @Override public void setJson(JSONObject arg) { clearPersistMappedPropertyCache(); // let them completely reload later when needed setJsonInternal(arg); // update JSON property of all parent web objects as all this web object hierarchy is actually described by top-most web object JSON property // and the JSON of each web object should never get out-of-sync with the child web objects it contains ISupportChilds parent = webObject.getParent(); if (parent instanceof IBasicWebObject) ((IBasicWebObject)parent).updateJSON(); } @Override public void setJsonSubproperty(String key, Object value) { setOrRemoveJsonSubproperty(key, value, false); } private boolean setOrRemoveJsonSubproperty(String key, Object value, boolean remove) { try { boolean removed = false; JSONObject oldJson = getJson(); // we can no longer check for differences here as we now reuse JSON objects/arrays JSONObject jsonObject = (oldJson == null ? new ServoyJSONObject() : oldJson); // we have to keep the same instance if possible cause otherwise com.servoy.eclipse.designer.property.UndoablePropertySheetEntry would set child but restore completely from parent when modifying a child value in case of nested properties if (remove) { removed = (jsonObject.remove(key) != null); } else { jsonObject.put(key, value); } setJsonInternal(jsonObject); ((AbstractBase)webObject).flagChanged(); if (arePersistMappedPropetiesLoaded && getPropertyDescription() != null) { updatePersistMappedProperty(key, value); // update this web object's child web objects if needed (if this key affects them) } // else not yet loaded - they will all load later so nothing to do here // update JSON property of all parent web objects as all this web object hierarchy is actually described by top-most web object JSON property // and the JSON of each web object should never get out-of-sync with the child web objects it contains ISupportChilds parent = webObject.getParent(); if (parent instanceof IBasicWebObject) ((IBasicWebObject)parent).updateJSON(); return removed; } catch (JSONException e) { Debug.error(e); } return false; } private Object convertFromJavaType(String propertyName, Object value) { PropertyDescription pd = getChildPropertyDescription(propertyName); if (pd != null && getConverter(pd) != null) { return getConverter(pd).toDesignValue(value, pd); } return value; } @Override public boolean removeJsonSubproperty(String key) { return setOrRemoveJsonSubproperty(key, null, true); } protected void clearPersistMappedPropertyCache() { arePersistMappedPropetiesLoaded = false; persistMappedPropeties.clear(); } @Override public void setJsonInternal(JSONObject arg) { ((AbstractBase)webObject).setTypedProperty(StaticContentSpecLoader.PROPERTY_JSON, arg); } @Override public void internalAddChild(IPersist obj) { if (obj instanceof IChildWebObject) { IChildWebObject persistMappedPropertyValue = (IChildWebObject)obj; IPropertyType< ? > type = persistMappedPropertyValue.getPropertyDescription().getType(); if (getPropertyDescription().isArrayReturnType(persistMappedPropertyValue.getJsonKey())) { if (type == ((CustomJSONArrayType< ? , ? >)getChildPropertyDescription( persistMappedPropertyValue.getJsonKey()).getType()).getCustomJSONTypeDefinition().getType()) { Object children = getPersistMappedProperties().get(persistMappedPropertyValue.getJsonKey()); if (children == null) children = new IChildWebObject[0]; if (children instanceof IChildWebObject[]) { List<IChildWebObject> t = new ArrayList<IChildWebObject>(Arrays.asList((IChildWebObject[])children)); t.add(persistMappedPropertyValue.getIndex(), persistMappedPropertyValue); for (int i = persistMappedPropertyValue.getIndex() + 1; i < t.size(); i++) { IChildWebObject ct = t.get(i); ct.setIndex(i); } setProperty(persistMappedPropertyValue.getJsonKey(), t.toArray(new IChildWebObject[t.size()])); } else { Debug.error("Unexpected array property persist value: " + children); } } else { Debug.error("Element type (" + ((CustomJSONArrayType< ? , ? >)getChildPropertyDescription( persistMappedPropertyValue.getJsonKey()).getType()).getCustomJSONTypeDefinition().getType() + ") does not match persist-to-add type: " + type + " - " + webObject); } } else { if (type == getChildPropertyDescription(persistMappedPropertyValue.getJsonKey()).getType()) { setProperty(persistMappedPropertyValue.getJsonKey(), persistMappedPropertyValue); } else { Debug.error("Property type (" + getChildPropertyDescription(persistMappedPropertyValue.getJsonKey()).getType() + ") does not match persist-to-add type: " + type + " - " + webObject); } } persistMappedPropetiesByUUID = null; } else { Debug.error("Trying to add non - IChildWebObject to a WebObject: " + obj + ", " + webObject); } } @Override public void internalRemoveChild(IPersist obj) { if (obj instanceof IChildWebObject) { IChildWebObject customType = (IChildWebObject)obj; Object children = getPersistMappedProperties().get(customType.getJsonKey()); if (children != null) { if (children instanceof IChildWebObject[]) { List<IChildWebObject> t = new ArrayList<IChildWebObject>(Arrays.asList((IChildWebObject[])children)); t.remove(customType); for (int i = customType.getIndex(); i < t.size(); i++) { IChildWebObject ct = t.get(i); ct.setIndex(i); } setProperty(customType.getJsonKey(), t.toArray(new IChildWebObject[t.size()])); } else { clearProperty(customType.getJsonKey()); } } persistMappedPropetiesByUUID = null; } else { Debug.error("Trying to remove non - IChildWebObject from a WebObject: " + obj + ", " + webObject); } } @Override public Iterator<IPersist> getAllObjects() { final Iterator<Object> it1 = getPersistMappedProperties().values().iterator(); return new CustomTypesIterator(it1); } protected static class CustomTypesIterator implements Iterator<IPersist> { IChildWebObject nextSingleObj; IChildWebObject[] nextArrayObj; int nextArrayObjIdx = 0; private final Iterator<Object> it; public CustomTypesIterator(Iterator<Object> it) { this.it = it; prepareNext(); } protected void prepareNext() { if (nextArrayObj != null) { // find next non-null in array while (nextArrayObjIdx < nextArrayObj.length && nextArrayObj[nextArrayObjIdx] == null) { nextArrayObjIdx++; } } if (!hasArrayItems()) { nextArrayObj = null; nextArrayObjIdx = 0; nextSingleObj = null; if (it.hasNext()) { Object o = it.next(); while (!prepareNextItemFromElement(o) && it.hasNext()) { o = it.next(); } } } } protected boolean prepareNextItemFromElement(Object o) { if (o instanceof IChildWebObject) { nextSingleObj = (IChildWebObject)o; return true; } else if (o instanceof IChildWebObject[]) { IChildWebObject[] arr = (IChildWebObject[])o; int i = 0; while (i < arr.length) { if (arr[i] != null) { nextArrayObj = arr; nextArrayObjIdx = i; return true; } } return false; } return false; } private boolean hasArrayItems() { return nextArrayObj != null && nextArrayObjIdx < nextArrayObj.length; } @Override public boolean hasNext() { return (nextSingleObj != null || hasArrayItems()); } @Override public IPersist next() { if (hasNext()) { IChildWebObject toReturn; if (nextSingleObj != null) toReturn = nextSingleObj; else toReturn = nextArrayObj[nextArrayObjIdx++]; prepareNext(); return toReturn; } else throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } } @Override public IPersist getChild(UUID childUuid) { if (persistMappedPropetiesByUUID == null) { Iterator<IPersist> allobjects = getAllObjects(); if (allobjects != null && allobjects.hasNext()) { persistMappedPropetiesByUUID = new ConcurrentHashMap<UUID, IPersist>(persistMappedPropeties.size(), 0.9f, 16); while (allobjects.hasNext()) { IPersist persist = allobjects.next(); if (persist != null) { persistMappedPropetiesByUUID.put(persist.getUUID(), persist); } } } } return persistMappedPropetiesByUUID == null ? null : persistMappedPropetiesByUUID.get(childUuid); } public static Pair<Integer, UUID> getNewIdAndUUID(IPersist persist) { UUID uuid = UUID.randomUUID(); int id; try { id = ((IPersistFactory)((Solution)persist.getAncestor(IRepository.SOLUTIONS)).getRepository()).getNewElementID(uuid); } catch (RepositoryException e) { Debug.error(e); id = 0; } return new Pair<>(Integer.valueOf(id), uuid); } public static JSONObject getFullJSONInFrmFile(IChildWebObject webObject, boolean isNew) { IBasicWebObject parentWebObject = webObject.getParent(); try { JSONObject entireModel = (parentWebObject.getFlattenedJson() != null ? parentWebObject.getFlattenedJson() : new ServoyJSONObject()); if (!isNew && entireModel.has(webObject.getJsonKey())) { Object v = entireModel.get(webObject.getJsonKey()); JSONObject obj = null; if (v instanceof JSONArray) { obj = ((JSONArray)v).optJSONObject(webObject.getIndex()); } else { obj = entireModel.getJSONObject(webObject.getJsonKey()); } return obj; } else { return new ServoyJSONObject(); } } catch (JSONException e) { Debug.error(e); } return null; } }