001 /**
002 * Copyright 2005-2013 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.parse.BeanTag;
019 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
020 import org.kuali.rice.krad.datadictionary.parse.BeanTags;
021 import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
022 import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
023 import org.kuali.rice.krad.datadictionary.validator.Validator;
024
025 import java.io.Serializable;
026 import java.util.ArrayList;
027 import java.util.List;
028 import java.util.Map;
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 @BeanTags({@BeanTag(name = "propertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer"),
072 @BeanTag(name = "conditionalBeanPropertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer")})
073 public class PropertyReplacer extends UifDictionaryBeanBase implements Serializable {
074 private static final long serialVersionUID = -8405429643299461398L;
075
076 private String propertyName;
077
078 @KeepExpression
079 private String condition;
080 private Object replacement;
081
082 public PropertyReplacer() {
083 super();
084 }
085
086 /**
087 * Returns a list of nested components
088 *
089 * <p>
090 * All nested components will be returned in the list. Current assumption is that
091 * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or
092 * <code>Map</code> for nested components
093 * </p>
094 *
095 * @return nested components
096 */
097 public List<Component> getNestedComponents() {
098 ArrayList<Component> nestedComponents = new ArrayList<Component>();
099
100 if (replacement instanceof Component) {
101 nestedComponents.add(((Component) replacement));
102 } else if (replacement instanceof List) {
103 for (Object replacementItem : (List<?>) replacement) {
104 if (replacementItem instanceof Component) {
105 nestedComponents.add((Component) replacementItem);
106 }
107 }
108 } else if (replacement instanceof Map) {
109 for (Object replacementItem : ((Map<?, ?>) replacement).values()) {
110 if (replacementItem instanceof Component) {
111 nestedComponents.add((Component) replacementItem);
112 }
113 }
114 }
115
116 return nestedComponents;
117 }
118
119 /**
120 * Name of the property on the Component the property replacer is associated with that
121 * will be set when the condition for the replacer succeeds
122 *
123 * <p>
124 * Note the property name must be readable/writable on the component. The property name may
125 * be nested, and include Map or List references.
126 * </p>
127 *
128 * @return property name to set
129 */
130 @BeanTagAttribute(name = "propertyName")
131 public String getPropertyName() {
132 return this.propertyName;
133 }
134
135 /**
136 * Setter for the property name that will be set
137 *
138 * @param propertyName
139 */
140 public void setPropertyName(String propertyName) {
141 this.propertyName = propertyName;
142 }
143
144 /**
145 * Gives the expression that should be evaluated to determine whether or not
146 * the property replacement should be made
147 *
148 * <p>
149 * Expression follows SPEL and may access any model data along with any variables
150 * available in the context for the Component. The expression should evaluate to
151 * a boolean. If the resulting boolean is true, the object given by {@link #getReplacement()}
152 * will be set as the value for the associated property on the component. If the resulting
153 * boolean is false, no action will take place
154 * </p>
155 *
156 * <p>
157 * Note the value does not need to contain the expression placeholder @{}
158 * </p>
159 *
160 * @return expression that should be evaluated
161 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService
162 * @see org.kuali.rice.krad.uif.UifConstants.ContextVariableNames
163 */
164 @BeanTagAttribute(name = "condition")
165 public String getCondition() {
166 return this.condition;
167 }
168
169 /**
170 * Setter for the replacement condition
171 *
172 * @param condition
173 */
174 public void setCondition(String condition) {
175 this.condition = condition;
176 }
177
178 /**
179 * Gives the Object that should be used to set the property value if the replacers condition
180 * evaluates to true
181 *
182 * <p>
183 * Note the configured Object must be valid for the type given by the property on the Component. Standard
184 * property editors will be used for setting the property value
185 * </p>
186 *
187 * @return instance to set
188 */
189 @BeanTagAttribute(name = "replacement", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
190 public Object getReplacement() {
191 return this.replacement;
192 }
193
194 /**
195 * Setter for the replacement Object
196 *
197 * @param replacement
198 */
199 public void setReplacement(Object replacement) {
200 this.replacement = replacement;
201 }
202
203 /**
204 * Validates different requirements of component compiling a series of reports detailing information on errors
205 * found in the component. Used by the RiceDictionaryValidator.
206 *
207 * @param tracer record of component's location
208 */
209 public void completeValidation(ValidationTrace tracer) {
210 tracer.addBean("PropertyReplacer", getPropertyName());
211
212 // Checking that required fields are set
213 if (getPropertyName() == null || getCondition() == null || getReplacement() == null) {
214 String currentValues[] = {"propertyName =" + getPropertyName(), "condition =" + getCondition(),
215 "replacement =" + getReplacement()};
216 tracer.createWarning("PropertyName, condition and replacement should be set", currentValues);
217 }
218
219 // Validating Spring EL in condition
220 if (!Validator.validateSpringEL(getCondition())) {
221 String currentValues[] = {"condition =" + getCondition()};
222 tracer.createError("Invalid Spring Expression Language", currentValues);
223 }
224 }
225 }