/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.coregui.client.components.configuration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.ListGridEditEvent;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.ButtonItem;
import com.smartgwt.client.widgets.form.fields.CheckboxItem;
import com.smartgwt.client.widgets.form.fields.ComboBoxItem;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.FormItemIcon;
import com.smartgwt.client.widgets.form.fields.PasswordItem;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.SpacerItem;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.form.fields.TextAreaItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.form.fields.events.ClickHandler;
import com.smartgwt.client.widgets.form.fields.events.FormItemClickHandler;
import com.smartgwt.client.widgets.form.fields.events.FormItemIconClickEvent;
import com.smartgwt.client.widgets.form.validator.Validator;
import com.smartgwt.client.widgets.grid.CellFormatter;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.RowEditorExitEvent;
import com.smartgwt.client.widgets.grid.events.RowEditorExitHandler;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionEnumeration;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionList;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.ImageManager;
import org.rhq.coregui.client.PopupWindow;
import org.rhq.coregui.client.components.form.SortedSelectItem;
import org.rhq.coregui.client.util.StringUtility;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton.ButtonColor;
import org.rhq.coregui.client.util.enhanced.EnhancedToolStrip;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
import org.rhq.coregui.client.util.message.Message;
import org.rhq.coregui.client.util.message.Message.Severity;
/**
* A SmartGWT widget for editing a group of RHQ {@link Configuration}s that conform to the same
* {@link ConfigurationDefinition}.
*
* @author Ian Springer
*/
public class GroupConfigurationEditor extends ConfigurationEditor {
private static final String RHQ_STATIC_ITEM_NAME_ATTRIBUTE = "rhq:staticItemName";
private static final String RHQ_UNSET_ITEM_NAME_ATTRIBUTE = "rhq:unsetItemName";
private static final String MEMBER_VALUES_EDITOR_FIELD_PREFIX = "memberValuesEditor-";
private static final String FIELD_ID = MEMBER_VALUES_EDITOR_FIELD_PREFIX + "idField";
private static final String FIELD_MEMBER = MEMBER_VALUES_EDITOR_FIELD_PREFIX + "memberField";
private static final String FIELD_UNSET = MEMBER_VALUES_EDITOR_FIELD_PREFIX + "unsetField";
private static final String FIELD_VALUE = MEMBER_VALUES_EDITOR_FIELD_PREFIX + "valueField";
private static final String ATTR_CONFIG_OBJ = "configObject";
private List<GroupMemberConfiguration> memberConfigurations;
public GroupConfigurationEditor(ConfigurationDefinition configurationDefinition,
List<GroupMemberConfiguration> memberConfigurations) {
super(configurationDefinition, buildAggregateConfiguration(memberConfigurations, configurationDefinition));
this.memberConfigurations = memberConfigurations;
}
private static Configuration buildAggregateConfiguration(List<GroupMemberConfiguration> memberConfigurations,
ConfigurationDefinition configurationDefinition) {
List<Configuration> configurations = new ArrayList<Configuration>(memberConfigurations.size());
for (GroupMemberConfiguration memberConfiguration : memberConfigurations) {
Configuration configuration = memberConfiguration.getConfiguration();
configurations.add(configuration);
}
return AggregateConfigurationBuilder.buildAggregateConfiguration(configurations, configurationDefinition);
}
@Override
protected List<FormItem> buildFieldsForPropertySimple(PropertyDefinition propertyDefinition,
PropertyDefinitionSimple propertyDefinitionSimple, PropertySimple propertySimple) {
List<FormItem> fields = super.buildFieldsForPropertySimple(propertyDefinition, propertyDefinitionSimple,
propertySimple);
if (isAggregateProperty(propertySimple)) {
// Create the "MEMBER VALUES DIFFER" label that is displayed when member values are heterogeneous.
final StaticTextItem staticItem = new StaticTextItem("static-" + propertySimple.getName());
staticItem.setValue(MSG.view_groupConfigEdit_valsDiff());
staticItem.setTextBoxStyle("InlineNote");
staticItem.setShowTitle(false);
staticItem.setTooltip(MSG.view_groupConfigEdit_tooltip_1());
Boolean isHomogeneous = isHomogeneous(propertySimple);
staticItem.setVisible(!isHomogeneous);
FormItem valueItem = fields.get(2);
FormItemIcon icon = buildEditMemberValuesIcon(propertyDefinitionSimple, propertySimple, valueItem);
staticItem.setIcons(icon);
valueItem.setAttribute(RHQ_STATIC_ITEM_NAME_ATTRIBUTE, staticItem.getName());
fields.add(3, staticItem);
}
return fields;
}
@Override
protected List<FormItem> buildFieldsForPropertyList(PropertyDefinition propertyDefinition, boolean oddRow,
PropertyDefinitionList propertyDefinitionList, PropertyDefinition memberDefinition, PropertyList propertyList) {
List<FormItem> fields = new ArrayList<FormItem>();
StaticTextItem nameItem = buildNameItem(propertyDefinition);
fields.add(nameItem);
StaticTextItem staticTextItem = new StaticTextItem();
staticTextItem.setShowTitle(false);
staticTextItem.setValue(MSG.view_groupConfigEdit_noListProps());
staticTextItem.setColSpan(3);
staticTextItem.setEndRow(true);
fields.add(staticTextItem);
return fields;
}
@Override
protected FormItem buildSimpleField(final PropertyDefinitionSimple propertyDefinitionSimple,
final PropertySimple propertySimple) {
final FormItem item = super.buildSimpleField(propertyDefinitionSimple, propertySimple);
boolean isAggregate = isAggregateProperty(propertySimple);
if (isAggregate) {
// Add the icon that user can click to edit the member values.
FormItemIcon icon = buildEditMemberValuesIcon(propertyDefinitionSimple, propertySimple, item);
item.setIcons(icon);
item.addChangedHandler(new ChangedHandler() {
public void onChanged(ChangedEvent changedEvent) {
Object value = changedEvent.getValue();
updateMemberProperties(propertySimple, value);
}
});
Boolean isHomogeneous = isHomogeneous(propertySimple);
item.setVisible(isHomogeneous);
}
return item;
}
private FormItemIcon buildEditMemberValuesIcon(final PropertyDefinitionSimple propertyDefinitionSimple,
final PropertySimple propertySimple, final FormItem dynamicItem) {
FormItemIcon icon = new FormItemIcon();
icon.setSrc(ImageManager.getEditIcon());
icon.setName("Edit Member Values");
icon.setNeverDisable(true);
icon.addFormItemClickHandler(new FormItemClickHandler() {
public void onFormItemClick(FormItemIconClickEvent event) {
// TODO: Pass the actual index, rather than null, if the prop is inside a list.
displayMemberValuesEditor(propertyDefinitionSimple, propertySimple, null, dynamicItem);
}
});
// TODO: Figure out a way to add a tooltip to the icon.
return icon;
}
private void updateMemberProperties(PropertySimple propertySimple, Object value) {
for (GroupMemberConfiguration memberConfiguration : this.memberConfigurations) {
Configuration configuration = memberConfiguration.getConfiguration();
PropertySimple memberPropertySimple = (PropertySimple) getProperty(configuration, propertySimple, null);
memberPropertySimple.setValue(value);
}
}
@Override
protected FormItem buildUnsetItem(final PropertyDefinitionSimple propertyDefinitionSimple,
final PropertySimple propertySimple, final FormItem valueItem) {
final FormItem unsetItem = super.buildUnsetItem(propertyDefinitionSimple, propertySimple, valueItem);
if (unsetItem instanceof CheckboxItem && !isHomogeneous(propertySimple) && isAggregateProperty(propertySimple)) {
// non-homogeneous aggregate property (i.e. members have mixed values)
unsetItem.setValue(false);
unsetItem.setDisabled(true);
}
valueItem.setAttribute(RHQ_UNSET_ITEM_NAME_ATTRIBUTE, unsetItem.getName());
return unsetItem;
}
private void displayMemberValuesEditor(final PropertyDefinitionSimple propertyDefinitionSimple,
final PropertySimple aggregatePropertySimple, Integer index, final FormItem aggregateValueItem) {
final PopupWindow popup = new PopupWindow(null);
popup.setTitle(MSG.view_groupConfigEdit_valsDiffForProp(propertyDefinitionSimple.getName()));
popup.setWidth(800);
popup.setHeight(600);
EnhancedVLayout layout = buildMemberEditor(propertyDefinitionSimple, aggregatePropertySimple, index,
aggregateValueItem, popup);
popup.addItem(layout);
popup.show();
}
private EnhancedVLayout buildMemberEditor(final PropertyDefinitionSimple propertyDefinitionSimple,
final PropertySimple aggregatePropertySimple, final Integer index, final FormItem aggregateValueItem,
final PopupWindow popup) {
final boolean propertyIsReadOnly = isReadOnly(propertyDefinitionSimple, aggregatePropertySimple);
EnhancedVLayout layout = new EnhancedVLayout();
layout.setHeight100();
layout.setWidth100();
layout.setMargin(11);
// create the header strip - will contain "set all values to" form
EnhancedToolStrip headerStrip = null;
if (!propertyIsReadOnly) {
headerStrip = new EnhancedToolStrip();
headerStrip.setWidth100();
headerStrip.setPadding(5);
headerStrip.setMembersMargin(10);
layout.addMember(headerStrip);
}
// create the list grid that contains all the member resource names and values
final ListGridRecord[] data = getMemberValuesGridData(this.memberConfigurations, propertyDefinitionSimple,
aggregatePropertySimple, index);
final ListGrid memberValuesGrid = new ListGrid();
memberValuesGrid.setHeight100();
memberValuesGrid.setWidth100();
memberValuesGrid.setData(data);
memberValuesGrid.setEditEvent(ListGridEditEvent.CLICK);
memberValuesGrid.setEditByCell(false); // selecting any cell allows the user to edit the entire row
ListGridField idField = new ListGridField(FIELD_ID, MSG.common_title_id());
idField.setHidden(true);
idField.setCanEdit(false);
ListGridField memberField = new ListGridField(FIELD_MEMBER, MSG.view_groupConfigEdit_member());
memberField.setCanEdit(false);
final ListGridField valueField = new ListGridField(FIELD_VALUE, MSG.common_title_value());
valueField.setRequired(propertyDefinitionSimple.isRequired());
valueField.setCanEdit(true);
final FormItem editorItem = buildEditorFormItem(valueField, propertyDefinitionSimple);
valueField.setEditorType(editorItem);
valueField.setCellFormatter(new CellFormatter() {
public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
if (value == null) {
return "";
}
List<PropertyDefinitionEnumeration> enumeratedValues = propertyDefinitionSimple.getEnumeratedValues();
if (enumeratedValues != null && !enumeratedValues.isEmpty()) {
for (PropertyDefinitionEnumeration option : enumeratedValues) {
if (option.getValue().equals(value.toString())) {
return option.getName();
}
}
return value.toString();
} else {
switch (propertyDefinitionSimple.getType()) {
case BOOLEAN: {
return BOOLEAN_PROPERTY_ITEM_VALUE_MAP.get(value.toString());
}
case PASSWORD: {
return "******";
}
default:
return value.toString();
}
}
}
});
ListGridField unsetField = new ListGridField(FIELD_UNSET, MSG.view_groupConfigEdit_unset());
unsetField.setType(ListGridFieldType.BOOLEAN);
if (propertyDefinitionSimple.isRequired()) {
unsetField.setHidden(true);
unsetField.setCanEdit(false);
} else {
unsetField.setHidden(false);
unsetField.setCanEdit(true);
unsetField.setCanToggle(false); // otherwise, our handler won't get the form
// add handler to handle when the user flips the unset bit
unsetField.addChangedHandler(new com.smartgwt.client.widgets.grid.events.ChangedHandler() {
public void onChanged(com.smartgwt.client.widgets.grid.events.ChangedEvent event) {
Boolean isUnset = (Boolean) event.getValue();
FormItem valueItem = event.getForm().getField(FIELD_VALUE);
if (isUnset) {
int rowNum = event.getRowNum();
memberValuesGrid.setEditValue(rowNum, FIELD_VALUE, (String) null);
} else {
valueItem.focusInItem();
}
valueItem.redraw();
}
});
// add handler when user clears the value field - this should turn on the "unset" button
valueField.addChangeHandler(new com.smartgwt.client.widgets.grid.events.ChangeHandler() {
public void onChange(com.smartgwt.client.widgets.grid.events.ChangeEvent event) {
int rowNum = event.getRowNum();
if (event.getValue() == null || event.getValue().toString().length() == 0) {
memberValuesGrid.setEditValue(rowNum, FIELD_UNSET, true);
} else {
memberValuesGrid.setEditValue(rowNum, FIELD_UNSET, false);
}
}
});
}
idField.setWidth("75");
memberField.setWidth("*");
unsetField.setWidth("50");
valueField.setWidth("45%");
memberValuesGrid.setFields(idField, memberField, unsetField, valueField);
layout.addMember(memberValuesGrid);
// create the footer strip - will contain ok and cancel buttons
EnhancedToolStrip footerStrip = new EnhancedToolStrip();
footerStrip.setWidth100();
footerStrip.setPadding(5);
footerStrip.setMembersMargin(10);
layout.addMember(footerStrip);
// footer OK button
final IButton okButton = new EnhancedIButton(MSG.common_button_ok(), ButtonColor.BLUE);
if (!propertyIsReadOnly) {
// if its read-only, allow the ok button to be enabled to just let the user close the window
// otherwise, disable it now and we will enable it after we know things are ready to be saved
okButton.setDisabled(true);
}
okButton.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
public void onClick(ClickEvent clickEvent) {
if (!propertyIsReadOnly) {
boolean valuesHomogeneous = true;
boolean isValid = true;
String firstValue = data[0].getAttribute(FIELD_VALUE);
int i = 0;
for (ListGridRecord valueItem : data) {
String value = valueItem.getAttribute(FIELD_VALUE);
if (valuesHomogeneous) {
if ((value != null && !value.equals(firstValue)) || (value == null && firstValue != null)) {
valuesHomogeneous = false;
}
}
isValid = isValid && memberValuesGrid.validateRow(i);
Configuration config = (Configuration) valueItem.getAttributeAsObject(ATTR_CONFIG_OBJ);
PropertySimple memberPropertySimple = (PropertySimple) getProperty(config,
aggregatePropertySimple, index);
memberPropertySimple.setValue(value);
memberPropertySimple.setErrorMessage(null);
}
String aggregateStaticItemName = aggregateValueItem.getAttribute(RHQ_STATIC_ITEM_NAME_ATTRIBUTE);
FormItem aggregateStaticItem = aggregateValueItem.getForm().getField(aggregateStaticItemName);
String aggregateUnsetItemName = aggregateValueItem.getAttribute(RHQ_UNSET_ITEM_NAME_ATTRIBUTE);
FormItem aggregateUnsetItem = aggregateValueItem.getForm().getField(aggregateUnsetItemName);
aggregateUnsetItem.setDisabled(!valuesHomogeneous);
if (valuesHomogeneous) {
// Update the value of the aggregate property and set its override flag to true.
aggregatePropertySimple.setValue(firstValue);
aggregatePropertySimple.setOverride(true);
boolean isUnset = (firstValue == null);
aggregateUnsetItem.setValue(isUnset);
// Set the aggregate value item's value to the homogeneous value, unhide it, and enable it
// unless it's unset.
setValue(aggregateValueItem, firstValue);
aggregateValueItem.show();
aggregateValueItem.setDisabled(isUnset);
aggregateStaticItem.hide();
} else {
aggregatePropertySimple.setValue(null);
aggregatePropertySimple.setOverride(false);
aggregateValueItem.hide();
aggregateStaticItem.show();
}
CoreGUI.getMessageCenter().notify(
new Message(MSG.view_groupConfigEdit_saveReminder(), Severity.Info));
// this has the potential to send another message to the message center
firePropertyChangedEvent(aggregatePropertySimple, propertyDefinitionSimple, isValid);
}
popup.destroy();
}
});
footerStrip.addMember(okButton);
// track errors in grid - enable/disable OK button accordingly
// I tried many ways to get this to work right - this is what I came up with
memberValuesGrid.addRowEditorExitHandler(new RowEditorExitHandler() {
public void onRowEditorExit(RowEditorExitEvent event) {
memberValuesGrid.validateRow(event.getRowNum());
if (memberValuesGrid.hasErrors()) {
okButton.disable();
} else {
okButton.enable();
}
}
});
// if the data can be changed, add some additional widgets; if not, make the value grid read only
if (propertyIsReadOnly) {
memberValuesGrid.setCanEdit(false);
} else {
// put a cancel button in the footer strip to allow the user to exit without saving changes
final IButton cancelButton = new EnhancedIButton(MSG.common_button_cancel());
cancelButton.focus();
cancelButton.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
public void onClick(ClickEvent clickEvent) {
popup.destroy();
}
});
footerStrip.addMember(cancelButton);
// create a small form in the header strip to allow the user to apply a value to ALL rows
final DynamicForm setAllForm = new DynamicForm();
setAllForm.setNumCols(3);
setAllForm.setCellPadding(5);
setAllForm.setColWidths("20%", 50, "*"); // button, unset, value
// the "set all" value field
PropertySimple masterPropertySimple = new PropertySimple(propertyDefinitionSimple.getName(), null);
final FormItem masterValueItem = super.buildSimpleField(propertyDefinitionSimple, masterPropertySimple);
masterValueItem.setName(MEMBER_VALUES_EDITOR_FIELD_PREFIX + masterValueItem.getName());
masterValueItem.setValidateOnChange(true);
masterValueItem.setDisabled(false);
masterValueItem.setAlign(Alignment.LEFT);
masterValueItem.setStartRow(false);
masterValueItem.setEndRow(true);
// the "set all" unset field
FormItem masterUnsetItem;
if (!propertyDefinitionSimple.isRequired()) {
final CheckboxItem unsetItem = new CheckboxItem(MEMBER_VALUES_EDITOR_FIELD_PREFIX + "unsetall");
boolean unset = (masterPropertySimple == null || masterPropertySimple.getStringValue() == null);
unsetItem.setValue(unset);
unsetItem.setDisabled(isReadOnly(propertyDefinitionSimple, masterPropertySimple));
unsetItem.setShowLabel(false);
unsetItem.setLabelAsTitle(false);
unsetItem.addChangedHandler(new ChangedHandler() {
public void onChanged(ChangedEvent changedEvent) {
Boolean isUnset = (Boolean) changedEvent.getValue();
masterValueItem.setDisabled(isUnset);
if (isUnset) {
setValue(masterValueItem, null);
}
masterValueItem.redraw();
}
});
masterValueItem.addChangeHandler(new ChangeHandler() {
public void onChange(ChangeEvent event) {
if (event.getValue() == null || event.getValue().toString().length() == 0) {
unsetItem.setValue(true);
} else {
unsetItem.setValue(false);
}
}
});
masterUnsetItem = unsetItem;
} else {
masterUnsetItem = new SpacerItem();
}
masterUnsetItem.setShowTitle(false);
masterUnsetItem.setStartRow(false);
masterUnsetItem.setEndRow(false);
masterUnsetItem.setAlign(Alignment.CENTER);
if (masterUnsetItem instanceof CheckboxItem) {
masterUnsetItem.setValue(false);
} else {
masterUnsetItem.setVisible(false);
}
// the "set all values to" apply button
final ButtonItem applyButton = new ButtonItem("apply", MSG.view_groupConfigEdit_setAll());
applyButton.setDisabled(true);
applyButton.setAlign(Alignment.RIGHT);
applyButton.setStartRow(true);
applyButton.setEndRow(false);
applyButton.addClickHandler(new ClickHandler() {
public void onClick(com.smartgwt.client.widgets.form.fields.events.ClickEvent event) {
if (masterValueItem.validate()) {
Object value = masterValueItem.getValue();
// Update the member property value items.
for (ListGridRecord field : data) {
String stringValue = (value != null) ? value.toString() : null;
field.setAttribute(FIELD_VALUE, stringValue);
field.setAttribute(FIELD_UNSET, value == null);
}
// since we know the master value is ok and has validated,
// we can clear all validation errors and re-enable the OK button
memberValuesGrid.discardAllEdits();
okButton.enable();
// redraw grid with the new values
memberValuesGrid.redraw();
}
}
});
masterValueItem.addChangedHandler(new ChangedHandler() {
public void onChanged(ChangedEvent changedEvent) {
applyButton.enable();
}
});
masterUnsetItem.addChangedHandler(new ChangedHandler() {
public void onChanged(ChangedEvent changedEvent) {
applyButton.enable();
if ((Boolean) changedEvent.getValue()) {
applyButton.focusInItem(); // unsetting value, nothing to enter so go to apply button
} else {
masterValueItem.focusInItem(); // if turning off "unset", go to value field to enter a value to set
}
}
});
setAllForm.setFields(applyButton, masterUnsetItem, masterValueItem);
headerStrip.addMember(setAllForm);
}
return layout;
}
@Override
protected boolean shouldFireEventOnPropertyValueChange(FormItem formItem,
PropertyDefinitionSimple propertyDefinitionSimple, PropertySimple propertySimple) {
if (formItem.getName().startsWith(MEMBER_VALUES_EDITOR_FIELD_PREFIX)) {
return false; // this is a form item in our member values editor dialog - don't fire event for it
}
boolean isAgg = isAggregateProperty(propertySimple);
return isAgg && super.shouldFireEventOnPropertyValueChange(formItem, propertyDefinitionSimple, propertySimple);
}
@Override
protected void updatePropertySimpleValue(FormItem formItem, Object value, PropertySimple propertySimple,
PropertyDefinitionSimple propertyDefinitionSimple) {
if ((propertySimple.getConfiguration() != null) || (propertySimple.getParentMap() != null)
|| (propertySimple.getParentList() != null)) {
// Update the aggregate property. this could also be a member values editor field - but we do the same thing
super.updatePropertySimpleValue(formItem, value, propertySimple, propertyDefinitionSimple);
propertySimple.setOverride(true);
// Update all the member properties if this is a true aggregate property
if (!formItem.getName().startsWith(MEMBER_VALUES_EDITOR_FIELD_PREFIX)) {
for (GroupMemberConfiguration memberConfiguration : this.memberConfigurations) {
Configuration configuration = memberConfiguration.getConfiguration();
PropertySimple memberPropertySimple = (PropertySimple) getProperty(configuration, propertySimple,
null);
memberPropertySimple.setErrorMessage(null);
memberPropertySimple.setValue(value);
}
}
}
}
@Override
protected void removePropertyFromDynamicMap(PropertySimple propertySimple) {
// Remove the aggregate property.
super.removePropertyFromDynamicMap(propertySimple);
// Remove the member properties.
for (GroupMemberConfiguration memberConfiguration : this.memberConfigurations) {
Configuration configuration = memberConfiguration.getConfiguration();
PropertySimple memberPropertySimple = (PropertySimple) getProperty(configuration, propertySimple, null);
PropertyMap parentMap = memberPropertySimple.getParentMap();
parentMap.getMap().remove(memberPropertySimple.getName());
}
}
@Override
protected void addPropertyToDynamicMap(PropertySimple propertySimple, PropertyMap propertyMap) {
// Add the aggregate property.
super.addPropertyToDynamicMap(propertySimple, propertyMap);
// Add the member properties.
for (GroupMemberConfiguration memberConfiguration : this.memberConfigurations) {
Configuration configuration = memberConfiguration.getConfiguration();
// The below call will create the member property and add it to the appropriate parent property in the member config.
PropertySimple memberPropertySimple = (PropertySimple) getProperty(configuration, propertySimple, null);
}
}
private Property getProperty(Configuration configuration, Property referenceProperty, Integer index) {
List<Property> referenceHierarchy = getParentFirstPropertiesHierarchy(referenceProperty);
// Add referenceProperty to the configuration, creating parents if necessary
Property property = configuration.get(referenceHierarchy.get(0).getName());
if (property == null) {
property = createPropertyByExample(referenceHierarchy.get(0));
configuration.put(property);
}
for (int i = 1; i < referenceHierarchy.size(); i++) {
String childPropertyName = referenceHierarchy.get(i).getName();
if (property instanceof PropertyMap) {
PropertyMap propertyMap = (PropertyMap) property;
property = propertyMap.get(childPropertyName);
if (property == null) {
property = createPropertyByExample(referenceHierarchy.get(i));
propertyMap.put(property);
}
} else if (property instanceof PropertyList) {
PropertyList propertyList = (PropertyList) property;
if (index < propertyList.getList().size()) {
property = propertyList.getList().get(index);
} else {
property = createPropertyByExample(referenceHierarchy.get(i));
propertyList.add(property);
}
}
}
return property;
}
private List<Property> getParentFirstPropertiesHierarchy(Property bottomProperty) {
LinkedList<Property> propertyHierarchy = new LinkedList<Property>();
for (Property currentProperty = bottomProperty; currentProperty != null; ) {
propertyHierarchy.addFirst(currentProperty);
if (currentProperty.getParentMap() != null) {
currentProperty = currentProperty.getParentMap();
} else if (currentProperty.getParentList() != null) {
currentProperty = currentProperty.getParentList();
} else if (currentProperty.getConfiguration() == null) {
throw new IllegalStateException(currentProperty + " has no parent.");
} else {
currentProperty = null;
}
}
return new ArrayList<Property>(propertyHierarchy);
}
private <T extends Property> T createPropertyByExample(T example) {
T property = null;
if (example instanceof PropertyMap) {
property = (T) new PropertyMap(example.getName());
} else if (example instanceof PropertyList) {
property = (T) new PropertyList(example.getName());
} else {
property = (T) new PropertySimple(example.getName(), null);
}
return property;
}
private boolean isAggregateProperty(PropertySimple propertySimple) {
return (getConfiguration(propertySimple) == getConfiguration());
}
private static Boolean isHomogeneous(PropertySimple propertySimple) {
Boolean override = propertySimple.getOverride();
return (override != null && override);
}
private static Configuration getConfiguration(Property property) {
Property topLevelProperty = getTopLevelProperty(property);
return topLevelProperty.getConfiguration();
}
private ListGridRecord[] getMemberValuesGridData(List<GroupMemberConfiguration> memberConfigs,
PropertyDefinitionSimple propDef, PropertySimple aggregateProp, Integer propertyIndex) {
ListGridRecord[] records;
if (memberConfigs == null || memberConfigs.isEmpty()) {
records = new ListGridRecord[0];
} else {
records = new ListGridRecord[memberConfigs.size()];
int i = 0;
for (GroupMemberConfiguration item : memberConfigs) {
ListGridRecord record = new ListGridRecord();
record.setAttribute(FIELD_ID, item.getId());
record.setAttribute(FIELD_MEMBER, item.getLabel());
Configuration configuration = item.getConfiguration();
PropertySimple memberProp = (PropertySimple) getProperty(configuration, aggregateProp, propertyIndex);
if (!propDef.isRequired()) {
boolean isUnset = (memberProp == null || memberProp.getStringValue() == null);
record.setAttribute(FIELD_UNSET, isUnset);
}
String value = memberProp.getStringValue();
if (null == value && null != propDef.getDefaultValue()) {
value = propDef.getDefaultValue();
}
if (!propDef.isReadOnly()) {
// user-editable, so escape HTML, to prevent XSS attacks.
value = StringUtility.escapeHtml(value);
}
record.setAttribute(FIELD_VALUE, value);
record.setAttribute(ATTR_CONFIG_OBJ, configuration);
records[i++] = record;
}
}
return records;
}
/**
* Creates the editor type for the given value field. Validators will be added appropriately.
*
* @param valueField the field that is editable
* @param propDef the definition of the property to be edited in the field
* @return the editor item
*/
protected FormItem buildEditorFormItem(ListGridField valueField, final PropertyDefinitionSimple propDef) {
FormItem editorItem = null;
// note: we don't have to worry about "read only" properties - if the members
// value editor is in read-only mode or viewing read-only properties, the list grid
// isn't editable anyway.
List<PropertyDefinitionEnumeration> enumeratedValues = propDef.getEnumeratedValues();
if (enumeratedValues != null && !enumeratedValues.isEmpty()) {
LinkedHashMap<String, String> valueOptions = new LinkedHashMap<String, String>();
for (PropertyDefinitionEnumeration option : enumeratedValues) {
valueOptions.put(option.getValue(), option.getName());
}
if (propDef.getAllowCustomEnumeratedValue()) {
editorItem = new ComboBoxItem();
((ComboBoxItem) editorItem).setAddUnknownValues(true);
} else {
if (valueOptions.size() > 5) {
editorItem = new SortedSelectItem();
} else {
// TODO: we want RadioGroupItem, but smartgwt seems to have a bug and it won't render this when
// our listgrid does not have the "unset" boolean field also present. If its just the value
// field, the radio group editor won't show.
editorItem = new SortedSelectItem(); // new RadioGroupItem();
}
}
editorItem.setValueMap(valueOptions);
} else {
switch (propDef.getType()) {
case STRING:
case FILE:
case DIRECTORY:
editorItem = new TextItem();
break;
case LONG_STRING:
editorItem = new TextAreaItem();
break;
case PASSWORD:
editorItem = new PasswordItem();
editorItem.setAttribute("autocomplete", "off");
break;
case BOOLEAN:
// TODO: we want RadioGroupItem, but smartgwt seems to have a bug and it won't render this when
// our listgrid does not have the "unset" boolean field also present. If its just the value
// field, the radio group editor won't show.
// RadioGroupItem radioGroupItem = new RadioGroupItem();
// radioGroupItem.setVertical(false);
// editorItem = radioGroupItem;
editorItem = new SelectItem();
editorItem.setValueMap(BOOLEAN_PROPERTY_ITEM_VALUE_MAP);
break;
case INTEGER:
case LONG:
editorItem = new TextItem(); // could not get smartgwt listgrid editing to work with IntegerItem
break;
case FLOAT:
case DOUBLE:
editorItem = new TextItem(); // could not get smartgwt listgrid editing to work with FloatItem
break;
}
}
editorItem.setName(MEMBER_VALUES_EDITOR_FIELD_PREFIX + "editor");
List<Validator> validators = buildValidators(propDef, new PropertySimple());
valueField.setValidators(validators.toArray(new Validator[validators.size()]));
return editorItem;
}
}