/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2013 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.awt.Dimension; import java.awt.Point; 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 java.util.Map.Entry; import java.util.Set; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; import org.json.JSONWriter; import org.sablo.IWebComponentInitializer; import org.sablo.specification.PropertyDescription; import org.sablo.specification.WebComponentSpecProvider; import org.sablo.specification.WebObjectSpecification; import org.sablo.specification.property.ICustomType; import org.sablo.specification.property.IPropertyType; import org.sablo.specification.property.types.AggregatedPropertyType; import org.sablo.specification.property.types.DimensionPropertyType; import org.sablo.specification.property.types.IntPropertyType; import org.sablo.specification.property.types.PointPropertyType; import org.sablo.specification.property.types.TypesRegistry; import org.sablo.websocket.TypedData; import org.sablo.websocket.utils.JSONUtils; import org.sablo.websocket.utils.PropertyUtils; import com.servoy.j2db.FlattenedSolution; import com.servoy.j2db.persistence.Bean; import com.servoy.j2db.persistence.FlattenedForm; import com.servoy.j2db.persistence.Form; import com.servoy.j2db.persistence.GraphicalComponent; import com.servoy.j2db.persistence.IFormElement; import com.servoy.j2db.persistence.IPersist; import com.servoy.j2db.persistence.ISupportBounds; import com.servoy.j2db.persistence.ISupportSize; import com.servoy.j2db.persistence.Part; import com.servoy.j2db.persistence.StaticContentSpecLoader; import com.servoy.j2db.server.ngclient.property.ComponentPropertyType; 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.NGConversions; import com.servoy.j2db.server.ngclient.property.types.NGConversions.FormElementToJSON; import com.servoy.j2db.server.ngclient.property.types.NGConversions.IDesignDefaultToFormElement; import com.servoy.j2db.server.ngclient.property.types.NGConversions.IDesignToFormElement; import com.servoy.j2db.server.ngclient.property.types.PropertyPath; import com.servoy.j2db.server.ngclient.template.FormTemplateGenerator; import com.servoy.j2db.server.ngclient.utils.MiniMap; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.ServoyJSONObject; import com.servoy.j2db.util.Utils; /** * @author jcompagner */ @SuppressWarnings("nls") public final class FormElement implements IWebComponentInitializer, INGFormElement { public static final String DROPPABLE = "droppable"; public static final String ERROR_BEAN = "servoycore-errorbean"; public static final String SVY_NAME_PREFIX = "svy_"; private final Form form; private Map<String, Object> propertyValues; private Map<Class< ? >, Map<PropertyDescription, Object>> preprocessedPropertyInfo; // standard information that can be computed in template about some properties and will be needed at runtime; (infoType, (propertyName, infoObject)) private final String componentType; private final PersistBasedFormElementImpl persistImpl; private final String uniqueIdWithinForm; private final boolean inDesigner; private FlattenedSolution fs; public FormElement(IFormElement persist, FlattenedSolution fs, PropertyPath propertyPath, boolean inDesigner) { this.fs = fs; this.inDesigner = inDesigner; this.persistImpl = new PersistBasedFormElementImpl(persist, this); Form f = persistImpl.getForm(); if (f instanceof FlattenedForm) this.form = f; else this.form = fs.getFlattenedForm(f); boolean willTurnIntoErrorBean = inDesigner && persist instanceof Bean && !DefaultNavigator.NAME_PROP_VALUE.equals(persist.getName()) && WebComponentSpecProvider.getInstance().getWebComponentSpecification(((Bean)persist).getBeanClassName()) != null && usesPersistTypedProperties(WebComponentSpecProvider.getInstance().getWebComponentSpecification(((Bean)persist).getBeanClassName())); this.componentType = willTurnIntoErrorBean ? FormElement.ERROR_BEAN : FormTemplateGenerator.getComponentTypeName(persist); this.uniqueIdWithinForm = String.valueOf(persist.getID()); propertyValues = new HashMap<String, Object>(); propertyValues.put(StaticContentSpecLoader.PROPERTY_NAME.getPropertyName(), persist.getName()); Map<String, PropertyDescription> specProperties = getWebComponentSpec().getProperties(); boolean addNameToPath = propertyPath.shouldAddElementNameAndClearFlag(); if (addNameToPath) propertyPath.add(getName()); Map<String, Object> map = persistImpl.getFormElementPropertyValues(fs, specProperties, propertyPath); adjustLocationRelativeToPart(fs, map); initTemplateProperties(specProperties, map, fs, propertyPath); if (willTurnIntoErrorBean) { map.put("error", "Please remove and insert this component again. Components which define custom types in their spec file will not work properly due to some changes in 8.0 beta2/beta3 versions (for solutions created with previous beta/alpha versions). See log file for details."); Debug.warn("Please remove and insert again the component with name '" + persist.getName() + (this.form != null ? "' on the form '" + this.form.getName() : "") + "'. Type: " + FormTemplateGenerator.getComponentTypeName(persist) + ". Components which define custom types in their spec file will not work properly due to some changes in 8.0 beta2/beta3 versions (for solutions created with previous beta/alpha versions). Properties: " + map.get("beanXML")); // so Bean persist used for custom components is no longer working properly - now it uses WebComponent } propertyValues = Collections.unmodifiableMap(new MiniMap<String, Object>(map, map.size())); if (addNameToPath) propertyPath.backOneLevel(); } public FormElement(String componentTypeString, JSONObject jsonObject, Form form, String uniqueIdWithinForm, FlattenedSolution fs, PropertyPath propertyPath, boolean inDesigner) { this.inDesigner = inDesigner; this.persistImpl = null; if (form instanceof FlattenedForm) this.form = form; else this.form = fs.getFlattenedForm(form); if (WebComponentSpecProvider.getInstance().getWebComponentSpecification(componentTypeString) == null) { this.componentType = FormElement.ERROR_BEAN; } else { this.componentType = componentTypeString; } this.uniqueIdWithinForm = uniqueIdWithinForm; propertyValues = new HashMap<String, Object>(); propertyValues.put(StaticContentSpecLoader.PROPERTY_NAME.getPropertyName(), jsonObject.optString(StaticContentSpecLoader.PROPERTY_NAME.getPropertyName())); Map<String, PropertyDescription> specProperties = getWebComponentSpec().getProperties(); boolean addNameToPath = propertyPath.shouldAddElementNameAndClearFlag(); if (addNameToPath) propertyPath.add(getName()); Map<String, Object> map = new HashMap<>(); try { convertFromJSONToFormElementValues(fs, specProperties, map, getWebComponentSpec().getHandlers(), jsonObject, propertyPath); } catch (JSONException ex) { Debug.error("Error while parsing component design JSON", ex); } adjustLocationRelativeToPart(fs, map); initTemplateProperties(specProperties, map, fs, propertyPath); if (this.componentType == FormElement.ERROR_BEAN) { map.put("error", "Component (specification) with type: " + componentTypeString + " not found. Please make sure that this custom web component type is available."); } propertyValues = Collections.unmodifiableMap(new MiniMap<String, Object>(map, map.size())); if (addNameToPath) propertyPath.backOneLevel(); } /** * This is part of 'Conversion 1' (see {@link NGConversions}) */ protected void convertFromJSONToFormElementValues(FlattenedSolution fs, Map<String, PropertyDescription> specProperties, Map<String, Object> jsonMap, Map<String, PropertyDescription> eventProperties, JSONObject propertyDesignJSONValues, PropertyPath propertyPath) throws JSONException { Iterator keys = propertyDesignJSONValues.keys(); while (keys.hasNext()) { String key = (String)keys.next(); Object value = propertyDesignJSONValues.get(key); convertDesignToFormElementValueAndPut(fs, getPropertyOrEvent(key, specProperties, eventProperties), jsonMap, key, value, propertyPath); } } /** * This is part of 'Conversion 1' (see {@link NGConversions}) * * It should ONLY have primitives in properties for property descriptor types that provide such a conversion, as those will be fed to * a converter that expects JSON. For types that do not provide a converter for 'Conversion 1' - it can be any type of object. * * If we figure out that we ever need conversions for non-primitive Java design types that a persist can create, we need another conversion on the type * or hardcoded in persist converter 'Conversion 0' to convert it to a JSON first (from non-primitive Persist property value). Currently, non-primitive * persist property values are always assumed to not need "Conversion 1" */ protected void convertFromPersistPrimitivesToFormElementValues(FlattenedSolution fs, Map<String, PropertyDescription> specProperties, Map<String, PropertyDescription> eventProperties, Map<String, Object> properties, PropertyPath propertyPath) { Iterator<String> keys = properties.keySet().iterator(); while (keys.hasNext()) { String key = keys.next(); Object value = properties.get(key); if (!(value instanceof Number || value instanceof String || value instanceof Byte || value instanceof Character)) { // non - primitive type; skip extra conversion as Persist already converted it or convertSpecialPersistProperties(...) already converted it continue; } convertDesignToFormElementValueAndPut(fs, getPropertyOrEvent(key, specProperties, eventProperties), properties, key, value, propertyPath); } } protected PropertyDescription getPropertyOrEvent(String key, Map<String, PropertyDescription> specProperties, Map<String, PropertyDescription> eventProperties) { PropertyDescription pd = specProperties.get(key); if (pd == null && eventProperties != null) { // or an event pd = eventProperties.get(key); } return pd; } /** * Applies 'Conversion 1' (see {@link NGConversions}) to one property value - from design to FormElement value and then puts the value in the given formElementValues map. */ protected void convertDesignToFormElementValueAndPut(FlattenedSolution fs, PropertyDescription pd, Map<String, Object> formElementValues, String key, Object value, PropertyPath propertyPath) { // is it a property if (pd != null) { propertyPath.add(key); formElementValues.put(key, NGConversions.INSTANCE.convertDesignToFormElementValue(value, pd, fs, this, propertyPath)); propertyPath.backOneLevel(); } else if (StaticContentSpecLoader.PROPERTY_NAME.getPropertyName().equals(key)) { formElementValues.put(key, value); } } public IPersist getPersistIfAvailable() { if (persistImpl != null) { return persistImpl.getPersist(); } return null; } private void initTemplateProperties(Map<String, PropertyDescription> specProperties, Map<String, Object> map, FlattenedSolution fs, PropertyPath propertyPath) { if (specProperties != null && map != null) { for (PropertyDescription pd : specProperties.values()) { if (!map.containsKey(pd.getName())) { if (pd.hasDefault()) { propertyPath.add(pd.getName()); map.put(pd.getName(), NGConversions.INSTANCE.convertDesignToFormElementValue(pd.getDefaultValue(), pd, fs, this, propertyPath)); propertyPath.backOneLevel(); } else if (pd.getType() instanceof IDesignDefaultToFormElement< ? , ? , ? >) { propertyPath.add(pd.getName()); map.put(pd.getName(), ((IDesignDefaultToFormElement< ? , ? , ? >)pd.getType()).toDefaultFormElementValue(pd, fs, this, propertyPath)); propertyPath.backOneLevel(); } else if (pd.getType().defaultValue(pd) != null) { // remember that we can use type specified default value when this gets transformed to JSON map.put(pd.getName(), NGConversions.IDesignToFormElement.TYPE_DEFAULT_VALUE_MARKER); } } Object formElementValue = map.get(pd.getName()); // check find mode aware and data linked properties and remember/cache what they will need at runtime if (formElementValue != NGConversions.IDesignToFormElement.TYPE_DEFAULT_VALUE_MARKER) { // as array element property descriptions can describe multiple property values in the same bean - we won't cache those if (pd.getType() instanceof IDataLinkedType) { getOrCreatePreprocessedPropertyInfoMap(IDataLinkedType.class).put(pd, ((IDataLinkedType)pd.getType()).getDataLinks(ServoyJSONObject.jsonNullToNull(formElementValue), pd, fs, this)); } if (pd.getType() instanceof IFindModeAwareType) { getOrCreatePreprocessedPropertyInfoMap(IFindModeAwareType.class).put(pd, ((IFindModeAwareType)pd.getType()).isFindModeAware(ServoyJSONObject.jsonNullToNull(formElementValue), pd, fs, this)); } } } } } /** * Get some standard info that was pre-processed at template stage but is needed at runtime. * * @param propertyInfoType for example {@link IFindModeAwareType}.class or {@link IDataLinkedType}.class. * @return the pre-processed information object (it's type and meaning depends on propertyInfoType). */ public Object getPreprocessedPropertyInfo(Class< ? > propertyInfoType, PropertyDescription key) { if (preprocessedPropertyInfo == null) return null; Map<PropertyDescription, Object> m = preprocessedPropertyInfo.get(propertyInfoType); if (m == null) return null; return m.get(key); } public Map<PropertyDescription, Object> getOrCreatePreprocessedPropertyInfoMap(Class< ? > propertyInfoType) { if (preprocessedPropertyInfo == null) preprocessedPropertyInfo = new HashMap<>(); Map<PropertyDescription, Object> m = preprocessedPropertyInfo.get(propertyInfoType); if (m == null) { m = new HashMap<>(); preprocessedPropertyInfo.put(propertyInfoType, m); } return m; } private void adjustLocationRelativeToPart(FlattenedSolution fs, Map<String, Object> map) { if (map != null && form != null) { Form flatForm = fs.getFlattenedForm(form); Point location = getDesignLocation(); if (location != null) { // if it is design client, it has no parts boolean isInDesginer = getDesignId() != null; if (isInDesginer) { map.put(StaticContentSpecLoader.PROPERTY_LOCATION.getPropertyName(), location); map.put("offsetY", 0); } else { Point newLocation = new Point(location); Part part = flatForm.getPartAt(location.y); if (part != null) { int top = flatForm.getPartStartYPos(part.getID()); newLocation.y = newLocation.y - top; map.put(StaticContentSpecLoader.PROPERTY_LOCATION.getPropertyName(), newLocation); map.put("offsetY", top); map.put("partHeight", part.getHeight()); } } } } adjustForAbsoluteLayout(); } /** * */ protected void adjustForAbsoluteLayout() { if (form != null && !form.isResponsiveLayout()) { WebObjectSpecification spec = getWebComponentSpec(); if (spec.getProperty("location") == null) spec.putProperty("location", new PropertyDescription("location", TypesRegistry.getType(PointPropertyType.TYPE_NAME))); if (spec.getProperty("size") == null) spec.putProperty("size", new PropertyDescription("size", TypesRegistry.getType(DimensionPropertyType.TYPE_NAME))); if (spec.getProperty("anchors") == null) spec.putProperty("anchors", new PropertyDescription("anchors", TypesRegistry.getType(IntPropertyType.TYPE_NAME))); } } public Map<String, Object> getRawPropertyValues() { return propertyValues; } public WebObjectSpecification getWebComponentSpec() { return getWebComponentSpec(true); } public WebObjectSpecification getWebComponentSpec(boolean throwException) { WebObjectSpecification spec = null; try { spec = WebComponentSpecProvider.getInstance().getWebComponentSpecification(componentType); } catch (RuntimeException re) { Debug.error(re); if (throwException) throw re; } if (spec == null) { String errorMessage = "Component spec for " + componentType + " not found; please check your component spec file(s)."; Debug.error(errorMessage); return WebComponentSpecProvider.getInstance().getWebComponentSpecification(FormElement.ERROR_BEAN); //if (throwException) throw new IllegalStateException(errorMessage); } return spec; } /** * Never returns null. Will try to return a name that is unique in containing form and consistent between different runs - if a name * was not explicitly set on the component. */ public String getName() { return getName((String)getPropertyValue(StaticContentSpecLoader.PROPERTY_NAME.getPropertyName())); } public String getRawName() { return (String)getPropertyValue(StaticContentSpecLoader.PROPERTY_NAME.getPropertyName()); } public String getName(String rawValue) { String name = rawValue; if (name == null) { name = SVY_NAME_PREFIX + uniqueIdWithinForm; } if (Character.isDigit(name.charAt(0))) { name = "_" + name; } return name.replace('-', '_'); } public String getDesignId() { if (inDesigner && getPersistIfAvailable() != null) { return getPersistIfAvailable().getUUID().toString(); } return null; } // // /** // * Gets the JSON value to be set in form's ftl template. (initial value)<br><br> // * // * This uses 'Conversion 2' (see {@link NGConversions}) // */ // public Object getPropertyValueConvertedForTemplate(String propertyName) // { // NGConversions.INSTANCE.applyConversion2(whatHere); // if (propertyDescription != null) // { // // if there is no spec defined default, nor any value for this property, use the sablo type default value, // // but that needs to go through Conversion 5.1 (see {@link NGConversions}) first // NGConversions.INSTANCE.applyConversion5_1(); // return propertyDescription.getType().defaultValue(); // } // // return propertyValues.get(propertyName); // } /** * Returns the actual value that this FormElement keeps for the requested property.<br> * It is possible that it will return a {@link IFromDesignToFormElement#TYPE_DEFAULT_VALUE_MARKER} in case * the type has a default value bu there was no design value or spec. defined default value for this property. */ public Object getRawPropertyValue(String propertyName) { return propertyValues.get(propertyName); } /** * Same as {@link #getRawPropertyValue(String)} but changes {@link IFromDesignToFormElement#TYPE_DEFAULT_VALUE_MARKER} to null. * It is probably that this method can be removed once more types are refactored to use converters instead of hard-coded lines here and there. */ public Object getPropertyValue(String propertyName) { Object value = propertyValues.get(propertyName); return value == IDesignToFormElement.TYPE_DEFAULT_VALUE_MARKER ? null : value; } @Override public Collection<PropertyDescription> getProperties(IPropertyType< ? > type) { if (getWebComponentSpec() != null) { return getWebComponentSpec().getProperties(type); } return null; } @Override public PropertyDescription getProperty(String name) { if (getWebComponentSpec() != null) { return getWebComponentSpec().getProperty(name); } return null; } /** * Returns sablo webcomponent value representation of this property value. * * This uses 'Conversion 3' (see {@link NGConversions}) */ public Object getPropertyValueConvertedForWebComponent(String propertyName, WebFormComponent component, DataAdapterList dal) { // // TODO remove this delegation when going with tree structure , this is needed for DataAdapterList which 'thinks' everything is flat // String[] split = name.split("\\."); // if (split.length > 1) // { // return ((Map)getProperty(split[0])).get(split[1]); // }// end toRemove PropertyDescription propertyDescription = getWebComponentSpec().getProperties().get(propertyName); if (propertyValues.containsKey(propertyName)) { if (propertyDescription != null) return NGConversions.INSTANCE.convertFormElementToSabloComponentValue(getRawPropertyValue(propertyName), propertyDescription, this, component, dal); else return getPropertyValue(propertyName); // just in case this method gets called for events for example (which are currently stored in the same map) } if (propertyDescription != null) { // we want a defaut value to be set anyway because it was sent into template return propertyDescription.getType().defaultValue(propertyDescription); } return null; } public boolean isForm() { return persistImpl != null && persistImpl.isForm(); } /** * Refactored hack. */ boolean isGraphicalComponentWithNoAction() { if ("servoydefault-button".equals(componentType) || "servoydefault-label".equals(componentType)) { Object onAction = getPropertyValue(StaticContentSpecLoader.PROPERTY_ONACTIONMETHODID.getPropertyName()); if (onAction == null || (onAction instanceof Integer && (((Integer)onAction).intValue() <= 0))) return true; } return false; } public Form getForm() { return form; } public boolean isLegacy() { return persistImpl != null && persistImpl.isLegacy(); } public String getTagname() { return FormTemplateGenerator.getTagName(componentType); } public String getTypeName() { return componentType; } public IFormElement getLabel() { IFormElement label = null; String name = (String)getPropertyValue(StaticContentSpecLoader.PROPERTY_NAME.getPropertyName()); if (name != null && form != null) { Iterator<IPersist> formElementsIte = form.getAllObjects(); IPersist p; while (formElementsIte.hasNext()) { p = formElementsIte.next(); if (p instanceof GraphicalComponent && name.equals(((GraphicalComponent)p).getLabelFor())) { label = (GraphicalComponent)p; break; } } } return label; } public Collection<String> getHandlers() { List<String> handlers = new ArrayList<>(); WebObjectSpecification componentSpec = getWebComponentSpec(); Set<String> events = componentSpec.getHandlers().keySet(); for (String eventName : events) { Object eventValue = getPropertyValue(eventName); if (eventValue != null && !(eventValue instanceof Integer && (((Integer)eventValue).intValue() == -1 || ((Integer)eventValue).intValue() == 0))) { handlers.add(eventName); } else if (Utils.equalObjects(eventName, StaticContentSpecLoader.PROPERTY_ONFOCUSGAINEDMETHODID.getPropertyName()) && (getForm().getOnElementFocusGainedMethodID() > 0)) { handlers.add(eventName); } else if (Utils.equalObjects(eventName, StaticContentSpecLoader.PROPERTY_ONFOCUSLOSTMETHODID.getPropertyName()) && (getForm().getOnElementFocusLostMethodID() > 0)) { handlers.add(eventName); } } return handlers; } public String getPropertiesString() throws JSONException { return propertiesAsTemplateJSON(null, new FormElementContext(this)).toString(); } public JSONWriter propertiesAsTemplateJSON(JSONWriter writer, FormElementContext context) throws JSONException { TypedData<Map<String, Object>> propertiesTypedData = propertiesForTemplateJSON(); JSONWriter propertyWriter = (writer != null ? writer : new JSONStringer()); try { propertyWriter.object(); JSONUtils.writeDataWithConversions(FormElementToJSON.INSTANCE, propertyWriter, propertiesTypedData.content, propertiesTypedData.contentType, context); return propertyWriter.endObject(); } catch (JSONException | IllegalArgumentException e) { Debug.error("Problem detected when handling a component's (" + getTagname() + ") properties / events.", e); throw e; } } public TypedData<Map<String, Object>> propertiesForTemplateJSON() { Map<String, Object> properties = new HashMap<>(); adjustForAbsoluteLayout(); WebObjectSpecification componentSpec = getWebComponentSpec(); Map<String, PropertyDescription> propDescription = componentSpec.getProperties(); for (PropertyDescription pd : propDescription.values()) { Object val = getRawPropertyValue(pd.getName()); if (val != null) { properties.put(pd.getName(), val); } } if (persistImpl == null || !persistImpl.isForm()) { Dimension dim = getDesignSize(); if (dim != null) properties.put(StaticContentSpecLoader.PROPERTY_SIZE.getPropertyName(), dim); Integer anchor = (Integer)getPropertyValue(StaticContentSpecLoader.PROPERTY_ANCHORS.getPropertyName()); if (anchor != null) { properties.put(StaticContentSpecLoader.PROPERTY_ANCHORS.getPropertyName(), anchor); } } Object offsetY = getPropertyValue("offsetY"); if (offsetY != null) properties.put("offsetY", offsetY); Object partHeight = getPropertyValue("partHeight"); if (partHeight != null) properties.put("partHeight", partHeight); Object formview = getPropertyValue("formview"); if (formview != null) properties.put("formview", formview); // get types for conversion PropertyDescription propertyTypes = AggregatedPropertyType.newAggregatedProperty(); for (Entry<String, Object> p : properties.entrySet()) { PropertyDescription t = getWebComponentSpec().getProperty(p.getKey()); if (t != null) propertyTypes.putProperty(p.getKey(), t); } return new TypedData<>(properties, propertyTypes.hasChildProperties() ? propertyTypes : null); } Dimension getDesignSize() { if (persistImpl != null && persistImpl.getPersist() instanceof ISupportSize) { return ((ISupportSize)persistImpl.getPersist()).getSize(); } return null; } Point getDesignLocation() { if (persistImpl != null && persistImpl.getPersist() instanceof ISupportBounds) { return ((ISupportBounds)persistImpl.getPersist()).getLocation(); } return null; } @Override public String toString() { return String.format( "<%1$s name=\"%2$s\" svy-model=\"model.%2$s\" svy-api=\"api.%2$s\" svy-handlers=\"handlers.%2$s\" svy-servoyApi=\"handlers.%2$s.svy_servoyApi\"></%1$s>", getTagname(), getName()); } /** * This should not be called normally; it's only called because legacy portal needs to remove some prefix from the dataprovider properties of children. */ public void updatePropertyValuesDontUse(Map<String, Object> elementProperties) { this.propertyValues = elementProperties; } /** * @return a list of accepted type names when dropping from palette. Possible type names include the types defined in the specfile and the "component" type. */ public List<String> getSvyTypesNames() { WebObjectSpecification spec = getWebComponentSpec(false); ArrayList<String> result = new ArrayList<String>(); Map<String, PropertyDescription> properties = spec.getProperties(); for (PropertyDescription propertyDescription : properties.values()) { Object configObject = propertyDescription.getConfig(); String simpleTypeName = PropertyUtils.getSimpleNameOfCustomJSONTypeProperty(propertyDescription.getType()); if (simpleTypeName.equals("component") || (configObject instanceof JSONObject && Boolean.TRUE.equals(((JSONObject)configObject).opt(DROPPABLE)) && PropertyUtils.isCustomJSONProperty(propertyDescription.getType()))) { result.add(simpleTypeName); } } return result; } public List<String> getForbiddenComponentNames() { ArrayList<String> result = new ArrayList<String>(); // TODO:maybe we can add this kind of info to the spec ? // or maybe have a list of 'allowed' components ? if ("servoycore-portal".equals(getTypeName())) { result.add("servoycore-portal"); result.add("servoydefault-tabpanel"); result.add("servoydefault-splitpane"); } return result; } public FlattenedSolution getFlattendSolution() { return fs; } /** * Returns true if the give property description makes use of types that are turned into persists when used in persist properties. * For example component property types, component array, custom object and custom object arrays return Persist instances when accessed through WebComponent.getProperty(...) * * @param pd the property description to look in. * @return see description. */ public static boolean usesPersistTypedProperties(PropertyDescription pd) { if (pd == null) return false; for (PropertyDescription x : pd.getCustomJSONProperties().values()) { if (PropertyUtils.isCustomJSONObjectProperty(x.getType()) || x.getType() instanceof ComponentPropertyType || (PropertyUtils.isCustomJSONArrayPropertyType(x.getType()) && usesPersistTypedProperties(((ICustomType< ? >)x.getType()).getCustomJSONTypeDefinition()))) return true; } return false; } }