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