1 /**
2 * Copyright 2005-2015 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 java.io.Serializable;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22
23 import org.kuali.rice.krad.datadictionary.parse.BeanTag;
24 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
25 import org.kuali.rice.krad.datadictionary.parse.BeanTags;
26 import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
27 import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
28 import org.kuali.rice.krad.datadictionary.validator.Validator;
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 @BeanTag(name = "propertyReplacer", parent = "Uif-ConditionalBeanPropertyReplacer")
72 public class PropertyReplacer extends UifDictionaryBeanBase {
73 private static final long serialVersionUID = -8405429643299461398L;
74
75 private String propertyName;
76
77 @KeepExpression
78 private String condition;
79 private Object replacement;
80
81 public PropertyReplacer() {
82 super();
83 }
84
85 /**
86 * Returns a list of nested components
87 *
88 * <p>
89 * All nested components will be returned in the list. Current assumption is that
90 * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or
91 * <code>Map</code> for nested components
92 * </p>
93 *
94 * @return nested components
95 */
96 public List<Component> getNestedComponents() {
97 ArrayList<Component> nestedComponents = new ArrayList<Component>();
98
99 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 }