View Javadoc

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 }