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