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 }