/* * RHQ Management Platform * Copyright (C) 2005-2008 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.core.gui.configuration.propset; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import javax.el.ValueExpression; import javax.faces.component.UIComponent; import javax.faces.component.UIForm; import javax.faces.component.UIInput; import javax.faces.component.html.HtmlPanelGrid; import javax.faces.component.html.HtmlPanelGroup; import javax.faces.component.html.HtmlSelectBooleanCheckbox; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.Renderer; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertyMap; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple; import org.rhq.core.gui.configuration.ConfigRenderer; import org.rhq.core.gui.configuration.CssStyleClasses; import org.rhq.core.gui.configuration.helper.ConfigurationUtility; import org.rhq.core.gui.configuration.helper.PropertyRenderingUtility; import org.rhq.core.gui.util.FacesComponentUtility; import org.rhq.core.gui.util.FacesContextUtility; import org.rhq.core.gui.util.FacesExpressionUtility; import org.rhq.core.util.sort.HumaneStringComparator; /** * @author Ian Springer */ public class PropertySetRenderer extends Renderer { static final String RENDERER_TYPE = "org.rhq.PropertySet"; private static final String INIT_INPUTS_JAVA_SCRIPT_COMPONENT_ID_SUFFIX = "-initInputsJavaScript"; private static final String PROPERTY_ALL_TO_SAME_VALUE_CONTROLS_COLUMN_CLASSES = "," + CssStyleClasses.PROPERTY_VALUE_CELL_BORDERLESS + ","; private static final String OPTIONAL_PROPERTY_ALL_TO_SAME_VALUE_CONTROLS_COLUMN_CLASSES = "," + CssStyleClasses.PROPERTY_VALUE_CELL_BORDERLESS + ",,,"; /** * Decode any new state from request parameters for the given {@link PropertySetComponent}. * * @param facesContext the JSF context for the current request * @param component the {@link PropertySetComponent} for which request parameters should be decoded */ @Override public void decode(FacesContext facesContext, UIComponent component) { PropertySetComponent propertySetComponent = (PropertySetComponent) component; validateAttributes(propertySetComponent); String id = getInitInputsJavaScriptComponentId(propertySetComponent); UIComponent initInputsJavaScriptComponent = propertySetComponent.findComponent(id); if (initInputsJavaScriptComponent != null) { FacesComponentUtility.detachComponent(initInputsJavaScriptComponent); PropertyRenderingUtility.addInitInputsJavaScript(propertySetComponent, id, false, true); } } /** * Render the beginning of the given {@link PropertySetComponent}. * * @param facesContext the JSF context for the current request * @param component the {@link PropertySetComponent} to be encoded */ @Override public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException { PropertySetComponent propertySetComponent = (PropertySetComponent) component; validateAttributes(propertySetComponent); // If it's an AJAX request for this component, apply the group config to the member configs // and clear our child components. NOTE: This can *not* be done in decode(), because the model // will not have been updated yet with any changes made to child UIInput values. String refresh = FacesContextUtility.getOptionalRequestParameter("refresh"); if (refresh != null && refresh.equals(ConfigRenderer.PROPERTY_SET_COMPONENT_ID)) { propertySetComponent.getConfigurationSet().applyGroupConfiguration(); component.getChildren().clear(); } ResponseWriter writer = facesContext.getResponseWriter(); String clientId = component.getClientId(facesContext); writer.write('\n'); writer.writeComment("********** Start of " + component.getClass().getSimpleName() + " component **********"); writer.startElement("div", component); writer.writeAttribute("id", clientId, "clientId"); // Only create the child components the first time around (i.e. once per JSF lifecycle). if (component.getChildCount() != 0) return; PropertySimple propertySimple = propertySetComponent.getProperty(); if (propertySimple == null) // This means the 'propertyExpressionString' request parameter was not specified, which means this is // the initial display of the main configSet page, which means there's nothing for us to render. return; PropertyDefinitionSimple propertyDefinitionSimple = propertySetComponent.getPropertyDefinition(); addPropertyDisplayNameAndDescription(propertySetComponent, propertyDefinitionSimple, propertySimple); boolean configReadOnly = propertySetComponent.getReadOnly() != null && propertySetComponent.getReadOnly(); boolean propIsReadOnly = PropertyRenderingUtility.isReadOnly(propertyDefinitionSimple.isReadOnly(), propertyDefinitionSimple.isRequired(), null, configReadOnly,false); HtmlPanelGroup setAllToSameValueControlPanel = null; if (!propIsReadOnly) { // NOTE: We'll add children to the below panel a bit later when we know the id's of the inputs. FacesComponentUtility.addVerbatimText(propertySetComponent, "<table class='" + CssStyleClasses.MEMBER_PROPERTIES_TABLE + "'><tr><td align='center'>"); setAllToSameValueControlPanel = FacesComponentUtility.addBlockPanel(propertySetComponent, null, null); FacesComponentUtility.addVerbatimText(propertySetComponent, "</td></tr></table>"); FacesComponentUtility.addVerbatimText(propertySetComponent, "<br/>\n"); } FacesComponentUtility.addVerbatimText(propertySetComponent, "\n\n<table class='" + CssStyleClasses.MEMBER_PROPERTIES_TABLE + "'>"); addPropertiesTableHeaderRow(propertySetComponent, propertyDefinitionSimple); List<PropertyInfo> propertyInfos = createPropertyInfos(propertySetComponent, propertySimple); for (int i = 0; i < propertyInfos.size(); i++) { PropertyInfo propertyInfo = propertyInfos.get(i); String rowStyleClass = ((i % 2) == 0) ? CssStyleClasses.ROW_ODD : CssStyleClasses.ROW_EVEN; addPropertyRow(propertySetComponent, propertyDefinitionSimple, propertyInfo, rowStyleClass); } FacesComponentUtility.addVerbatimText(propertySetComponent, "</table>\n"); FacesComponentUtility.addVerbatimText(propertySetComponent, "<br/>\n"); if (!propIsReadOnly) addSetAllToSameValueControls(propertySetComponent, propertyDefinitionSimple, setAllToSameValueControlPanel, propertyInfos); String id = getInitInputsJavaScriptComponentId(propertySetComponent); PropertyRenderingUtility.addInitInputsJavaScript(propertySetComponent, id, false, false); } private void addSetAllToSameValueControls(PropertySetComponent propertySetComponent, PropertyDefinitionSimple propertyDefinitionSimple, HtmlPanelGroup setAllToSameValueControlPanel, List<PropertyInfo> propertyInfos) { String masterInputId = propertySetComponent.getId() + "_setAllToSameValue"; UIInput input = PropertyRenderingUtility.createInput(propertyDefinitionSimple); input.setId(masterInputId); // NOTE: Don't add the input to the component tree yet - we'll add it a bit later. HtmlPanelGrid panelGrid = FacesComponentUtility.createComponent(HtmlPanelGrid.class); if (isOptional(propertyDefinitionSimple)) { panelGrid.setColumns(5); panelGrid.setColumnClasses(OPTIONAL_PROPERTY_ALL_TO_SAME_VALUE_CONTROLS_COLUMN_CLASSES); } else { panelGrid.setColumns(3); panelGrid.setColumnClasses(PROPERTY_ALL_TO_SAME_VALUE_CONTROLS_COLUMN_CLASSES); } setAllToSameValueControlPanel.getChildren().add(panelGrid); FacesComponentUtility.addOutputText(panelGrid, null, "Set All Values To: ", null); // the property value input panelGrid.getChildren().add(input); // the unset checkbox (if property is optional) HtmlSelectBooleanCheckbox unsetCheckbox = null; if (isOptional(propertyDefinitionSimple)) { FacesComponentUtility.addOutputText(panelGrid, null, "Unset All: ", null); unsetCheckbox = PropertyRenderingUtility.addUnsetControl(panelGrid, propertyDefinitionSimple.isRequired(), propertyDefinitionSimple.isReadOnly(), null, propertySetComponent.getListIndex(), input, false, propertySetComponent.getReadOnly(), false); } // the 'APPLY' button UIForm form = FacesComponentUtility.getEnclosingForm(input); StringBuilder html = new StringBuilder(); html.append("<button type='button' class='" + CssStyleClasses.BUTTON_SMALL + "' onclick='"); if (isOptional(propertyDefinitionSimple)) { html.append("setAllValuesForOptionalProperty(new Array("); for (PropertyInfo propertyInfo : propertyInfos) { String inputHtmlDomReference = getHtmlDomReference(propertyInfo.getInput(), form, '"'); html.append(inputHtmlDomReference).append(", "); } html.delete(html.length() - 2, html.length()); // chop off the extra ", " html.append("), new Array("); for (PropertyInfo propertyInfo : propertyInfos) { String unsetCheckboxHtmlDomReference = getHtmlDomReference(propertyInfo.getUnsetCheckbox(), form, '"'); html.append(unsetCheckboxHtmlDomReference).append(", "); } html.delete(html.length() - 2, html.length()); // chop off the extra ", " String masterInputHtmlDomReference = getHtmlDomReference(input, form, '"'); html.append("), getElementValue(").append(masterInputHtmlDomReference); String masterUnsetCheckboxHtmlDomReference = getHtmlDomReference(unsetCheckbox, form, '"'); html.append("), ").append(masterUnsetCheckboxHtmlDomReference).append(".checked);"); } else { /*html.append("setInputsUnset(new Array("); for (PropertyInfo propertyInfo : propertyInfos) { String inputHtmlDomReference = getHtmlDomReference(propertyInfo.getInput(), form, '"'); html.append(inputHtmlDomReference).append(", "); } html.delete(html.length() - 2, html.length()); // chop off the extra ", " html.append("), false);");*/ html.append("setInputsToValue(new Array("); for (PropertyInfo propertyInfo : propertyInfos) { String inputHtmlDomReference = getHtmlDomReference(propertyInfo.getInput(), form, '"'); html.append(inputHtmlDomReference).append(", "); } html.delete(html.length() - 2, html.length()); // chop off the extra ", " html.append("), "); String masterInputHtmlDomReference = getHtmlDomReference(input, form, '"'); html.append("getElementValue(").append(masterInputHtmlDomReference).append("));"); } html.append("'>Apply</button>"); FacesComponentUtility.addVerbatimText(panelGrid, html); } private static String getHtmlDomReference(UIInput input, UIForm form, char quoteChar) { FacesContext facesContext = FacesContext.getCurrentInstance(); String inputHtmlDomReference = "document." + form.getClientId(facesContext) + "[" + quoteChar + input.getClientId(facesContext) + quoteChar + "]"; return inputHtmlDomReference; } @Override public void encodeEnd(FacesContext context, UIComponent component) throws IOException { ResponseWriter writer = context.getResponseWriter(); writer.writeText("\n", component, null); writer.endElement("div"); writer.writeComment("********** End of " + component.getClass().getSimpleName() + " component **********"); } private void addPropertyDisplayNameAndDescription(PropertySetComponent propertySetComponent, PropertyDefinitionSimple propertyDefinitionSimple, PropertySimple propertySimple) { FacesComponentUtility.addVerbatimText(propertySetComponent, "<br/>\n"); PropertyRenderingUtility.addPropertyDisplayName(propertySetComponent, propertyDefinitionSimple, propertySimple, propertySetComponent.getReadOnly()); if (propertyDefinitionSimple != null) { FacesComponentUtility.addVerbatimText(propertySetComponent, " - "); PropertyRenderingUtility.addPropertyDescription(propertySetComponent, propertyDefinitionSimple); } FacesComponentUtility.addVerbatimText(propertySetComponent, "<br/><br/>\n"); } private List<PropertyInfo> createPropertyInfos(PropertySetComponent propertySetComponent, PropertySimple groupPropertySimple) { ValueExpression configurationInfosExpression = propertySetComponent .getValueExpression(PropertySetComponent.CONFIGURATION_SET_ATTRIBUTE); String configurationInfosExpressionString = configurationInfosExpression.getExpressionString(); String configurationExpressionStringFormat = "#{" + FacesExpressionUtility.unwrapExpressionString(configurationInfosExpressionString) + ".members[%d].configuration}"; String propertyExpressionStringFormat = createPropertyExpressionFormat(configurationExpressionStringFormat, groupPropertySimple, propertySetComponent.getListIndex()); String propertyValueExpressionStringFormat = "#{" + FacesExpressionUtility.unwrapExpressionString(propertyExpressionStringFormat) + ".stringValue}"; //noinspection ConstantConditions List<ConfigurationSetMember> configurationSetMembers = propertySetComponent.getConfigurationSet().getMembers(); List<PropertyInfo> propertyInfos = new ArrayList(configurationSetMembers.size()); for (int i = 0; i < configurationSetMembers.size(); i++) { @SuppressWarnings( { "ConstantConditions" }) ConfigurationSetMember memberInfo = configurationSetMembers.get(i); String propertyExpressionString = String.format(propertyExpressionStringFormat, i); PropertySimple propertySimple = FacesExpressionUtility.getValue(propertyExpressionString, PropertySimple.class); String propertyValueExpressionString = String.format(propertyValueExpressionStringFormat, i); ValueExpression propertyValueExpression = FacesExpressionUtility.createValueExpression( propertyValueExpressionString, String.class); PropertyInfo propertyInfo = new PropertyInfo(memberInfo.getLabel(), propertySimple, propertyValueExpression); propertyInfos.add(propertyInfo); } Collections.sort(propertyInfos); return propertyInfos; } private void addPropertiesTableHeaderRow(PropertySetComponent propertySetComponent, PropertyDefinitionSimple propertyDefinitionSimple) { FacesComponentUtility.addVerbatimText(propertySetComponent, "\n\n<tr>"); FacesComponentUtility.addVerbatimText(propertySetComponent, "<th class='" + CssStyleClasses.PROPERTIES_TABLE_HEADER_CELL + "'>"); FacesComponentUtility.addOutputText(propertySetComponent, null, "Member", FacesComponentUtility.NO_STYLE_CLASS); FacesComponentUtility.addVerbatimText(propertySetComponent, "</th>"); if (isOptional(propertyDefinitionSimple)) { FacesComponentUtility.addVerbatimText(propertySetComponent, "<th class='" + CssStyleClasses.PROPERTIES_TABLE_HEADER_CELL + "'>"); FacesComponentUtility.addOutputText(propertySetComponent, null, "Unset", FacesComponentUtility.NO_STYLE_CLASS); FacesComponentUtility.addVerbatimText(propertySetComponent, "</th>"); } FacesComponentUtility.addVerbatimText(propertySetComponent, "<th class='" + CssStyleClasses.PROPERTIES_TABLE_HEADER_CELL + "'>"); FacesComponentUtility.addOutputText(propertySetComponent, null, "Value", FacesComponentUtility.NO_STYLE_CLASS); FacesComponentUtility.addVerbatimText(propertySetComponent, "</th>"); FacesComponentUtility.addVerbatimText(propertySetComponent, "</tr>"); } private void validateAttributes(PropertySetComponent propertySetComponent) { /*if (propertySetComponent.getValueExpression(PropertySetComponent.PROPERTY_DEFINITION_ATTRIBUTE) == null) { throw new IllegalStateException("The " + propertySetComponent.getClass().getName() + " component requires a '" + PropertySetComponent.PROPERTY_DEFINITION_ATTRIBUTE + "' attribute."); }*/ if (propertySetComponent.getValueExpression(PropertySetComponent.CONFIGURATION_SET_ATTRIBUTE) == null) { throw new IllegalStateException("The " + propertySetComponent.getClass().getName() + " component requires a '" + PropertySetComponent.CONFIGURATION_SET_ATTRIBUTE + "' attribute."); } } private static String createPropertyExpressionFormat(String configurationExpressionString, PropertySimple propertySimple, Integer listIndex) { StringBuilder stringBuilder = new StringBuilder("#{"); stringBuilder.append(FacesExpressionUtility.unwrapExpressionString(configurationExpressionString)); LinkedList<Property> propertyHierarchy = ConfigurationUtility.getPropertyHierarchy(propertySimple); for (Property property : propertyHierarchy) { Property parentProperty = ConfigurationUtility.getParentProperty(property); stringBuilder.append("."); if (parentProperty == null || parentProperty instanceof PropertyMap) { // top-level or map member property stringBuilder.append("map['").append(property.getName()).append("']"); } else { // list member property stringBuilder.append("list[").append(listIndex).append("]"); } } stringBuilder.append("}"); return stringBuilder.toString(); } private String getInitInputsJavaScriptComponentId(PropertySetComponent propertySetComponent) { return propertySetComponent.getId() + INIT_INPUTS_JAVA_SCRIPT_COMPONENT_ID_SUFFIX; } private static void addPropertyRow(PropertySetComponent propertySetComponent, PropertyDefinitionSimple propertyDefinitionSimple, PropertyInfo propertyInfo, String rowStyleClass) { FacesComponentUtility.addVerbatimText(propertySetComponent, "\n\n<tr class='" + rowStyleClass + "'>"); // Member (i.e. label) column FacesComponentUtility.addVerbatimText(propertySetComponent, "<td class='" + CssStyleClasses.MEMBER_PROPERTY_LABEL_CELL + "'>"); FacesComponentUtility.addOutputText(propertySetComponent, null, propertyInfo.getLabel(), null); FacesComponentUtility.addVerbatimText(propertySetComponent, "</td>"); // Create the input, which is referenced by the unset control and rendered under the Value column. UIInput input; if (propertyDefinitionSimple != null) input = PropertyRenderingUtility.createInputForSimpleProperty(propertyDefinitionSimple, propertyInfo .getProperty(), propertyInfo.getPropertyValueExpression(), propertySetComponent.getListIndex(), false, propertySetComponent.getReadOnly(), false, true,null); else input = PropertyRenderingUtility.createInputForSimpleProperty(propertyInfo.getProperty(), propertyInfo .getPropertyValueExpression(), propertySetComponent.getReadOnly()); propertyInfo.setInput(input); // Unset column (only if property is optional) if (isOptional(propertyDefinitionSimple)) { FacesComponentUtility.addVerbatimText(propertySetComponent, "<td class='" + CssStyleClasses.MEMBER_PROPERTY_UNSET_CELL + "'>"); HtmlSelectBooleanCheckbox unsetCheckbox = PropertyRenderingUtility.addUnsetControl(propertySetComponent, propertyDefinitionSimple.isRequired(), propertyDefinitionSimple.isReadOnly(), propertyInfo.getProperty(), propertySetComponent.getListIndex(), input, false, propertySetComponent.getReadOnly(), false); propertyInfo.setUnsetCheckbox(unsetCheckbox); FacesComponentUtility.addVerbatimText(propertySetComponent, "</td>"); } // Value column FacesComponentUtility.addVerbatimText(propertySetComponent, "<td class='" + CssStyleClasses.MEMBER_PROPERTY_VALUE_CELL + "'>"); propertySetComponent.getChildren().add(input); FacesComponentUtility.addVerbatimText(propertySetComponent, "<br/>"); PropertyRenderingUtility.addMessageComponentForInput(propertySetComponent, input); FacesComponentUtility.addVerbatimText(propertySetComponent, "</td>"); FacesComponentUtility.addVerbatimText(propertySetComponent, "</tr>"); } private static boolean isOptional(PropertyDefinitionSimple propertyDefinitionSimple) { return propertyDefinitionSimple == null || !propertyDefinitionSimple.isRequired(); } class PropertyInfo implements Comparable<PropertyInfo> { private String label; private PropertySimple property; private ValueExpression propertyValueExpression; private UIInput input; private HtmlSelectBooleanCheckbox unsetCheckbox; PropertyInfo(String label, PropertySimple property, ValueExpression propertyValueExpression) { // Ensure this.label will never be null, so our compareTo() impl doesn't need to deal with null labels. this.label = (label != null) ? label : ""; this.property = property; this.propertyValueExpression = propertyValueExpression; } public String getLabel() { return label; } public PropertySimple getProperty() { return property; } public ValueExpression getPropertyValueExpression() { return propertyValueExpression; } public UIInput getInput() { return input; } public void setInput(UIInput input) { this.input = input; } public HtmlSelectBooleanCheckbox getUnsetCheckbox() { return unsetCheckbox; } public void setUnsetCheckbox(HtmlSelectBooleanCheckbox unsetCheckbox) { this.unsetCheckbox = unsetCheckbox; } public int compareTo(PropertyInfo that) { return new HumaneStringComparator().compare(this.label, that.label); } } }