001 /** 002 * Copyright 2005-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.krad.uif.component; 017 018 import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase; 019 import org.kuali.rice.krad.datadictionary.validator.ErrorReport; 020 import org.kuali.rice.krad.datadictionary.validator.RDValidator; 021 import org.kuali.rice.krad.datadictionary.validator.TracerToken; 022 023 import java.io.Serializable; 024 import java.util.ArrayList; 025 import java.util.List; 026 import java.util.Map; 027 028 /** 029 * Configuration for replacing a property value based on a condition 030 * 031 * <p> 032 * A <code>Component</code> may be configured with one or more <code>PropertyReplacer</code> instances. Each defines 033 * a condition to evaluate during the apply model phase, and if that condition succeeds the property on the component 034 * given by {@link #getPropertyName()}, will be replaced with the value given by {@link #getReplacement()}. Conditions 035 * are defined using an expression language and may reference any variables available in the component's context. 036 * </p> 037 * 038 * <p> 039 * Property replacers can be used to change out an entire Component or List/Map of Components. For example, based on a 040 * condition you might want to display a <code>TextControl</code> or <code>RadioControl</code> for an 041 * <code>InputField</code>. You can define the field with a text control, then include a property replacer as 042 * follows: 043 * <pre> 044 <bean parent="PropertyReplacer" p:propertyName="control" 045 p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/> 046 * 047 * </pre> 048 * 049 * Note <code>Component</code> contains a <code>List</code> or property replacers which will be evaluated in the order 050 * contained within the list. So in the above example if we wanted to now add a further condition which sets the control 051 * to a checkbox, we would just add another property replacer bean. 052 * <pre> 053 * <property name="propertyReplacers"> 054 <list> 055 <bean parent="PropertyReplacer" p:propertyName="control" 056 p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/> 057 <bean parent="PropertyReplacer" p:propertyName="control" 058 p:condition="field1 eq '11456'" p:replacement-ref="CheckboxControl"/> 059 * </list> 060 * </property> 061 * </pre> 062 * 063 * Property replacers may be used to substitute primitive properties as well, such as Strings 064 * </p> 065 * 066 * @author Kuali Rice Team (rice.collab@kuali.org) 067 */ 068 public class PropertyReplacer extends UifDictionaryBeanBase implements Serializable { 069 private static final long serialVersionUID = -8405429643299461398L; 070 071 private String propertyName; 072 073 @KeepExpression 074 private String condition; 075 private Object replacement; 076 077 public PropertyReplacer() { 078 super(); 079 } 080 081 /** 082 * Returns a list of nested components 083 * 084 * <p> 085 * All nested components will be returned in the list. Current assumption is that 086 * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or 087 * <code>Map</code> for nested components 088 * </p> 089 * 090 * @return List<Component> nested components 091 */ 092 public List<Component> getNestedComponents() { 093 ArrayList<Component> nestedComponents = new ArrayList<Component>(); 094 095 if (replacement instanceof Component) { 096 nestedComponents.add(((Component) replacement)); 097 } else if (replacement instanceof List) { 098 for (Object replacementItem : (List<?>) replacement) { 099 if (replacementItem instanceof Component) { 100 nestedComponents.add((Component) replacementItem); 101 } 102 } 103 } else if (replacement instanceof Map) { 104 for (Object replacementItem : ((Map<?, ?>) replacement).values()) { 105 if (replacementItem instanceof Component) { 106 nestedComponents.add((Component) replacementItem); 107 } 108 } 109 } 110 111 return nestedComponents; 112 } 113 114 /** 115 * Name of the property on the Component the property replacer is associated with that 116 * will be set when the condition for the replacer succeeds 117 * 118 * <p> 119 * Note the property name must be readable/writable on the component. The property name may 120 * be nested, and include Map or List references. 121 * </p> 122 * 123 * @return String property name to set 124 */ 125 public String getPropertyName() { 126 return this.propertyName; 127 } 128 129 /** 130 * Setter for the property name that will be set 131 * 132 * @param propertyName 133 */ 134 public void setPropertyName(String propertyName) { 135 this.propertyName = propertyName; 136 } 137 138 /** 139 * Gives the expression that should be evaluated to determine whether or not 140 * the property replacement should be made 141 * 142 * <p> 143 * Expression follows SPEL and may access any model data along with any variables 144 * available in the context for the Component. The expression should evaluate to 145 * a boolean. If the resulting boolean is true, the object given by {@link #getReplacement()} 146 * will be set as the value for the associated property on the component. If the resulting 147 * boolean is false, no action will take place 148 * </p> 149 * 150 * <p> 151 * Note the value does not need to contain the expression placeholder @{} 152 * </p> 153 * 154 * @return String expression that should be evaluated 155 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService 156 * @see org.kuali.rice.krad.uif.UifConstants.ContextVariableNames 157 */ 158 public String getCondition() { 159 return this.condition; 160 } 161 162 /** 163 * Setter for the replacement condition 164 * 165 * @param condition 166 */ 167 public void setCondition(String condition) { 168 this.condition = condition; 169 } 170 171 /** 172 * Gives the Object that should be used to set the property value if the replacers condition 173 * evaluates to true 174 * 175 * <p> 176 * Note the configured Object must be valid for the type given by the property on the Component. Standard 177 * property editors will be used for setting the property value 178 * </p> 179 * 180 * @return Object instance to set 181 */ 182 public Object getReplacement() { 183 return this.replacement; 184 } 185 186 /** 187 * Setter for the replacement Object 188 * 189 * @param replacement 190 */ 191 public void setReplacement(Object replacement) { 192 this.replacement = replacement; 193 } 194 195 /** 196 * Validates different requirements of component compiling a series of reports detailing information on errors 197 * found in the component. Used by the RiceDictionaryValidator. 198 * 199 * @param tracer Record of component's location 200 * @param parser Set of tools for parsing the xml files which were used to create the component 201 * @return A list of ErrorReports detailing errors found within the component and referenced within it 202 */ 203 public ArrayList<ErrorReport> completeValidation(TracerToken tracer){ 204 ArrayList<ErrorReport> reports=new ArrayList<ErrorReport>(); 205 tracer.addBean("PropertyReplacer",getPropertyName()); 206 207 // Checking that required fields are set 208 if(getPropertyName()==null || getCondition()==null || getReplacement()==null){ 209 ErrorReport error = new ErrorReport(ErrorReport.WARNING); 210 error.setValidationFailed("PropertyName, condition and replacement should be set"); 211 error.setBeanLocation(tracer.getBeanLocation()); 212 error.addCurrentValue("propertyName ="+getPropertyName()); 213 error.addCurrentValue("condition ="+getCondition()); 214 error.addCurrentValue("replacement ="+getReplacement()); 215 reports.add(error); 216 } 217 218 // Validating Spring EL in condition 219 if(!RDValidator.validateSpringEL(getCondition())){ 220 ErrorReport error = new ErrorReport(ErrorReport.ERROR); 221 error.setValidationFailed("Invalid Spring Expression Language"); 222 error.setBeanLocation(tracer.getBeanLocation()); 223 error.addCurrentValue("condition ="+getCondition()); 224 reports.add(error); 225 } 226 227 return reports; 228 } 229 }