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    }