View Javadoc
1   /**
2    * Copyright 2005-2016 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 }