View Javadoc
1   /**
2    * Copyright 2005-2016 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.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
21  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
22  import org.kuali.rice.krad.uif.UifConstants;
23  import org.kuali.rice.krad.uif.view.View;
24  import org.kuali.rice.krad.util.ObjectUtils;
25  
26  import java.io.Serializable;
27  
28  /**
29   * Provides binding configuration for an DataBinding component (attribute or
30   * collection)
31   *
32   * <p>
33   * From the binding configuration the binding path is determined (if not
34   * manually set) and used to set the path in the UI or to get the value from the
35   * model
36   * </p>
37   *
38   * @author Kuali Rice Team (rice.collab@kuali.org)
39   */
40  @BeanTag(name = "bindingInfo-bean", parent = "Uif-BindingInfo")
41  public class BindingInfo extends UifDictionaryBeanBase implements Serializable {
42      private static final long serialVersionUID = -7389398061672136091L;
43  
44      private boolean bindToForm;
45      private boolean bindToMap;
46  
47      private String bindingName;
48      private String bindByNamePrefix;
49      private String bindingObjectPath;
50  
51      private String collectionPath;
52  
53      private String bindingPath;
54  
55      public BindingInfo() {
56          super();
57  
58          bindToForm = false;
59          bindToMap = false;
60      }
61  
62      /**
63       * Sets up some default binding properties based on the view configuration
64       * and the component's property name
65       *
66       * <p>
67       * Sets the bindingName (if not set) to the given property name, and if the
68       * binding object path has not been set uses the default binding object path
69       * setup for the view
70       * </p>
71       *
72       * @param view the view instance the component belongs to
73       * @param propertyName name of the property (relative to the parent object) the component binds to
74       */
75      public void setDefaults(View view, String propertyName) {
76          if (StringUtils.isBlank(bindingName)) {
77              bindingName = propertyName;
78          }
79  
80          if (StringUtils.isBlank(bindingObjectPath)) {
81              bindingObjectPath = view.getDefaultBindingObjectPath();
82          }
83      }
84  
85      /**
86       * Path to the property on the model the component binds to. Uses standard
87       * dot notation for nested properties. If the binding path was manually set
88       * it will be returned as it is, otherwise the path will be formed by using
89       * the binding object path and the bind prefix
90       *
91       * <p>
92       * e.g. Property name 'foo' on a model would have binding path "foo", while
93       * property name 'name' of the nested model property 'account' would have
94       * binding path "account.name"
95       * </p>
96       *
97       * @return binding path
98       */
99      @BeanTagAttribute(name = "bindingPath")
100     public String getBindingPath() {
101         if (StringUtils.isNotBlank(bindingPath)) {
102             return bindingPath;
103         }
104 
105         String formedBindingPath = "";
106 
107         if (!bindToForm && StringUtils.isNotBlank(bindingObjectPath)) {
108             formedBindingPath = bindingObjectPath;
109         }
110 
111         if (StringUtils.isNotBlank(bindByNamePrefix)) {
112             if (!bindByNamePrefix.startsWith("[") && StringUtils.isNotBlank(formedBindingPath)) {
113                 formedBindingPath += ".";
114             }
115             formedBindingPath += bindByNamePrefix;
116         }
117 
118         if (StringUtils.isNotBlank(bindingName)) {
119             if (bindToMap) {
120                 formedBindingPath += "[" + bindingName + "]";
121             } else {
122                 if (StringUtils.isNotBlank(formedBindingPath)) {
123                     formedBindingPath += ".";
124                 }
125                 formedBindingPath += bindingName;
126             }
127         }
128 
129         return formedBindingPath;
130     }
131 
132     /**
133      * Returns the binding prefix string that can be used to setup the binding
134      * on <code>DataBinding</code> components that are children of the component
135      * that contains the <code>BindingInfo</code>. The binding prefix is formed
136      * like the binding path but without including the object path
137      *
138      * @return binding prefix for nested components
139      */
140     public String getBindingPrefixForNested() {
141         String bindingPrefix = "";
142 
143         if (StringUtils.isNotBlank(bindByNamePrefix)) {
144             bindingPrefix = bindByNamePrefix;
145         }
146 
147         if (bindToMap) {
148             bindingPrefix += "[" + bindingName + "]";
149         } else {
150             if (StringUtils.isNotBlank(bindingPrefix)) {
151                 bindingPrefix += ".";
152             }
153             bindingPrefix += bindingName;
154         }
155 
156         return bindingPrefix;
157     }
158 
159     /**
160      * Returns the binding path that is formed by taking the binding configuration
161      * of this <code>BindingInfo</code> instance with the given property path as the
162      * binding name. This can be used to get the binding path when just a property
163      * name is given that is assumed to be on the same parent object of the field with
164      * the configured binding info
165      *
166      * <p>
167      * Special check is done for org.kuali.rice.krad.uif.UifConstants#NO_BIND_ADJUST_PREFIX prefix
168      * on the property name which indicates the property path is the full path and should
169      * not be adjusted. Also, if the property is prefixed with
170      * org.kuali.rice.krad.uif.UifConstants#FIELD_PATH_BIND_ADJUST_PREFIX, this indicates we should only append the
171      * binding object path
172      * </p>
173      *
174      * @param propertyPath path for property to return full binding path for
175      * @return full binding path
176      */
177     public String getPropertyAdjustedBindingPath(String propertyPath) {
178         if (propertyPath.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
179             propertyPath = StringUtils.removeStart(propertyPath, UifConstants.NO_BIND_ADJUST_PREFIX);
180 
181             return propertyPath;
182         }
183 
184         if (propertyPath.startsWith(UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX)) {
185             propertyPath = StringUtils.removeStart(propertyPath, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX);
186         }
187 
188         String formedBindingPath = "";
189 
190         if (!bindToForm && StringUtils.isNotBlank(bindingObjectPath)) {
191             formedBindingPath = bindingObjectPath;
192         }
193 
194         if (StringUtils.isNotBlank(bindByNamePrefix)) {
195             if (!bindByNamePrefix.startsWith("[") && StringUtils.isNotBlank(formedBindingPath)) {
196                 formedBindingPath += ".";
197             }
198             formedBindingPath += bindByNamePrefix;
199         }
200 
201         if (bindToMap) {
202             formedBindingPath += "[" + propertyPath + "]";
203         } else {
204             if (StringUtils.isNotBlank(formedBindingPath)) {
205                 formedBindingPath += ".";
206             }
207             formedBindingPath += propertyPath;
208         }
209 
210         return formedBindingPath;
211     }
212 
213     /**
214      * Helper method for adding a path to the binding prefix
215      *
216      * @param bindPrefix path to add
217      */
218     public void addToBindByNamePrefix(String bindPrefix) {
219         if (StringUtils.isNotBlank(bindByNamePrefix) && StringUtils.isNotBlank(bindPrefix)) {
220             bindByNamePrefix += "." + bindPrefix;
221         } else {
222             bindByNamePrefix = bindPrefix;
223         }
224     }
225 
226     /**
227      * Setter for the binding path. Can be left blank in which the path will be
228      * determined from the binding configuration
229      *
230      * @param bindingPath
231      */
232     public void setBindingPath(String bindingPath) {
233         this.bindingPath = bindingPath;
234     }
235 
236     /**
237      * Indicates whether the component binds directly to the form (that is its
238      * bindingName gives a property available through the form), or whether is
239      * binds through a nested form object. If bindToForm is false, it is assumed
240      * the component binds to the object given by the form property whose path
241      * is configured by bindingObjectPath.
242      *
243      * @return true if component binds directly to form, false if it
244      *         binds to a nested object
245      */
246     @BeanTagAttribute(name = "bindToForm")
247     public boolean isBindToForm() {
248         return this.bindToForm;
249     }
250 
251     /**
252      * Setter for the bind to form indicator
253      *
254      * @param bindToForm
255      */
256     public void setBindToForm(boolean bindToForm) {
257         this.bindToForm = bindToForm;
258     }
259 
260     /**
261      * Gives the name of the property that the component binds to. The name can
262      * be nested but not the full path, just from the parent object or in the
263      * case of binding directly to the form from the form object
264      *
265      * <p>
266      * If blank this will be set from the name field of the component
267      * </p>
268      *
269      * @return name of the bind property
270      */
271     @BeanTagAttribute(name = "bindingName")
272     public String getBindingName() {
273         return this.bindingName;
274     }
275 
276     /**
277      * Setter for the bind property name
278      *
279      * @param bindingName
280      */
281     public void setBindingName(String bindingName) {
282         this.bindingName = bindingName;
283     }
284 
285     /**
286      * Prefix that will be used to form the binding path from the component
287      * name. Typically used for nested collection properties
288      *
289      * @return binding prefix
290      */
291     @BeanTagAttribute(name = "bindByNamePrefix")
292     public String getBindByNamePrefix() {
293         return this.bindByNamePrefix;
294     }
295 
296     /**
297      * Setter for the prefix to use for forming the binding path by name
298      *
299      * @param bindByNamePrefix
300      */
301     public void setBindByNamePrefix(String bindByNamePrefix) {
302         this.bindByNamePrefix = bindByNamePrefix;
303     }
304 
305     /**
306      * If field is part of a collection field, gives path to collection
307      *
308      * <p>
309      * This is used for metadata purposes when getting finding the attribute
310      * definition from the dictionary and is not used in building the final
311      * binding path
312      * </p>
313      *
314      * @return path to collection
315      */
316     public String getCollectionPath() {
317         return this.collectionPath;
318     }
319 
320     /**
321      * Setter for the field's collection path (if part of a collection)
322      *
323      * @param collectionPath
324      */
325     public void setCollectionPath(String collectionPath) {
326         this.collectionPath = collectionPath;
327     }
328 
329     /**
330      * For attribute fields that do not belong to the default form object (given
331      * by the view), this field specifies the path to the object (on the form)
332      * the attribute does belong to.
333      *
334      * <p>
335      * e.g. Say we have an attribute field with property name 'number', that
336      * belongs to the object given by the 'account' property on the form. The
337      * form object path would therefore be set to 'account'. If the property
338      * belonged to the object given by the 'document.header' property of the
339      * form, the binding object path would be set to 'document.header'. Note if
340      * the binding object path is not set for an attribute field (or any
341      * <code>DataBinding</code> component), the binding object path configured
342      * on the <code>View</code> will be used (unless bindToForm is set to true,
343      * where is assumed the property is directly available from the form).
344      * </p>
345      *
346      * @return path to object from form
347      */
348     @BeanTagAttribute(name = "bindingObjectPath")
349     public String getBindingObjectPath() {
350         return this.bindingObjectPath;
351     }
352 
353     /**
354      * Setter for the object path on the form
355      *
356      * @param bindingObjectPath
357      */
358     public void setBindingObjectPath(String bindingObjectPath) {
359         this.bindingObjectPath = bindingObjectPath;
360     }
361 
362     /**
363      * Indicates whether the parent object for the property that we are binding
364      * to is a Map. If true the binding path will be adjusted to use the map key
365      * syntax
366      *
367      * @return true if the property binds to a map, false if it does not
368      */
369     @BeanTagAttribute(name = "bindToMap")
370     public boolean isBindToMap() {
371         return this.bindToMap;
372     }
373 
374     /**
375      * Setter for the bind to map indicator
376      *
377      * @param bindToMap
378      */
379     public void setBindToMap(boolean bindToMap) {
380         this.bindToMap = bindToMap;
381     }
382 
383     /**
384      * Returns a clone of the binding info.
385      *
386      * @return BindingInfo clone of the component
387      */
388     public <T> T copy() {
389         T copiedClass = null;
390         try {
391             copiedClass = (T) this.getClass().newInstance();
392         } catch (Exception exception) {
393             throw new RuntimeException();
394         }
395 
396         copyProperties(copiedClass);
397 
398         return copiedClass;
399     }
400 
401     /**
402      * Copies the properties over for the copy method.
403      *
404      * @param bindingInfo The BindingInfo to copy
405      */
406     protected <T> void copyProperties(T bindingInfo) {
407         super.copyProperties(bindingInfo);
408 
409         BindingInfo bindingInfoCopy = (BindingInfo) bindingInfo;
410 
411         bindingInfoCopy.setBindByNamePrefix(this.bindByNamePrefix);
412         bindingInfoCopy.setBindingName(this.bindingName);
413         bindingInfoCopy.setBindingObjectPath(this.bindingObjectPath);
414         bindingInfoCopy.setBindingPath(this.bindingPath);
415         bindingInfoCopy.setBindToForm(this.bindToForm);
416         bindingInfoCopy.setBindToMap(this.bindToMap);
417         bindingInfoCopy.setCollectionPath(this.collectionPath);
418     }
419 }