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;
019
020import org.apache.commons.lang.StringUtils;
021import org.kuali.rice.krad.datadictionary.parse.BeanTag;
022import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
023import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
024import org.kuali.rice.krad.uif.UifConstants;
025import org.kuali.rice.krad.uif.view.View;
026
027/**
028 * Provides binding configuration for an DataBinding component (attribute or
029 * collection)
030 *
031 * <p>
032 * From the binding configuration the binding path is determined (if not
033 * manually set) and used to set the path in the UI or to get the value from the
034 * model
035 * </p>
036 *
037 * @author Kuali Rice Team (rice.collab@kuali.org)
038 */
039@BeanTag(name = "bindingInfo", parent = "Uif-BindingInfo")
040public class BindingInfo extends UifDictionaryBeanBase implements Serializable {
041    private static final long serialVersionUID = -7389398061672136091L;
042
043    private boolean bindToForm;
044    private boolean bindToMap;
045
046    private String bindingName;
047    private String bindByNamePrefix;
048    private String bindingObjectPath;
049
050    private String collectionPath;
051
052    private String bindingPath;
053
054    public BindingInfo() {
055        super();
056
057        bindToForm = false;
058        bindToMap = false;
059    }
060
061    /**
062     * Sets up some default binding properties based on the view configuration
063     * and the component's property name
064     *
065     * <p>
066     * Sets the bindingName (if not set) to the given property name, and if the
067     * binding object path has not been set uses the default binding object path
068     * setup for the view
069     * </p>
070     *
071     * @param view the view instance the component belongs to
072     * @param propertyName name of the property (relative to the parent object) the component binds to
073     */
074    public void setDefaults(View view, String propertyName) {
075        if (StringUtils.isBlank(bindingName)) {
076            bindingName = propertyName;
077        }
078
079        if (StringUtils.isBlank(bindingObjectPath)) {
080            bindingObjectPath = view.getDefaultBindingObjectPath();
081        }
082    }
083
084    /**
085     * Path to the property on the model the component binds to. Uses standard
086     * dot notation for nested properties. If the binding path was manually set
087     * it will be returned as it is, otherwise the path will be formed by using
088     * the binding object path and the bind prefix
089     *
090     * <p>
091     * e.g. Property name 'foo' on a model would have binding path "foo", while
092     * property name 'name' of the nested model property 'account' would have
093     * binding path "account.name"
094     * </p>
095     *
096     * @return binding path
097     */
098    @BeanTagAttribute
099    public String getBindingPath() {
100        if (StringUtils.isNotBlank(bindingPath)) {
101            return bindingPath;
102        }
103
104        String formedBindingPath = getBindingPathPrefix();
105
106        if (StringUtils.isNotBlank(bindingName)) {
107            if (bindToMap) {
108                formedBindingPath += "[" + bindingName + "]";
109            } else {
110                if (StringUtils.isNotBlank(formedBindingPath)) {
111                    formedBindingPath += ".";
112                }
113                formedBindingPath += bindingName;
114            }
115        }
116
117        return formedBindingPath;
118    }
119
120    /**
121     * Returns the binding prefix string that can be used to setup the binding
122     * on <code>DataBinding</code> components that are children of the component
123     * that contains the <code>BindingInfo</code>. The binding prefix is formed
124     * like the binding path but without including the object path
125     *
126     * @return binding prefix for nested components
127     */
128    public String getBindingPrefixForNested() {
129        String bindingPrefix = "";
130
131        if (StringUtils.isNotBlank(bindByNamePrefix)) {
132            bindingPrefix = bindByNamePrefix;
133        }
134
135        if (bindToMap) {
136            bindingPrefix += "[" + bindingName + "]";
137        } else {
138            if (StringUtils.isNotBlank(bindingPrefix)) {
139                bindingPrefix += ".";
140            }
141            bindingPrefix += bindingName;
142        }
143
144        return bindingPrefix;
145    }
146
147    /**
148     * Returns the binding path that is formed by taking the binding configuration
149     * of this <code>BindingInfo</code> instance with the given property path as the
150     * binding name. This can be used to get the binding path when just a property
151     * name is given that is assumed to be on the same parent object of the field with
152     * the configured binding info
153     *
154     * <p>
155     * Special check is done for org.kuali.rice.krad.uif.UifConstants#NO_BIND_ADJUST_PREFIX prefix
156     * on the property name which indicates the property path is the full path and should
157     * not be adjusted. Also, if the property is prefixed with
158     * org.kuali.rice.krad.uif.UifConstants#FIELD_PATH_BIND_ADJUST_PREFIX, this indicates we should only append the
159     * binding object path
160     * </p>
161     *
162     * @param propertyPath path for property to return full binding path for
163     * @return full binding path
164     */
165    public String getPropertyAdjustedBindingPath(String propertyPath) {
166        if (propertyPath.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
167            propertyPath = StringUtils.removeStart(propertyPath, UifConstants.NO_BIND_ADJUST_PREFIX);
168
169            return propertyPath;
170        }
171
172        if (propertyPath.startsWith(UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX)) {
173            propertyPath = StringUtils.removeStart(propertyPath, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX);
174        }
175
176        String formedBindingPath = getBindingPathPrefix();
177
178        //KULRICE-13096 avoid duplicate property paths
179        if (propertyPath.startsWith(formedBindingPath)) {
180            return propertyPath;
181        }
182
183        if (bindToMap) {
184            formedBindingPath += "[" + propertyPath + "]";
185        } else {
186            if (StringUtils.isNotBlank(formedBindingPath)) {
187                formedBindingPath += ".";
188            }
189            formedBindingPath += propertyPath;
190        }
191
192        return formedBindingPath;
193    }
194
195    /**
196     * Returns the binding path prefix to affix before the binding path or property adjusted binding path.
197     *
198     * @return the binding path prefix to affix before a binding path.
199     */
200    public String getBindingPathPrefix() {
201        String bindingPathPrefix = "";
202
203        if (!bindToForm && StringUtils.isNotBlank(bindingObjectPath)) {
204            bindingPathPrefix = bindingObjectPath;
205        }
206
207        if (StringUtils.isNotBlank(bindByNamePrefix)) {
208            if (!bindByNamePrefix.startsWith("[") && StringUtils.isNotBlank(bindingPathPrefix)) {
209                bindingPathPrefix += ".";
210            }
211
212            bindingPathPrefix += bindByNamePrefix;
213        }
214
215        return bindingPathPrefix;
216    }
217
218    /**
219     * Helper method for adding a path to the binding prefix
220     *
221     * @param bindPrefix path to add
222     */
223    public void addToBindByNamePrefix(String bindPrefix) {
224        if (StringUtils.isNotBlank(bindByNamePrefix) && StringUtils.isNotBlank(bindPrefix)) {
225            bindByNamePrefix += "." + bindPrefix;
226        } else {
227            bindByNamePrefix = bindPrefix;
228        }
229    }
230
231    /**
232     * Setter for the binding path. Can be left blank in which the path will be
233     * determined from the binding configuration
234     *
235     * @param bindingPath
236     */
237    public void setBindingPath(String bindingPath) {
238        this.bindingPath = bindingPath;
239    }
240
241    /**
242     * Indicates whether the component binds directly to the form (that is its
243     * bindingName gives a property available through the form), or whether is
244     * binds through a nested form object. If bindToForm is false, it is assumed
245     * the component binds to the object given by the form property whose path
246     * is configured by bindingObjectPath.
247     *
248     * @return true if component binds directly to form, false if it
249     *         binds to a nested object
250     */
251    @BeanTagAttribute
252    public boolean isBindToForm() {
253        return this.bindToForm;
254    }
255
256    /**
257     * Setter for the bind to form indicator
258     *
259     * @param bindToForm
260     */
261    public void setBindToForm(boolean bindToForm) {
262        this.bindToForm = bindToForm;
263    }
264
265    /**
266     * Gives the name of the property that the component binds to. The name can
267     * be nested but not the full path, just from the parent object or in the
268     * case of binding directly to the form from the form object
269     *
270     * <p>
271     * If blank this will be set from the name field of the component
272     * </p>
273     *
274     * @return name of the bind property
275     */
276    @BeanTagAttribute
277    public String getBindingName() {
278        return this.bindingName;
279    }
280
281    /**
282     * Setter for the bind property name
283     *
284     * @param bindingName
285     */
286    public void setBindingName(String bindingName) {
287        this.bindingName = bindingName;
288    }
289
290    /**
291     * Prefix that will be used to form the binding path from the component
292     * name. Typically used for nested collection properties
293     *
294     * @return binding prefix
295     */
296    @BeanTagAttribute
297    public String getBindByNamePrefix() {
298        return this.bindByNamePrefix;
299    }
300
301    /**
302     * Setter for the prefix to use for forming the binding path by name
303     *
304     * @param bindByNamePrefix
305     */
306    public void setBindByNamePrefix(String bindByNamePrefix) {
307        this.bindByNamePrefix = bindByNamePrefix;
308    }
309
310    /**
311     * If field is part of a collection field, gives path to collection
312     *
313     * <p>
314     * This is used for metadata purposes when getting finding the attribute
315     * definition from the dictionary and is not used in building the final
316     * binding path
317     * </p>
318     *
319     * @return path to collection
320     */
321    public String getCollectionPath() {
322        return this.collectionPath;
323    }
324
325    /**
326     * Setter for the field's collection path (if part of a collection)
327     *
328     * @param collectionPath
329     */
330    public void setCollectionPath(String collectionPath) {
331        this.collectionPath = collectionPath;
332    }
333
334    /**
335     * For attribute fields that do not belong to the default form object (given
336     * by the view), this field specifies the path to the object (on the form)
337     * the attribute does belong to.
338     *
339     * <p>
340     * e.g. Say we have an attribute field with property name 'number', that
341     * belongs to the object given by the 'account' property on the form. The
342     * form object path would therefore be set to 'account'. If the property
343     * belonged to the object given by the 'document.header' property of the
344     * form, the binding object path would be set to 'document.header'. Note if
345     * the binding object path is not set for an attribute field (or any
346     * <code>DataBinding</code> component), the binding object path configured
347     * on the <code>View</code> will be used (unless bindToForm is set to true,
348     * where is assumed the property is directly available from the form).
349     * </p>
350     *
351     * @return path to object from form
352     */
353    @BeanTagAttribute
354    public String getBindingObjectPath() {
355        return this.bindingObjectPath;
356    }
357
358    /**
359     * Setter for the object path on the form
360     *
361     * @param bindingObjectPath
362     */
363    public void setBindingObjectPath(String bindingObjectPath) {
364        this.bindingObjectPath = bindingObjectPath;
365    }
366
367    /**
368     * Indicates whether the parent object for the property that we are binding
369     * to is a Map. If true the binding path will be adjusted to use the map key
370     * syntax
371     *
372     * @return true if the property binds to a map, false if it does not
373     */
374    @BeanTagAttribute
375    public boolean isBindToMap() {
376        return this.bindToMap;
377    }
378
379    /**
380     * Setter for the bind to map indicator
381     *
382     * @param bindToMap
383     */
384    public void setBindToMap(boolean bindToMap) {
385        this.bindToMap = bindToMap;
386    }
387}