1 /** 2 * Copyright 2005-2013 The Kuali Foundation 3 * 4 * Licensed under the Educational Community License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.opensource.org/licenses/ecl2.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.kuali.rice.krad.uif.component; 17 18 import org.kuali.rice.krad.datadictionary.parse.BeanTag; 19 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 20 import org.kuali.rice.krad.datadictionary.parse.BeanTags; 21 import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase; 22 import org.kuali.rice.krad.datadictionary.validator.ValidationTrace; 23 import org.kuali.rice.krad.datadictionary.validator.Validator; 24 25 import java.io.Serializable; 26 import java.util.ArrayList; 27 import java.util.List; 28 import java.util.Map; 29 30 /** 31 * Configuration for replacing a property value based on a condition 32 * 33 * <p> 34 * A <code>Component</code> may be configured with one or more <code>PropertyReplacer</code> instances. Each defines 35 * a condition to evaluate during the apply model phase, and if that condition succeeds the property on the component 36 * given by {@link #getPropertyName()}, will be replaced with the value given by {@link #getReplacement()}. Conditions 37 * are defined using an expression language and may reference any variables available in the component's context. 38 * </p> 39 * 40 * <p> 41 * Property replacers can be used to change out an entire Component or List/Map of Components. For example, based on a 42 * condition you might want to display a <code>TextControl</code> or <code>RadioControl</code> for an 43 * <code>InputField</code>. You can define the field with a text control, then include a property replacer as 44 * follows: 45 * <pre> 46 * <bean parent="PropertyReplacer" p:propertyName="control" 47 * p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/> 48 * 49 * </pre> 50 * 51 * Note <code>Component</code> contains a <code>List</code> or property replacers which will be evaluated in the order 52 * contained within the list. So in the above example if we wanted to now add a further condition which sets the 53 * control 54 * to a checkbox, we would just add another property replacer bean. 55 * <pre> 56 * <property name="propertyReplacers"> 57 * <list> 58 * <bean parent="PropertyReplacer" p:propertyName="control" 59 * p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/> 60 * <bean parent="PropertyReplacer" p:propertyName="control" 61 * p:condition="field1 eq '11456'" p:replacement-ref="CheckboxControl"/> 62 * </list> 63 * </property> 64 * </pre> 65 * 66 * Property replacers may be used to substitute primitive properties as well, such as Strings 67 * </p> 68 * 69 * @author Kuali Rice Team (rice.collab@kuali.org) 70 */ 71 @BeanTags({@BeanTag(name = "propertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer"), 72 @BeanTag(name = "conditionalBeanPropertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer")}) 73 public class PropertyReplacer extends UifDictionaryBeanBase implements Serializable { 74 private static final long serialVersionUID = -8405429643299461398L; 75 76 private String propertyName; 77 78 @KeepExpression 79 private String condition; 80 private Object replacement; 81 82 public PropertyReplacer() { 83 super(); 84 } 85 86 /** 87 * Returns a list of nested components 88 * 89 * <p> 90 * All nested components will be returned in the list. Current assumption is that 91 * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or 92 * <code>Map</code> for nested components 93 * </p> 94 * 95 * @return List<Component> nested components 96 */ 97 public List<Component> getNestedComponents() { 98 ArrayList<Component> nestedComponents = new ArrayList<Component>(); 99 100 if (replacement instanceof Component) { 101 nestedComponents.add(((Component) replacement)); 102 } else if (replacement instanceof List) { 103 for (Object replacementItem : (List<?>) replacement) { 104 if (replacementItem instanceof Component) { 105 nestedComponents.add((Component) replacementItem); 106 } 107 } 108 } else if (replacement instanceof Map) { 109 for (Object replacementItem : ((Map<?, ?>) replacement).values()) { 110 if (replacementItem instanceof Component) { 111 nestedComponents.add((Component) replacementItem); 112 } 113 } 114 } 115 116 return nestedComponents; 117 } 118 119 /** 120 * Name of the property on the Component the property replacer is associated with that 121 * will be set when the condition for the replacer succeeds 122 * 123 * <p> 124 * Note the property name must be readable/writable on the component. The property name may 125 * be nested, and include Map or List references. 126 * </p> 127 * 128 * @return String property name to set 129 */ 130 @BeanTagAttribute(name = "propertyName") 131 public String getPropertyName() { 132 return this.propertyName; 133 } 134 135 /** 136 * Setter for the property name that will be set 137 * 138 * @param propertyName 139 */ 140 public void setPropertyName(String propertyName) { 141 this.propertyName = propertyName; 142 } 143 144 /** 145 * Gives the expression that should be evaluated to determine whether or not 146 * the property replacement should be made 147 * 148 * <p> 149 * Expression follows SPEL and may access any model data along with any variables 150 * available in the context for the Component. The expression should evaluate to 151 * a boolean. If the resulting boolean is true, the object given by {@link #getReplacement()} 152 * will be set as the value for the associated property on the component. If the resulting 153 * boolean is false, no action will take place 154 * </p> 155 * 156 * <p> 157 * Note the value does not need to contain the expression placeholder @{} 158 * </p> 159 * 160 * @return String expression that should be evaluated 161 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService 162 * @see org.kuali.rice.krad.uif.UifConstants.ContextVariableNames 163 */ 164 @BeanTagAttribute(name = "condition") 165 public String getCondition() { 166 return this.condition; 167 } 168 169 /** 170 * Setter for the replacement condition 171 * 172 * @param condition 173 */ 174 public void setCondition(String condition) { 175 this.condition = condition; 176 } 177 178 /** 179 * Gives the Object that should be used to set the property value if the replacers condition 180 * evaluates to true 181 * 182 * <p> 183 * Note the configured Object must be valid for the type given by the property on the Component. Standard 184 * property editors will be used for setting the property value 185 * </p> 186 * 187 * @return Object instance to set 188 */ 189 @BeanTagAttribute(name = "replacement", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 190 public Object getReplacement() { 191 return this.replacement; 192 } 193 194 /** 195 * Setter for the replacement Object 196 * 197 * @param replacement 198 */ 199 public void setReplacement(Object replacement) { 200 this.replacement = replacement; 201 } 202 203 /** 204 * Validates different requirements of component compiling a series of reports detailing information on errors 205 * found in the component. Used by the RiceDictionaryValidator. 206 * 207 * @param tracer Record of component's location 208 */ 209 public void completeValidation(ValidationTrace tracer) { 210 tracer.addBean("PropertyReplacer", getPropertyName()); 211 212 // Checking that required fields are set 213 if (getPropertyName() == null || getCondition() == null || getReplacement() == null) { 214 String currentValues[] = {"propertyName =" + getPropertyName(), "condition =" + getCondition(), 215 "replacement =" + getReplacement()}; 216 tracer.createWarning("PropertyName, condition and replacement should be set", currentValues); 217 } 218 219 // Validating Spring EL in condition 220 if (!Validator.validateSpringEL(getCondition())) { 221 String currentValues[] = {"condition =" + getCondition()}; 222 tracer.createError("Invalid Spring Expression Language", currentValues); 223 } 224 } 225 }