/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2014 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
import org.sablo.specification.PropertyDescription;
import org.sablo.specification.property.CustomJSONArrayType;
import com.servoy.j2db.FlattenedSolution;
import com.servoy.j2db.component.ComponentFactory;
import com.servoy.j2db.persistence.AbstractBase;
import com.servoy.j2db.persistence.Bean;
import com.servoy.j2db.persistence.Field;
import com.servoy.j2db.persistence.Form;
import com.servoy.j2db.persistence.IBasicWebComponent;
import com.servoy.j2db.persistence.IFlattenedPersistWrapper;
import com.servoy.j2db.persistence.IFormElement;
import com.servoy.j2db.persistence.IPersist;
import com.servoy.j2db.persistence.ISupportChilds;
import com.servoy.j2db.persistence.ISupportExtendsID;
import com.servoy.j2db.persistence.Portal;
import com.servoy.j2db.persistence.StaticContentSpecLoader;
import com.servoy.j2db.persistence.Tab;
import com.servoy.j2db.persistence.TabPanel;
import com.servoy.j2db.server.ngclient.property.ComponentPropertyType;
import com.servoy.j2db.server.ngclient.property.types.DataproviderPropertyType;
import com.servoy.j2db.server.ngclient.property.types.NGTabSeqPropertyType;
import com.servoy.j2db.server.ngclient.property.types.PropertyPath;
import com.servoy.j2db.server.ngclient.template.FormTemplateGenerator;
import com.servoy.j2db.util.ComponentFactoryHelper;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.PersistHelper;
import com.servoy.j2db.util.ServoyJSONObject;
/**
* Form element that is based on a persist.
*
* @author acostescu
*/
class PersistBasedFormElementImpl
{
private final IPersist persist;
private final FormElement formElement;
PersistBasedFormElementImpl(IPersist persist, FormElement formElement)
{
this.persist = persist;
this.formElement = formElement;
}
public Form getForm()
{
if (persist instanceof Form) return (Form)persist;
ISupportChilds parent = persist.getParent();
while (parent != null && !(parent instanceof Form))
{
parent = parent.getParent();
}
return (Form)parent;
}
public Map<String, Object> getFormElementPropertyValues(FlattenedSolution fs, Map<String, PropertyDescription> specProperties, PropertyPath propertyPath)
{
if (persist instanceof IBasicWebComponent)
{
if (FormTemplateGenerator.isWebcomponentBean(persist))
{
JSONObject jsonProperties = ((IBasicWebComponent)persist).getFlattenedJson();
if (jsonProperties == null) jsonProperties = new ServoyJSONObject();
// convert from persist design-time value (which might be non-json) to the expected value
Map<String, Object> jsonMap = processPersistProperties(fs, specProperties, propertyPath);
jsonMap.remove(StaticContentSpecLoader.PROPERTY_BEANXML); // this is handled separately as NG component definition
jsonMap.remove(StaticContentSpecLoader.PROPERTY_JSON); // this is handled separately as NG component definition
try
{
// add beanXML (which is actually a JSON string here) defined properties to the map
formElement.convertFromJSONToFormElementValues(fs, specProperties, jsonMap, formElement.getWebComponentSpec().getHandlers(),
jsonProperties, propertyPath);
}
catch (Exception ex)
{
Debug.error("Error while parsing bean design json", ex);
jsonMap.put("error", "Error while parsing bean design json(bean not supported in NGClient?): " + persist);
}
return jsonMap;
}
else
{
Map<String, Object> defaultProperties = new HashMap<String, Object>();
defaultProperties.put(StaticContentSpecLoader.PROPERTY_SIZE.getPropertyName(), ((IBasicWebComponent)persist).getSize());
defaultProperties.put(StaticContentSpecLoader.PROPERTY_NAME.getPropertyName(), ((IBasicWebComponent)persist).getName());
defaultProperties.put("error", "Bean not supported in NGClient: " + persist);
return defaultProperties;
}
}
else if (persist instanceof AbstractBase)
{
Map<String, Object> map = processPersistProperties(fs, specProperties, propertyPath);
if (persist instanceof Field && ((Field)persist).getDisplayType() == Field.MULTISELECT_LISTBOX)
{
map.put("multiselectListbox", Boolean.TRUE);
}
else if (persist instanceof TabPanel)
{
convertFromTabPanelToNGProperties((IFormElement)persist, fs, map, specProperties, propertyPath);
}
else if (persist instanceof Portal)
{
convertFromPortalToNGProperties((Portal)persist, fs, map, specProperties, propertyPath);
}
return map;
}
else
{
return Collections.emptyMap();
}
}
private Map<String, Object> processPersistProperties(FlattenedSolution fs, Map<String, PropertyDescription> specProperties, PropertyPath propertyPath)
{
Map<String, Object> jsonMap = convertSpecialPersistProperties(getFlattenedPropertiesMap(), fs, specProperties);
formElement.convertFromPersistPrimitivesToFormElementValues(fs, specProperties, formElement.getWebComponentSpec().getHandlers(), jsonMap, propertyPath);
return jsonMap;
}
/**
* Applies 'Conversion 0' (see https://support.servoy.com/browse/SVY-6666 attachment) to persist property values - from design to FormElement value. 'Conversion 0' is currently hardcoded as it's limited
* to a small set of persist properties - that are not likely to be added to when ng types develop further.
*
* AbstractBase.getPropertiesMap() returns for format, borderType etc the STRING representation instead of the high level class representation used in ngClient.
* It doesn't return JSON either so we can't use 'Conversion 1' type converters in this case.
*
* Converts string representation to high level Class representation of properties that will be the FormElement value of that property.
*/
private Map<String, Object> convertSpecialPersistProperties(Map<String, Object> propertiesMap, FlattenedSolution fs,
Map<String, PropertyDescription> specProperties)
{
// it is a bit strange here as from Persist we get
// 1. primitive values (some of which might need no conversion and some of which in NG client world need to be converted - for example TagStringPropertyType);
// primitives are not affected by this method and can further undergo 'Conversion 1'
// 2. actual values (like Color) - that will need no conversion; if in the future a 'Conversion 1' is needed for such a value then this method should convert
// that value to JSON format first that can be used by 'Conversion 1'
// 3. (serialized to String) values like borders (see ComponentFactoryHelper.createBorder()) that need to be converted to actual values; once that is done,
// it is the same as "2" above
Map<String, Object> convPropertiesMap = new HashMap<>();
for (String pv : propertiesMap.keySet())
{
Object val = propertiesMap.get(pv);
if (val == null) continue;
// the ones in this switch are converted from
switch (pv)
{
case com.servoy.j2db.persistence.IContentSpecConstants.PROPERTY_BORDERTYPE : //PropertyType.border.getType
val = ComponentFactoryHelper.createBorder((String)val);
break;
case com.servoy.j2db.persistence.IContentSpecConstants.PROPERTY_FONTTYPE : //PropertyType.font.getType
val = PersistHelper.createFont((String)val);
break;
default :
// use default
break;
}
convPropertiesMap.put(pv, val);
}
return convPropertiesMap;
}
Map<String, Object> getFlattenedPropertiesMap()
{
IPersist p = persist;
if (p instanceof IFlattenedPersistWrapper)
{
p = ((IFlattenedPersistWrapper< ? >)p).getWrappedPersist();
}
if (p instanceof ISupportExtendsID)
{
return ((ISupportExtendsID)p).getFlattenedPropertiesMap();
}
return ((AbstractBase)p).getPropertiesMap();
}
private void putAndConvertProperty(String propName, Object val, Map<String, Object> map, FlattenedSolution fs, PropertyDescription desc,
PropertyPath propertyPath)
{
formElement.convertDesignToFormElementValueAndPut(fs, desc, map, propName, val, propertyPath);
}
private void convertFromTabPanelToNGProperties(IFormElement persist, FlattenedSolution fs, Map<String, Object> map,
Map<String, PropertyDescription> specProperties, PropertyPath propertyPath)
{
ArrayList<Map<String, Object>> tabList = new ArrayList<>();
// add the tabs.
Iterator<IPersist> tabs = ((TabPanel)persist).getTabs();
putAndConvertProperty("tabIndex", 1, map, fs, specProperties.get("tabIndex"), propertyPath);
PropertyDescription tabSpecProperties = specProperties.get("tabs");
boolean active = true;
while (tabs.hasNext())
{
Map<String, Object> tabMap = new HashMap<>();
Tab tab = (Tab)tabs.next();
putAndConvertProperty("text", tab.getText(), tabMap, fs, tabSpecProperties.getProperty("text"), propertyPath);
putAndConvertProperty("relationName", tab.getRelationName(), tabMap, fs, tabSpecProperties.getProperty("relationName"), propertyPath);
putAndConvertProperty("active", Boolean.valueOf(active), tabMap, fs, tabSpecProperties.getProperty("active"), propertyPath);
tabMap.put("foreground", tab.getForeground());
putAndConvertProperty("name", tab.getName(), tabMap, fs, tabSpecProperties.getProperty("name"), propertyPath);
putAndConvertProperty("mnemonic", tab.getMnemonic(), tabMap, fs, tabSpecProperties.getProperty("mnemonic"), propertyPath);
int containsFormID = tab.getContainsFormID();
// TODO should this be resolved way later on?
// if solution model then this form can change..
Form form = fs.getForm(containsFormID);
if (form != null)
{
putAndConvertProperty("containsFormId", form.getName(), tabMap, fs, tabSpecProperties.getProperty("containsFormId"), propertyPath);
}
putAndConvertProperty("disabled", false, tabMap, fs, tabSpecProperties.getProperty("disabled"), propertyPath);
int orient = ((TabPanel)persist).getTabOrientation();
if (orient != TabPanel.SPLIT_HORIZONTAL && orient != TabPanel.SPLIT_VERTICAL)
{
int tabMediaID = tab.getImageMediaID();
if (tabMediaID > 0)
{
putAndConvertProperty("imageMediaID", Integer.valueOf(tabMediaID), tabMap, fs, tabSpecProperties.getProperty("imageMediaID"), propertyPath);
}
}
tabList.add(tabMap);
active = false;
}
map.put("tabs", tabList.toArray());
}
private void convertFromPortalToNGProperties(Portal portal, FlattenedSolution fs, Map<String, Object> map, Map<String, PropertyDescription> specProperties,
PropertyPath propertyPath)
{
try
{
map.remove("relationName");
JSONObject relatedFoundset = new JSONObject();
relatedFoundset.put("foundsetSelector", portal.getRelationName());
// get property type 'foundset'
PropertyDescription pd = specProperties.get("relatedFoundset");
if (pd == null)
{
Debug.error(new RuntimeException("Cannot find foundset special type to use for portal."));
return;
}
else
{
putAndConvertProperty("relatedFoundset", relatedFoundset, map, fs, pd, propertyPath);
}
map.remove("ngReadOnlyMode");
if (portal.getNgReadOnlyMode() != null)
{
PropertyDescription readOnlyModePD = specProperties.get("readOnlyMode");
if (pd == null)
{
Debug.error(new RuntimeException("Cannot find foundset special type to use for portal."));
return;
}
else
{
putAndConvertProperty("readOnlyMode", portal.getNgReadOnlyMode(), map, fs, readOnlyModePD, propertyPath);
}
}
// components: 'component[]',
// component: {
// definition: 'componentDef',
// (...)
// },
// get property type 'component definition'
pd = specProperties.get("childElements");
if (pd != null) pd = ((CustomJSONArrayType< ? , ? >)pd.getType()).getCustomJSONTypeDefinition();
List<IPersist> components = ComponentFactory.sortElementsOnPositionAndGroup(portal.getAllObjectsAsList());
if (pd == null)
{
Debug.error(new RuntimeException("Cannot find component definition special type to use for portal."));
return;
}
else
{
Object[] componentFormElementValues = new Object[components.size()]; // of ComponentTypeFormElementValue type (this array of objects corresponds to CustomJSONArrayType form element value
int i = 0;
ComponentPropertyType type = ((ComponentPropertyType)pd.getType());
propertyPath.add("childElements");
for (IPersist component : components)
{
if (component instanceof IFormElement)
{
FormElement nfe = FormElementHelper.INSTANCE.getFormElement((IFormElement)component, fs, propertyPath,
formElement.getDesignId() != null);
boolean somePropertyChanged = false;
propertyPath.add(i);
// remove the name of relation prefix from child dataproviders as it only stands in the way later on...
Collection<PropertyDescription> dataProviderProperties = nfe.getWebComponentSpec().getProperties(DataproviderPropertyType.INSTANCE);
String relationPrefix = portal.getRelationName() + '.';
Map<String, Object> elementProperties = new HashMap<>(nfe.getRawPropertyValues());
for (PropertyDescription dpProperty : dataProviderProperties)
{
String dpPropertyName = dpProperty.getName();
String dp = (String)nfe.getPropertyValue(dpPropertyName); // TODO adjust this when/if dataprovider properties change the form element value type in the future
if (dp != null && dp.startsWith(relationPrefix))
{
somePropertyChanged = true;
// portal always prefixes comp. dataproviders with related fs name
putAndConvertProperty(dpPropertyName, dp.substring(relationPrefix.length()), elementProperties, fs,
nfe.getWebComponentSpec().getProperty(dpPropertyName), propertyPath);
}
}
// legacy portals need to set the same tabSeq. property for all children if they are to function properly
Collection<PropertyDescription> tabSequenceProperties = nfe.getWebComponentSpec().getProperties(NGTabSeqPropertyType.NG_INSTANCE);
for (PropertyDescription tabSeqProperty : tabSequenceProperties)
{
String tabSeqPropertyName = tabSeqProperty.getName();
somePropertyChanged = true;
putAndConvertProperty(tabSeqPropertyName, Integer.valueOf(1), elementProperties, fs,
nfe.getWebComponentSpec().getProperty(tabSeqPropertyName), propertyPath); // just put an 1 on all (default legacy portal doesn't allow non-default tab seq in it)
}
if (somePropertyChanged) nfe.updatePropertyValuesDontUse(elementProperties);
componentFormElementValues[i++] = type.getFormElementValue(null, pd, propertyPath, nfe, fs);
propertyPath.backOneLevel();
}
}
propertyPath.backOneLevel();
map.put("childElements", componentFormElementValues);
}
}
catch (IllegalArgumentException e)
{
Debug.error(e);
return;
}
catch (JSONException e)
{
Debug.error(e);
return;
}
}
public boolean isLegacy()
{
return !(persist instanceof Bean);
}
public boolean isForm()
{
return persist instanceof Form;
}
IPersist getPersist()
{
return persist;
}
}