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 }