001/**
002 * Copyright 2005-2018 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 */
016package org.kuali.rice.krad.uif.component;
017
018import java.io.Serializable;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Map;
022
023import org.kuali.rice.krad.datadictionary.parse.BeanTag;
024import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
025import org.kuali.rice.krad.datadictionary.parse.BeanTags;
026import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
027import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
028import org.kuali.rice.krad.datadictionary.validator.Validator;
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@BeanTag(name = "propertyReplacer", parent = "Uif-ConditionalBeanPropertyReplacer")
072public class PropertyReplacer extends UifDictionaryBeanBase {
073    private static final long serialVersionUID = -8405429643299461398L;
074
075    private String propertyName;
076
077    @KeepExpression
078    private String condition;
079    private Object replacement;
080
081    public PropertyReplacer() {
082        super();
083    }
084
085    /**
086     * Returns a list of nested components
087     *
088     * <p>
089     * All nested components will be returned in the list. Current assumption is that
090     * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or
091     * <code>Map</code> for nested components
092     * </p>
093     *
094     * @return nested components
095     */
096    public List<Component> getNestedComponents() {
097        ArrayList<Component> nestedComponents = new ArrayList<Component>();
098
099        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}