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  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Map;
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  @BeanTags({@BeanTag(name = "propertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer"),
72          @BeanTag(name = "conditionalBeanPropertyReplacer-bean", parent = "Uif-ConditionalBeanPropertyReplacer")})
73  public class PropertyReplacer extends UifDictionaryBeanBase implements Serializable {
74      private static final long serialVersionUID = -8405429643299461398L;
75  
76      private String propertyName;
77  
78      @KeepExpression
79      private String condition;
80      private Object replacement;
81  
82      public PropertyReplacer() {
83          super();
84      }
85  
86      /**
87       * Returns a list of nested components
88       *
89       * <p>
90       * All nested components will be returned in the list. Current assumption is that
91       * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or
92       * <code>Map</code> for nested components
93       * </p>
94       *
95       * @return List<Component> nested components
96       */
97      public List<Component> getNestedComponents() {
98          ArrayList<Component> nestedComponents = new ArrayList<Component>();
99  
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 String 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 String 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 Object 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 }