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 import org.kuali.rice.krad.uif.layout.LayoutManager; 25 import org.kuali.rice.krad.uif.util.CloneUtils; 26 27 import java.io.Serializable; 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.Map; 31 32 /** 33 * Configuration for replacing a property value based on a condition 34 * 35 * <p> 36 * A <code>Component</code> may be configured with one or more <code>PropertyReplacer</code> instances. Each defines 37 * a condition to evaluate during the apply model phase, and if that condition succeeds the property on the component 38 * given by {@link #getPropertyName()}, will be replaced with the value given by {@link #getReplacement()}. Conditions 39 * are defined using an expression language and may reference any variables available in the component's context. 40 * </p> 41 * 42 * <p> 43 * Property replacers can be used to change out an entire Component or List/Map of Components. For example, based on a 44 * condition you might want to display a <code>TextControl</code> or <code>RadioControl</code> for an 45 * <code>InputField</code>. You can define the field with a text control, then include a property replacer as 46 * follows: 47 * <pre> 48 * <bean parent="PropertyReplacer" p:propertyName="control" 49 * p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/> 50 * 51 * </pre> 52 * 53 * Note <code>Component</code> contains a <code>List</code> or property replacers which will be evaluated in the order 54 * contained within the list. So in the above example if we wanted to now add a further condition which sets the 55 * control 56 * to a checkbox, we would just add another property replacer bean. 57 * <pre> 58 * <property name="propertyReplacers"> 59 * <list> 60 * <bean parent="PropertyReplacer" p:propertyName="control" 61 * p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/> 62 * <bean parent="PropertyReplacer" p:propertyName="control" 63 * p:condition="field1 eq '11456'" p:replacement-ref="CheckboxControl"/> 64 * </list> 65 * </property> 66 * </pre> 67 * 68 * Property replacers may be used to substitute primitive properties as well, such as Strings 69 * </p> 70 * 71 * @author Kuali Rice Team (rice.collab@kuali.org) 72 */ 73 @BeanTags({@BeanTag(name = "propertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer"), 74 @BeanTag(name = "conditionalBeanPropertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer")}) 75 public class PropertyReplacer extends UifDictionaryBeanBase implements Serializable { 76 private static final long serialVersionUID = -8405429643299461398L; 77 78 private String propertyName; 79 80 @KeepExpression 81 private String condition; 82 private Object replacement; 83 84 public PropertyReplacer() { 85 super(); 86 } 87 88 /** 89 * Returns a list of nested components 90 * 91 * <p> 92 * All nested components will be returned in the list. Current assumption is that 93 * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or 94 * <code>Map</code> for nested components 95 * </p> 96 * 97 * @return nested components 98 */ 99 public List<Component> getNestedComponents() { 100 ArrayList<Component> nestedComponents = new ArrayList<Component>(); 101 102 if (replacement instanceof Component) { 103 nestedComponents.add(((Component) replacement)); 104 } else if (replacement instanceof List) { 105 for (Object replacementItem : (List<?>) replacement) { 106 if (replacementItem instanceof Component) { 107 nestedComponents.add((Component) replacementItem); 108 } 109 } 110 } else if (replacement instanceof Map) { 111 for (Object replacementItem : ((Map<?, ?>) replacement).values()) { 112 if (replacementItem instanceof Component) { 113 nestedComponents.add((Component) replacementItem); 114 } 115 } 116 } 117 118 return nestedComponents; 119 } 120 121 /** 122 * Name of the property on the Component the property replacer is associated with that 123 * will be set when the condition for the replacer succeeds 124 * 125 * <p> 126 * Note the property name must be readable/writable on the component. The property name may 127 * be nested, and include Map or List references. 128 * </p> 129 * 130 * @return property name to set 131 */ 132 @BeanTagAttribute(name = "propertyName") 133 public String getPropertyName() { 134 return this.propertyName; 135 } 136 137 /** 138 * Setter for the property name that will be set 139 * 140 * @param propertyName 141 */ 142 public void setPropertyName(String propertyName) { 143 this.propertyName = propertyName; 144 } 145 146 /** 147 * Gives the expression that should be evaluated to determine whether or not 148 * the property replacement should be made 149 * 150 * <p> 151 * Expression follows SPEL and may access any model data along with any variables 152 * available in the context for the Component. The expression should evaluate to 153 * a boolean. If the resulting boolean is true, the object given by {@link #getReplacement()} 154 * will be set as the value for the associated property on the component. If the resulting 155 * boolean is false, no action will take place 156 * </p> 157 * 158 * <p> 159 * Note the value does not need to contain the expression placeholder @{} 160 * </p> 161 * 162 * @return expression that should be evaluated 163 * @see org.kuali.rice.krad.uif.view.ExpressionEvaluator 164 * @see org.kuali.rice.krad.uif.UifConstants.ContextVariableNames 165 */ 166 @BeanTagAttribute(name = "condition") 167 public String getCondition() { 168 return this.condition; 169 } 170 171 /** 172 * Setter for the replacement condition 173 * 174 * @param condition 175 */ 176 public void setCondition(String condition) { 177 this.condition = condition; 178 } 179 180 /** 181 * Gives the Object that should be used to set the property value if the replacers condition 182 * evaluates to true 183 * 184 * <p> 185 * Note the configured Object must be valid for the type given by the property on the Component. Standard 186 * property editors will be used for setting the property value 187 * </p> 188 * 189 * @return instance to set 190 */ 191 @BeanTagAttribute(name = "replacement", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 192 public Object getReplacement() { 193 return this.replacement; 194 } 195 196 /** 197 * Setter for the replacement Object 198 * 199 * @param replacement 200 */ 201 public void setReplacement(Object replacement) { 202 this.replacement = replacement; 203 } 204 205 /** 206 * Validates different requirements of component compiling a series of reports detailing information on errors 207 * found in the component. Used by the RiceDictionaryValidator. 208 * 209 * @param tracer record of component's location 210 */ 211 public void completeValidation(ValidationTrace tracer) { 212 tracer.addBean("PropertyReplacer", getPropertyName()); 213 214 // Checking that required fields are set 215 if (getPropertyName() == null || getCondition() == null || getReplacement() == null) { 216 String currentValues[] = {"propertyName =" + getPropertyName(), "condition =" + getCondition(), 217 "replacement =" + getReplacement()}; 218 tracer.createWarning("PropertyName, condition and replacement should be set", currentValues); 219 } 220 221 // Validating Spring EL in condition 222 if (!Validator.validateSpringEL(getCondition())) { 223 String currentValues[] = {"condition =" + getCondition()}; 224 tracer.createError("Invalid Spring Expression Language", currentValues); 225 } 226 } 227 228 /** 229 * Returns a copy of the property replacer. 230 * 231 * @return PropertyReplacer copy of the property replacer 232 */ 233 public <T> T copy() { 234 T copiedClass = null; 235 try { 236 copiedClass = (T)this.getClass().newInstance(); 237 } 238 catch(Exception exception) { 239 throw new RuntimeException(); 240 } 241 242 copyProperties(copiedClass); 243 244 return copiedClass; 245 } 246 247 /** 248 * Copies the properties over for the copy method 249 */ 250 protected <T> void copyProperties(T propertyReplacer) { 251 super.copyProperties(propertyReplacer); 252 253 PropertyReplacer propertyReplacerCopy = ((PropertyReplacer) propertyReplacer); 254 255 propertyReplacerCopy.setCondition(this.condition); 256 propertyReplacerCopy.setPropertyName(this.propertyName); 257 258 if (this.replacement != null) { 259 Object replacementCopy = null; 260 if (this.replacement instanceof Component) { 261 replacementCopy = ((Component) this.replacement).copy(); 262 } else if (this.replacement instanceof LayoutManager) { 263 replacementCopy = ((LayoutManager) this.replacement).copy(); 264 } else { 265 replacementCopy = CloneUtils.deepClone(this.replacement); 266 } 267 268 propertyReplacerCopy.setReplacement(replacementCopy); 269 } 270 } 271 }