/* 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.server.ngclient.property; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONWriter; import org.mozilla.javascript.Scriptable; import org.sablo.BaseWebObject; import org.sablo.specification.IYieldingType; import org.sablo.specification.PropertyDescription; import org.sablo.specification.WebObjectSpecification; import org.sablo.specification.property.IBrowserConverterContext; import org.sablo.specification.property.IConvertedPropertyType; import org.sablo.specification.property.IPropertyType; import org.sablo.specification.property.ISupportsGranularUpdates; import org.sablo.util.ValueReference; import org.sablo.websocket.utils.DataConversion; import org.sablo.websocket.utils.JSONUtils; import com.servoy.j2db.FlattenedSolution; import com.servoy.j2db.server.ngclient.DataAdapterList; import com.servoy.j2db.server.ngclient.FormElement; import com.servoy.j2db.server.ngclient.FormElementContext; import com.servoy.j2db.server.ngclient.INGFormElement; import com.servoy.j2db.server.ngclient.WebFormComponent; import com.servoy.j2db.server.ngclient.property.types.IDataLinkedType; import com.servoy.j2db.server.ngclient.property.types.IFindModeAwareType; import com.servoy.j2db.server.ngclient.property.types.ISupportTemplateValue; import com.servoy.j2db.server.ngclient.property.types.IWrapperDataLinkedType; import com.servoy.j2db.server.ngclient.property.types.NGConversions; import com.servoy.j2db.server.ngclient.property.types.NGConversions.IFormElementToSabloComponent; import com.servoy.j2db.server.ngclient.property.types.NGConversions.IFormElementToTemplateJSON; import com.servoy.j2db.server.ngclient.property.types.NGConversions.IRhinoToSabloComponent; import com.servoy.j2db.server.ngclient.property.types.NGConversions.ISabloComponentToRhino; import com.servoy.j2db.util.Pair; /** * This property type can be used to give "foundset linked" capabilities to existing property types.<br/> * So many property types can be wrapped by this type to be able to provide values for an entire foundset instead of just one value.<br/><br/> * * When config option "forFoundset" is not present in the .spec file for such a property, the PropertyDescription will fall back to the wrapped * property type. * * For example the DataproviderPropertyType can be wrapped in FoundsetLinkedPropertyType and registered as "dataprovider". Then when used in spec. files * like "myProp: 'dataprovider'" it will be treated directly as DataproviderPropertyType (that is what the {@link PropertyDescription#getType()} will return) avoiding any * overhead a proxy-like implementation of FoundsetLinkedPropertyType would add.<br/><br/> * * But when a property is declared as "myProp: {type: 'dataprovider', forFoundset: ...}" then {@link PropertyDescription#getType()} will return and work with this FoundsetLinkedPropertyType. * * It will work with a the wrapped type that uses a IServoyAwarePropertyValue (for YT - as this is it's main goal - record linked values) but can work with non-record linked values as well (if wrapped * property type decides to change this at runtime for example). * * FIXME NOTE: as more ICanBeLinkedToFoundset types are added we must make sure that FoundsetLinkedPropertyType implements all type interfaces of the wrapped types for it to work correctly! * * @author acostescu */ public class FoundsetLinkedPropertyType<YF, YT> implements IYieldingType<FoundsetLinkedTypeSabloValue<YF, YT>, YT>, IFormElementToTemplateJSON<YF, FoundsetLinkedTypeSabloValue<YF, YT>>, ISupportTemplateValue<YF>, IWrapperDataLinkedType<YF, FoundsetLinkedTypeSabloValue<YF, YT>>, IFormElementToSabloComponent<YF, FoundsetLinkedTypeSabloValue<YF, YT>>, IConvertedPropertyType<FoundsetLinkedTypeSabloValue<YF, YT>>, IFindModeAwareType<YF, FoundsetLinkedTypeSabloValue<YF, YT>>, ISabloComponentToRhino<FoundsetLinkedTypeSabloValue<YF, YT>>, IRhinoToSabloComponent<FoundsetLinkedTypeSabloValue<YF, YT>>, ISupportsGranularUpdates<FoundsetLinkedTypeSabloValue<YF, YT>> { protected static final String SINGLE_VALUE = "sv"; //$NON-NLS-1$ protected static final String SINGLE_VALUE_UPDATE = "svu"; //$NON-NLS-1$ protected static final String VIEWPORT_VALUE = "vp"; //$NON-NLS-1$ protected static final String VIEWPORT_VALUE_UPDATE = "vpu"; //$NON-NLS-1$ protected static final String CONVERSION_NAME = "fsLinked"; public static final String FOR_FOUNDSET_PROPERTY_NAME = "forFoundset"; //$NON-NLS-1$ protected final String name; protected ICanBeLinkedToFoundset<YF, YT> wrappedType; public FoundsetLinkedPropertyType(String name, ICanBeLinkedToFoundset<YF, YT> wrappedType) { this.name = name; this.wrappedType = wrappedType; } @Override public String getName() { return name; } @Override public IPropertyType< ? > yieldToOtherIfNeeded(String propertyName, YieldDescriptionArguments parameters) { FoundsetLinkedConfig cfg = (FoundsetLinkedConfig)parameters.getConfig(); if (cfg == null || cfg.forFoundset == null) { // the wrapped type can do it's thing then; it's not linked to a foundset so we yield to it's impl. parameters.setConfig(cfg != null ? cfg.wrappedConfig : wrappedType.parseConfig(null)); return wrappedType; } FoundsetLinkedConfig config = ((FoundsetLinkedConfig)parameters.getConfig()); config.setWrappedPropertyDescription(new PropertyDescription(propertyName, wrappedType, ((FoundsetLinkedConfig)parameters.getConfig()).wrappedConfig, parameters.defaultValue, parameters.defaultValue != null, parameters.values, parameters.pushToServer, parameters.tags, parameters.optional)); return this; } @Override public IPropertyType<YT> getPossibleYieldType() { return wrappedType; } @Override public FoundsetLinkedConfig parseConfig(JSONObject config) { return config == null ? null : new FoundsetLinkedConfig(config.optString(FOR_FOUNDSET_PROPERTY_NAME, null), wrappedType.parseConfig(config)); } @Override public FoundsetLinkedTypeSabloValue<YF, YT> defaultValue(PropertyDescription pd) { YT wrappedDefault = wrappedType.defaultValue(getConfig(pd).wrappedPropertyDescription); return wrappedDefault == null ? null : new FoundsetLinkedTypeSabloValue<YF, YT>(wrappedDefault, getConfig(pd).forFoundset); } @Override public boolean isProtecting() { return wrappedType.isProtecting(); } @Override public TargetDataLinks getDataLinks(YF formElementValue, PropertyDescription pd, FlattenedSolution flattenedSolution, FormElement formElement) { if (wrappedType instanceof IDataLinkedType) { PropertyDescription wrappedPd = getConfig(pd).wrappedPropertyDescription; TargetDataLinks r = ((IDataLinkedType<YF, YT>)wrappedType).getDataLinks(formElementValue, wrappedPd, flattenedSolution, formElement); if (!WebObjectSpecification.ARRAY_ELEMENT_PD_NAME.equals(wrappedPd.getName())) { formElement.getOrCreatePreprocessedPropertyInfoMap(IDataLinkedType.class).put(wrappedPd, r); } return r; } return TargetDataLinks.NOT_LINKED_TO_DATA; } @Override public boolean valueInTemplate(YF formElementValue, PropertyDescription pd, FormElementContext formElementContext) { return true; // even if wrapped value is not in template, we still send the "forFoundset" config value } @Override public JSONWriter toTemplateJSONValue(JSONWriter writer, String key, YF formElementValue, PropertyDescription pd, DataConversion browserConversionMarkers, FormElementContext formElementContext) throws JSONException { browserConversionMarkers.convert(CONVERSION_NAME); JSONUtils.addKeyIfPresent(writer, key); writer.object(); writer.key(FoundsetLinkedPropertyType.FOR_FOUNDSET_PROPERTY_NAME).value(getConfig(pd).forFoundset); if (wrappedType instanceof ISupportTemplateValue && ((ISupportTemplateValue<YF>)wrappedType).valueInTemplate(formElementValue, getConfig(pd).wrappedPropertyDescription, formElementContext)) { DataConversion dataConversions = new DataConversion(); dataConversions.pushNode(SINGLE_VALUE); NGConversions.INSTANCE.convertFormElementToTemplateJSONValue(writer, SINGLE_VALUE, formElementValue, getConfig(pd).wrappedPropertyDescription, browserConversionMarkers, formElementContext); JSONUtils.writeClientConversions(writer, dataConversions); } writer.endObject(); return writer; } protected FoundsetLinkedConfig getConfig(PropertyDescription pd) { return ((FoundsetLinkedConfig)pd.getConfig()); } @Override public FoundsetLinkedTypeSabloValue<YF, YT> toSabloComponentValue(YF formElementValue, PropertyDescription pd, INGFormElement formElement, WebFormComponent component, DataAdapterList dataAdapterList) { return new FoundsetLinkedTypeSabloValue<YF, YT>(getConfig(pd).forFoundset, formElementValue, getConfig(pd).wrappedPropertyDescription, formElement, component); } @Override public FoundsetLinkedTypeSabloValue<YF, YT> fromJSON(Object newJSONValue, FoundsetLinkedTypeSabloValue<YF, YT> previousSabloValue, PropertyDescription pd, IBrowserConverterContext dataConverterContext, ValueReference<Boolean> returnValueAdjustedIncommingValue) { if (previousSabloValue != null) { previousSabloValue.browserUpdatesReceived(newJSONValue, getConfig(pd).wrappedPropertyDescription, pd, dataConverterContext, returnValueAdjustedIncommingValue); } // else there's nothing to do here / this type can't receive browser updates when server has no value for it return previousSabloValue; } @Override public JSONWriter toJSON(JSONWriter writer, String key, FoundsetLinkedTypeSabloValue<YF, YT> sabloValue, PropertyDescription pd, DataConversion clientConversion, IBrowserConverterContext dataConverterContext) throws JSONException { return sabloValue.fullToJSON(writer, key, clientConversion, getConfig(pd).wrappedPropertyDescription, dataConverterContext); } @Override public JSONWriter changesToJSON(JSONWriter writer, String key, FoundsetLinkedTypeSabloValue<YF, YT> sabloValue, PropertyDescription pd, DataConversion clientConversion, IBrowserConverterContext dataConverterContext) throws JSONException { return sabloValue.changesToJSON(writer, key, clientConversion, getConfig(pd).wrappedPropertyDescription, dataConverterContext); } @Override public boolean isValueAvailableInRhino(FoundsetLinkedTypeSabloValue<YF, YT> webComponentValue, PropertyDescription pd, BaseWebObject componentOrService) { if (wrappedType instanceof ISabloComponentToRhino) return ((ISabloComponentToRhino<YT>)wrappedType).isValueAvailableInRhino( webComponentValue.getWrappedValue(), getConfig(pd).wrappedPropertyDescription, componentOrService); return true; } @Override public Object toRhinoValue(FoundsetLinkedTypeSabloValue<YF, YT> webComponentValue, PropertyDescription pd, BaseWebObject componentOrService, Scriptable startScriptable) { return NGConversions.INSTANCE.convertSabloComponentToRhinoValue(webComponentValue.getWrappedValue(), getConfig(pd).wrappedPropertyDescription, componentOrService, startScriptable); } @Override public FoundsetLinkedTypeSabloValue<YF, YT> toSabloComponentValue(Object rhinoValue, FoundsetLinkedTypeSabloValue<YF, YT> previousComponentValue, PropertyDescription pd, BaseWebObject componentOrService) { previousComponentValue.rhinoToSablo(rhinoValue, getConfig(pd).wrappedPropertyDescription, componentOrService); return previousComponentValue; } @Override public boolean isFindModeAware(YF formElementValue, PropertyDescription pd, FlattenedSolution flattenedSolution, FormElement formElement) { if (wrappedType instanceof IFindModeAwareType) { PropertyDescription wrappedPd = getConfig(pd).wrappedPropertyDescription; boolean r = ((IFindModeAwareType<YF, YT>)wrappedType).isFindModeAware(formElementValue, wrappedPd, flattenedSolution, formElement); if (!WebObjectSpecification.ARRAY_ELEMENT_PD_NAME.equals(wrappedPd.getName())) { formElement.getOrCreatePreprocessedPropertyInfoMap(IFindModeAwareType.class).put(wrappedPd, Boolean.valueOf(r)); } return r; } return false; } @Override public Pair<IDataLinkedPropertyValue, PropertyDescription> getWrappedDataLinkedValue(FoundsetLinkedTypeSabloValue<YF, YT> propertyValue, PropertyDescription pd) { if (propertyValue != null && propertyValue.wrappedSabloValue instanceof IDataLinkedPropertyValue) return new Pair(propertyValue.wrappedSabloValue, getConfig(pd).wrappedPropertyDescription); return null; } }