View Javadoc

1   /**
2    * Copyright 2005-2014 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.field;
17  
18  import org.apache.commons.lang.StringEscapeUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.core.api.util.type.TypeUtils;
22  import org.kuali.rice.krad.bo.DataObjectRelationship;
23  import org.kuali.rice.krad.bo.KualiCode;
24  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
25  import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
26  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
27  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
28  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
29  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
30  import org.kuali.rice.krad.datadictionary.validator.Validator;
31  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
32  import org.kuali.rice.krad.uif.UifConstants;
33  import org.kuali.rice.krad.uif.component.BindingInfo;
34  import org.kuali.rice.krad.uif.component.Component;
35  import org.kuali.rice.krad.uif.component.ComponentSecurity;
36  import org.kuali.rice.krad.uif.component.DataBinding;
37  import org.kuali.rice.krad.uif.component.KeepExpression;
38  import org.kuali.rice.krad.uif.util.ComponentFactory;
39  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
40  import org.kuali.rice.krad.uif.util.ViewModelUtils;
41  import org.kuali.rice.krad.uif.view.View;
42  import org.kuali.rice.krad.uif.widget.Help;
43  import org.kuali.rice.krad.uif.widget.Helpable;
44  import org.kuali.rice.krad.uif.widget.Inquiry;
45  import org.kuali.rice.krad.uif.widget.Tooltip;
46  import org.kuali.rice.krad.util.KRADPropertyConstants;
47  import org.kuali.rice.krad.util.ObjectUtils;
48  import org.kuali.rice.krad.valuefinder.ValueFinder;
49  
50  import java.beans.PropertyEditor;
51  import java.util.ArrayList;
52  import java.util.List;
53  
54  /**
55   * Field that renders data from the application, such as the value of a data object property
56   *
57   * @author Kuali Rice Team (rice.collab@kuali.org)
58   */
59  @BeanTags({@BeanTag(name = "dataField-bean", parent = "Uif-DataField"),
60          @BeanTag(name = "dataField-labelTop-bean", parent = "Uif-DataField-LabelTop"),
61          @BeanTag(name = "dataField-labelRight-bean", parent = "Uif-DataField-LabelRight"),
62          @BeanTag(name = "dataField-withoutLabel-bean", parent = "Uif-DataField-withoutLabel")})
63  public class DataField extends FieldBase implements DataBinding, Helpable {
64      private static final long serialVersionUID = -4129678891948564724L;
65  
66      // binding
67      private String propertyName;
68      private BindingInfo bindingInfo;
69  
70      private String dictionaryAttributeName;
71      private String dictionaryObjectEntry;
72  
73      // value props
74      private String defaultValue;
75      private Class<? extends ValueFinder> defaultValueFinderClass;
76      private Object[] defaultValues;
77      private String forcedValue;
78  
79      private PropertyEditor propertyEditor;
80  
81      private boolean addHiddenWhenReadOnly;
82  
83      // read only display properties
84      protected String readOnlyDisplayReplacementPropertyName;
85      protected String readOnlyDisplaySuffixPropertyName;
86  
87      private String readOnlyDisplayReplacement;
88      private String readOnlyDisplaySuffix;
89  
90      private String readOnlyListDisplayType;
91      private String readOnlyListDelimiter;
92  
93      private boolean applyMask;
94      private MaskFormatter maskFormatter;
95  
96      private List<String> additionalHiddenPropertyNames;
97      private List<String> propertyNamesForAdditionalDisplay;
98  
99      private boolean escapeHtmlInPropertyValue;
100     private boolean multiLineReadOnlyDisplay;
101 
102     // widgets
103     private Inquiry inquiry;
104     private boolean enableAutoInquiry;
105 
106     private Help help;
107 
108     public DataField() {
109         super();
110 
111         enableAutoInquiry = true;
112         escapeHtmlInPropertyValue = true;
113 
114         additionalHiddenPropertyNames = new ArrayList<String>();
115         propertyNamesForAdditionalDisplay = new ArrayList<String>();
116     }
117 
118     /**
119      * The following initialization is performed:
120      *
121      * <ul>
122      * <li>Set defaults for binding</li>
123      * <li>Default the model path if not set</li>
124      * </ul>
125      *
126      * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View,
127      *      java.lang.Object)
128      */
129     @Override
130     public void performInitialization(View view, Object model) {
131         super.performInitialization(view, model);
132 
133         if (bindingInfo != null) {
134             bindingInfo.setDefaults(view, getPropertyName());
135         }
136     }
137 
138     /**
139      * The following updates are done here:
140      *
141      * <ul>
142      * <li>If readOnlyHidden set to true, set field to readonly and add to hidden property names</li>
143      * </ul>
144      */
145     public void performApplyModel(View view, Object model, Component parent) {
146         super.performApplyModel(view, model, parent);
147 
148         if (this.enableAutoInquiry && (this.inquiry == null) && isReadOnly()) {
149             buildAutomaticInquiry(view, model, false);
150         }
151 
152         if (isAddHiddenWhenReadOnly()) {
153             setReadOnly(true);
154             getAdditionalHiddenPropertyNames().add(getPropertyName());
155         }
156     }
157 
158     /**
159      * The following actions are performed:
160      *
161      * <ul>
162      * <li>Set the ids for the various attribute components</li>
163      * <li>Sets up the client side validation for constraints on this field. In
164      * addition, it sets up the messages applied to this field</li>
165      * <li>If this field is of type list and readOnly, generates the appropriate readOnly output.  Handles other
166      * readOnlyReplacement cases, as well.</li>
167      * </ul>
168      *
169      * @see org.kuali.rice.krad.uif.component.ComponentBase#performFinalize(org.kuali.rice.krad.uif.view.View,
170      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
171      */
172     @Override
173     public void performFinalize(View view, Object model, Component parent) {
174         super.performFinalize(view, model, parent);
175 
176         // adjust the path for hidden fields
177         // TODO: should this check the view#readOnly?
178         List<String> hiddenPropertyPaths = new ArrayList<String>();
179         for (String hiddenPropertyName : getAdditionalHiddenPropertyNames()) {
180             String hiddenPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(hiddenPropertyName);
181             hiddenPropertyPaths.add(hiddenPropertyPath);
182         }
183         this.additionalHiddenPropertyNames = hiddenPropertyPaths;
184 
185         // adjust paths on informational property names
186         List<String> informationalPropertyPaths = new ArrayList<String>();
187         for (String infoPropertyName : getPropertyNamesForAdditionalDisplay()) {
188             String infoPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(infoPropertyName);
189             informationalPropertyPaths.add(infoPropertyPath);
190         }
191         this.propertyNamesForAdditionalDisplay = informationalPropertyPaths;
192 
193         //Special processing for List<?> readOnly
194         Class<?> type = ObjectPropertyUtils.getPropertyType(model, getBindingInfo().getBindingPath());
195         if (this.isReadOnly() && type != null && List.class.isAssignableFrom(type) && StringUtils.isBlank(
196                 getReadOnlyDisplayReplacement()) && StringUtils.isBlank(getReadOnlyDisplayReplacementPropertyName())) {
197             //get the list
198             Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
199 
200             //check for null, empty or non-simple type content (not supported by DataField)
201             if (fieldValue != null && fieldValue instanceof List<?> && !((List) fieldValue).isEmpty()) {
202                 List<?> list = (List<?>) fieldValue;
203                 processReadOnlyListDisplay(model, list);
204             } else {
205                 this.setReadOnlyDisplayReplacement("&nbsp;");
206             }
207 
208         } else {
209             // Additional and Alternate display value
210             setAlternateAndAdditionalDisplayValue(view, model);
211         }
212 
213         if (this.getFieldLabel() != null && StringUtils.isNotBlank(this.getId())) {
214             this.getFieldLabel().setLabelForComponentId(this.getId() + UifConstants.IdSuffixes.CONTROL);
215         }
216     }
217 
218     /**
219      * Creates a new {@link org.kuali.rice.krad.uif.widget.Inquiry} and then invokes the lifecycle process for
220      * the inquiry to determine if a relationship was found, if so the inquiry is assigned to the field
221      *
222      * @param view view instance being processed
223      * @param model object containing the view data
224      * @param enableDirectInquiry whether direct inquiry should be enabled if an inquiry is found
225      */
226     protected void buildAutomaticInquiry(View view, Object model, boolean enableDirectInquiry) {
227         Inquiry autoInquiry = ComponentFactory.getInquiry();
228 
229         view.getViewHelperService().spawnSubLifecyle(view, model, autoInquiry, this, null, null);
230 
231         // if render flag is true, that means the inquiry was able to find a relationship
232         if (autoInquiry.isRender()) {
233             this.inquiry = autoInquiry;
234         }
235     }
236 
237     /**
238      * This method is called when the list is readOnly as determined in DataField's performFinalize method.  This
239      * method
240      * should be overridden to perform any additional processing to the values before calling
241      * generateReadOnlyListDisplayReplacement.  The default implementation calls it directly with the originalList.
242      *
243      * @param model the model
244      * @param originalList originalList of values
245      */
246     protected void processReadOnlyListDisplay(Object model, List<?> originalList) {
247         this.setReadOnlyDisplayReplacement(generateReadOnlyListDisplayReplacement(originalList));
248     }
249 
250     /**
251      * Generates the html to be used and sets the readOnlyDisplayReplacement for DataFields that contain lists and
252      * do not have their own readOnlyDisplayReplacement defined.  The type of html generated is based on the options
253      * set on readOnlyListDisplayType and readOnlyListDelimiter.
254      *
255      * @param list the list to be converted to readOnly html
256      */
257     protected String generateReadOnlyListDisplayReplacement(List<?> list) {
258         String generatedHtml = "";
259 
260         //Default to delimited if nothing is set
261         if (getReadOnlyListDisplayType() == null) {
262             this.setReadOnlyListDisplayType(UifConstants.ReadOnlyListTypes.DELIMITED.name());
263         }
264 
265         //begin generation setup
266         if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.UL.name())) {
267             generatedHtml = "<ul class='uif-readOnlyStringList'>";
268         } else if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.OL.name())) {
269             generatedHtml = "<ol class='uif-readOnlyStringList'>";
270         } else if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.BREAK.name())) {
271             setReadOnlyListDelimiter("<br/>");
272         } else if (this.getReadOnlyListDelimiter() == null) {
273             setReadOnlyListDelimiter(", ");
274         }
275 
276         //iterate through each value
277         for (Object value : list) {
278             //if blank skip
279             if (!TypeUtils.isSimpleType(value.getClass()) || StringUtils.isBlank(value.toString())) {
280                 continue;
281             }
282 
283             //handle mask if any
284             if (isApplyMask()) {
285                 value = getMaskFormatter().maskValue(value);
286             }
287 
288             //TODO the value should use the formatted text property value we would expect to see instead of toString
289             //two types - delimited and html list
290             if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.UL.name())
291                     || getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.OL.name())) {
292                 generatedHtml = generatedHtml + "<li>" + StringEscapeUtils.escapeHtml(value.toString()) + "</li>";
293             } else {
294                 //no matching needed - delimited is always the fallback and break uses same logic
295                 generatedHtml = generatedHtml + StringEscapeUtils.escapeHtml(value.toString())
296                         + this.getReadOnlyListDelimiter();
297             }
298         }
299 
300         //end the generation
301         if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.UL.name())) {
302             generatedHtml = generatedHtml + "</ul>";
303         } else if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.OL.name())) {
304             generatedHtml = generatedHtml + "</ol>";
305         } else {
306             generatedHtml = StringUtils.removeEnd(generatedHtml, this.getReadOnlyListDelimiter());
307         }
308 
309         if (StringUtils.isNotBlank(generatedHtml)) {
310             return generatedHtml;
311         } else {
312             //this must be done or the ftl will skip and throw error
313             return "&nbsp;";
314         }
315     }
316 
317     /**
318      * Sets alternate and additional property value for this field.
319      *
320      * <p>
321      * If <code>AttributeSecurity</code> present in this field, make sure the current user has permission to view the
322      * field value. If user doesn't have permission to view the value, mask the value as configured and set it
323      * as alternate value for display. If security doesn't exists for this field but
324      * <code>alternateDisplayPropertyName</code> present, get its value and format it based on that
325      * fields formatting and set for display.
326      * </p>
327      *
328      * <p>
329      * For additional display value, if <code>AttributeSecurity</code> not present, sets the value if
330      * <code>additionalDisplayPropertyName</code> present. If not present, check whether this field is a
331      * <code>KualiCode</code> and get the relationship configured in the datadictionary file and set the name
332      * additional display value which will be displayed along with the code. If additional display property not
333      * present, check whether this field is has <code>MultiValueControlBase</code>. If yes, get the Label
334      * for the value and set it as additional display value.
335      * </p>
336      *
337      * @param view the current view instance
338      * @param model model instance
339      */
340     protected void setAlternateAndAdditionalDisplayValue(View view, Object model) {
341         // if alternate or additional display values set don't use property names
342         if (StringUtils.isNotBlank(readOnlyDisplayReplacement) || StringUtils.isNotBlank(readOnlyDisplaySuffix)) {
343             return;
344         }
345 
346         // check whether field value needs to be masked, and if so apply masking as alternateDisplayValue
347         if (isApplyMask()) {
348             Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
349             readOnlyDisplayReplacement = getMaskFormatter().maskValue(fieldValue);
350 
351             return;
352         }
353 
354         // if not read only, return without trying to set alternate and additional values
355         if (!isReadOnly()) {
356             return;
357         }
358 
359         // if field is not secure, check for alternate and additional display properties
360         if (StringUtils.isNotBlank(getReadOnlyDisplayReplacementPropertyName())) {
361             String alternateDisplayPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(
362                     getReadOnlyDisplayReplacementPropertyName());
363 
364             Object alternateFieldValue = ObjectPropertyUtils.getPropertyValue(model, alternateDisplayPropertyPath);
365             if (alternateFieldValue != null) {
366                 // TODO: format by type
367                 readOnlyDisplayReplacement = alternateFieldValue.toString();
368             }
369         }
370 
371         // perform automatic translation for code references if enabled on view
372         if (StringUtils.isBlank(getReadOnlyDisplaySuffixPropertyName()) && view.isTranslateCodesOnReadOnlyDisplay()) {
373             // check for any relationship present for this field and it's of type KualiCode
374             Class<?> parentObjectClass = ViewModelUtils.getParentObjectClassForMetadata(view, model, this);
375             DataObjectRelationship relationship =
376                     KRADServiceLocatorWeb.getDataObjectMetaDataService().getDataObjectRelationship(null,
377                             parentObjectClass, getBindingInfo().getBindingName(), "", true, false, false);
378 
379             if (relationship != null
380                     && getPropertyName().startsWith(relationship.getParentAttributeName())
381                     && KualiCode.class.isAssignableFrom(relationship.getRelatedClass())) {
382                 readOnlyDisplaySuffixPropertyName =
383                         relationship.getParentAttributeName() + "." + KRADPropertyConstants.NAME;
384             }
385         }
386 
387         // now check for an additional display property and if set get the value
388         if (StringUtils.isNotBlank(getReadOnlyDisplaySuffixPropertyName())) {
389             String additionalDisplayPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(
390                     getReadOnlyDisplaySuffixPropertyName());
391 
392             Object additionalFieldValue = ObjectPropertyUtils.getPropertyValue(model, additionalDisplayPropertyPath);
393             if (additionalFieldValue != null) {
394                 // TODO: format by type
395                 readOnlyDisplaySuffix = additionalFieldValue.toString();
396             }
397         }
398     }
399 
400     /**
401      * Defaults the properties of the <code>DataField</code> to the
402      * corresponding properties of its <code>AttributeDefinition</code>
403      * retrieved from the dictionary (if such an entry exists). If the field
404      * already contains a value for a property, the definitions value is not
405      * used.
406      *
407      * @param view view instance the field belongs to
408      * @param attributeDefinition AttributeDefinition instance the property values should be
409      * copied from
410      */
411     public void copyFromAttributeDefinition(View view, AttributeDefinition attributeDefinition) {
412         // label
413         if (StringUtils.isEmpty(getLabel())) {
414             setLabel(attributeDefinition.getLabel());
415         }
416 
417         // short label
418         if (StringUtils.isEmpty(getShortLabel())) {
419             setShortLabel(attributeDefinition.getShortLabel());
420         }
421 
422         // security
423         if (getDataFieldSecurity().getAttributeSecurity() == null) {
424             getDataFieldSecurity().setAttributeSecurity(attributeDefinition.getAttributeSecurity());
425         }
426 
427         // alternate property name
428         if (getReadOnlyDisplayReplacementPropertyName() == null && StringUtils.isNotBlank(
429                 attributeDefinition.getAlternateDisplayAttributeName())) {
430             setReadOnlyDisplayReplacementPropertyName(attributeDefinition.getAlternateDisplayAttributeName());
431         }
432 
433         // additional property display name
434         if (getReadOnlyDisplaySuffixPropertyName() == null && StringUtils.isNotBlank(
435                 attributeDefinition.getAdditionalDisplayAttributeName())) {
436             setReadOnlyDisplaySuffixPropertyName(attributeDefinition.getAdditionalDisplayAttributeName());
437         }
438 
439         // property editor
440         if (getPropertyEditor() == null) {
441             setPropertyEditor(attributeDefinition.getPropertyEditor());
442         }
443     }
444 
445     /**
446      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
447      */
448     @Override
449     public List<Component> getComponentsForLifecycle() {
450         List<Component> components = super.getComponentsForLifecycle();
451 
452         components.add(inquiry);
453         components.add(help);
454 
455         return components;
456     }
457 
458     /**
459      * Indicates whether the data field instance allows input, subclasses should override and set to
460      * true if input is allowed
461      *
462      * @return true if input is allowed, false if read only
463      */
464     public boolean isInputAllowed() {
465         return false;
466     }
467 
468     /**
469      * @see org.kuali.rice.krad.uif.component.DataBinding#getPropertyName()
470      */
471     @BeanTagAttribute(name = "propertyName")
472     public String getPropertyName() {
473         return this.propertyName;
474     }
475 
476     /**
477      * Setter for the component's property name
478      *
479      * @param propertyName
480      */
481     public void setPropertyName(String propertyName) {
482         this.propertyName = propertyName;
483     }
484 
485     /**
486      * Performs formatting of the field value for display and then converting the value back to its
487      * expected type from a string
488      *
489      * <p>
490      * Note property editors exist and are already registered for the basic Java types and the
491      * common Kuali types such as [@link KualiDecimal}. Registration with this property is only
492      * needed for custom property editors
493      * </p>
494      *
495      * @return PropertyEditor property editor instance to use for this field
496      */
497     @BeanTagAttribute(name = "propertyEditor", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
498     public PropertyEditor getPropertyEditor() {
499         return propertyEditor;
500     }
501 
502     /**
503      * Setter for the custom property editor to use for the field
504      *
505      * @param propertyEditor
506      */
507     public void setPropertyEditor(PropertyEditor propertyEditor) {
508         this.propertyEditor = propertyEditor;
509     }
510 
511     /**
512      * Convenience setter for configuring a property editor by class
513      *
514      * @param propertyEditorClass
515      */
516     public void setPropertyEditorClass(Class<? extends PropertyEditor> propertyEditorClass) {
517         this.propertyEditor = ObjectUtils.newInstance(propertyEditorClass);
518     }
519 
520     /**
521      * @see org.kuali.rice.krad.uif.component.DataBinding#getBindingInfo()
522      */
523     @BeanTagAttribute(name = "bindingInfo", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
524     public BindingInfo getBindingInfo() {
525         return this.bindingInfo;
526     }
527 
528     /**
529      * Setter for the field's binding info
530      *
531      * @param bindingInfo
532      */
533     public void setBindingInfo(BindingInfo bindingInfo) {
534         this.bindingInfo = bindingInfo;
535     }
536 
537     /**
538      * Returns the full binding path (the path used in the name attribute of the input).
539      * This differs from propertyName in that it uses BindingInfo to determine the path.
540      *
541      * @return full binding path name
542      */
543     public String getName() {
544         return this.getBindingInfo().getBindingPath();
545     }
546 
547     /**
548      * Name of the attribute within the data dictionary the attribute field is
549      * associated with
550      *
551      * <p>
552      * During the initialize phase for the <code>View</code>, properties for
553      * attribute fields are defaulted from a corresponding
554      * <code>AttributeDefinition</code> in the data dictionary. Based on the
555      * propertyName and parent object class the framework attempts will
556      * determine the attribute definition that is associated with the field and
557      * set this property. However this property can also be set in the fields
558      * configuration to use another dictionary attribute.
559      * </p>
560      *
561      * <p>
562      * The attribute name is used along with the dictionary object entry to find
563      * the <code>AttributeDefinition</code>
564      * </p>
565      *
566      * @return attribute name
567      */
568     @BeanTagAttribute(name = "dictionaryAttributeName")
569     public String getDictionaryAttributeName() {
570         return this.dictionaryAttributeName;
571     }
572 
573     /**
574      * Setter for the dictionary attribute name
575      *
576      * @param dictionaryAttributeName
577      */
578     public void setDictionaryAttributeName(String dictionaryAttributeName) {
579         this.dictionaryAttributeName = dictionaryAttributeName;
580     }
581 
582     /**
583      * Object entry name in the data dictionary the associated attribute is
584      * apart of
585      *
586      * <p>
587      * During the initialize phase for the <code>View</code>, properties for
588      * attribute fields are defaulted from a corresponding
589      * <code>AttributeDefinition</code> in the data dictionary. Based on the
590      * parent object class the framework will determine the object entry for the
591      * associated attribute. However the object entry can be set in the field's
592      * configuration to use another object entry for the attribute
593      * </p>
594      *
595      * <p>
596      * The attribute name is used along with the dictionary object entry to find
597      * the <code>AttributeDefinition</code>
598      * </p>
599      *
600      * @return String
601      */
602     @BeanTagAttribute(name = "dictionaryObjectEntry")
603     public String getDictionaryObjectEntry() {
604         return this.dictionaryObjectEntry;
605     }
606 
607     /**
608      * Setter for the dictionary object entry
609      *
610      * @param dictionaryObjectEntry
611      */
612     public void setDictionaryObjectEntry(String dictionaryObjectEntry) {
613         this.dictionaryObjectEntry = dictionaryObjectEntry;
614     }
615 
616     /**
617      * Default value for the model property the field points to
618      *
619      * <p>
620      * When a new <code>View</code> instance is requested, the corresponding
621      * model will be newly created. During this initialization process the value
622      * for the model property will be set to the given default value, if it was null.
623      * This will only work on properties which can be determined to be null.
624      * Therefore a String property with an empty string value will
625      * not be ovewritten with the defaultValue set here.
626      * </p>
627      *
628      * <p>
629      * In addition, int, boolean, and other primitive types
630      * will not use this default value because they inherently have a value in Java (0 for int, false for boolean, etc).
631      * To use such types either using a primitive wrapper type (Integer, Boolean, etc) so an unset variable can
632      * be determined to be null, or explicitly set the default value on the form/object itself for these types and
633      * not through this property.
634      * </p>
635      *
636      * @return default value
637      */
638     @BeanTagAttribute(name = "defaultValue")
639     public String getDefaultValue() {
640         return this.defaultValue;
641     }
642 
643     /**
644      * Setter for the fields default value
645      *
646      * @param defaultValue
647      */
648     public void setDefaultValue(String defaultValue) {
649         this.defaultValue = defaultValue;
650     }
651 
652     /**
653      * Gives Class that should be invoked to produce the default value for the
654      * field
655      *
656      * @return default value finder class
657      */
658     @BeanTagAttribute(name = "defaultValueFinderClass")
659     public Class<? extends ValueFinder> getDefaultValueFinderClass() {
660         return this.defaultValueFinderClass;
661     }
662 
663     /**
664      * Setter for the default value finder class
665      *
666      * @param defaultValueFinderClass
667      */
668     public void setDefaultValueFinderClass(Class<? extends ValueFinder> defaultValueFinderClass) {
669         this.defaultValueFinderClass = defaultValueFinderClass;
670     }
671 
672     /**
673      * Array of default values for the model property the field points to
674      *
675      * <p>
676      * When a new <code>View</code> instance is requested, the corresponding
677      * model will be newly created. During this initialization process the value
678      * for the model property will be set to the given default values (if set)
679      * </p>
680      *
681      * @return default value
682      */
683     @BeanTagAttribute(name = "defaultValues", type = BeanTagAttribute.AttributeType.LISTBEAN)
684     public Object[] getDefaultValues() {
685         return this.defaultValues;
686     }
687 
688     /**
689      * Setter for the fields default values
690      *
691      * @param defaultValues
692      */
693     public void setDefaultValues(Object[] defaultValues) {
694         this.defaultValues = defaultValues;
695     }
696 
697     public String getForcedValue() {
698         return forcedValue;
699     }
700 
701     public void setForcedValue(String forcedValue) {
702         this.forcedValue = forcedValue;
703     }
704 
705     /**
706      * Summary help text for the field
707      *
708      * @return summary help text
709      */
710     @BeanTagAttribute(name = "helpSummary")
711     public String getHelpSummary() {
712         return this.help.getTooltipHelpContent();
713     }
714 
715     /**
716      * Setter for the summary help text
717      *
718      * @param helpSummary
719      */
720     public void setHelpSummary(String helpSummary) {
721         this.help.setTooltipHelpContent(helpSummary);
722     }
723 
724     /**
725      * Data Field Security object that indicates what authorization (permissions) exist for the field
726      *
727      * @return DataFieldSecurity instance
728      */
729     public DataFieldSecurity getDataFieldSecurity() {
730         return (DataFieldSecurity) super.getComponentSecurity();
731     }
732 
733     /**
734      * Override to assert a {@link DataFieldSecurity} instance is set
735      *
736      * @param componentSecurity instance of DataFieldSecurity
737      */
738     @Override
739     public void setComponentSecurity(ComponentSecurity componentSecurity) {
740         if ((componentSecurity != null) && !(componentSecurity instanceof DataFieldSecurity)) {
741             throw new RiceRuntimeException("Component security for DataField should be instance of DataFieldSecurity");
742         }
743 
744         super.setComponentSecurity(componentSecurity);
745     }
746 
747     /**
748      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentSecurityClass()
749      */
750     @Override
751     protected Class<? extends ComponentSecurity> getComponentSecurityClass() {
752         return DataFieldSecurity.class;
753     }
754 
755     /**
756      * Indicates the field should be read-only but also a hidden should be generated for the field
757      *
758      * <p>
759      * Useful for when a value is just displayed but is needed by script
760      * </p>
761      *
762      * @return true if field should be readOnly hidden, false if not
763      */
764     @BeanTagAttribute(name = "addHiddenWhenReadOnly")
765     public boolean isAddHiddenWhenReadOnly() {
766         return addHiddenWhenReadOnly;
767     }
768 
769     /**
770      * Setter for the read-only hidden indicator
771      *
772      * @param addHiddenWhenReadOnly
773      */
774     public void setAddHiddenWhenReadOnly(boolean addHiddenWhenReadOnly) {
775         this.addHiddenWhenReadOnly = addHiddenWhenReadOnly;
776     }
777 
778     /**
779      * Inquiry widget for the field
780      *
781      * <p>
782      * The inquiry widget will render a link for the field value when read-only
783      * that points to the associated inquiry view for the field. The inquiry can
784      * be configured to point to a certain <code>InquiryView</code>, or the
785      * framework will attempt to associate the field with a inquiry based on its
786      * metadata (in particular its relationships in the model)
787      * </p>
788      *
789      * @return Inquiry field inquiry
790      */
791     @BeanTagAttribute(name = "inquiry", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
792     public Inquiry getInquiry() {
793         return this.inquiry;
794     }
795 
796     /**
797      * Setter for the inquiry widget
798      *
799      * @param inquiry
800      */
801     public void setInquiry(Inquiry inquiry) {
802         this.inquiry = inquiry;
803     }
804 
805     /**
806      * Indicates whether inquiries should be automatically set when a relationship for the field's property
807      * is found
808      *
809      * <p>
810      * Note this only applies when the {@link #getInquiry()} widget has not been configured (is null)
811      * and is set to true by default
812      * </p>
813      *
814      * @return true if auto inquiries are enabled, false if not
815      */
816     public boolean isEnableAutoInquiry() {
817         return enableAutoInquiry;
818     }
819 
820     /**
821      * Setter for enabling automatic inquiries
822      *
823      * @param enableAutoInquiry
824      */
825     public void setEnableAutoInquiry(boolean enableAutoInquiry) {
826         this.enableAutoInquiry = enableAutoInquiry;
827     }
828 
829     /**
830      * Help configuration object for the datafield
831      *
832      * <p>
833      * External help information can be configured for the datafield. The
834      * <code>Help</code> object can the configuration for rendering a link to
835      * that help information.
836      * </p>
837      *
838      * @return Help for datafield
839      */
840     @Override
841     @BeanTagAttribute(name = "help", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
842     public Help getHelp() {
843         return this.help;
844     }
845 
846     /**
847      * Setter for the datafield help content
848      *
849      * @param help
850      */
851     @Override
852     public void setHelp(Help help) {
853         this.help = help;
854     }
855 
856     /**
857      * For data fields the help tooltip is placed on the label.
858      *
859      * @see org.kuali.rice.krad.uif.widget.Helpable#setTooltipOfComponent(org.kuali.rice.krad.uif.widget.Tooltip))
860      */
861     @Override
862     @BeanTagAttribute(name = "tooltipOfComponent", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
863     public void setTooltipOfComponent(Tooltip tooltip) {
864         getFieldLabel().setToolTip(tooltip);
865     }
866 
867     /**
868      * Return the field label for the help title
869      *
870      * @return field label
871      * @see org.kuali.rice.krad.uif.widget.Helpable#setTooltipOfComponent(org.kuali.rice.krad.uif.widget.Tooltip)
872      */
873     @Override
874     public String getHelpTitle() {
875         return this.getLabel();
876     }
877 
878     /**
879      * Additional display attribute name, which will be displayed next to the actual field value
880      * when the field is readonly with hyphen in between like PropertyValue - AdditionalPropertyValue
881      *
882      * @param readOnlyDisplaySuffixPropertyName name of the additional display property
883      */
884     public void setReadOnlyDisplaySuffixPropertyName(String readOnlyDisplaySuffixPropertyName) {
885         this.readOnlyDisplaySuffixPropertyName = readOnlyDisplaySuffixPropertyName;
886     }
887 
888     /**
889      * Returns the additional display attribute name to be displayed when the field is readonly
890      *
891      * @return additional display attribute name
892      */
893     @BeanTagAttribute(name = "readOnlyDisplaceSuffixPropertyName")
894     public String getReadOnlyDisplaySuffixPropertyName() {
895         return this.readOnlyDisplaySuffixPropertyName;
896     }
897 
898     /**
899      * Sets the alternate display attribute name to be displayed when the field is readonly.
900      * This properties value will be displayed instead of actual fields value when the field is readonly.
901      *
902      * @param readOnlyDisplayReplacementPropertyName alternate display property name
903      */
904     public void setReadOnlyDisplayReplacementPropertyName(String readOnlyDisplayReplacementPropertyName) {
905         this.readOnlyDisplayReplacementPropertyName = readOnlyDisplayReplacementPropertyName;
906     }
907 
908     /**
909      * Returns the alternate display attribute name to be displayed when the field is readonly.
910      *
911      * @return alternate Display Property Name
912      */
913     @BeanTagAttribute(name = "readOnlyDisplayReplacementPropertyName")
914     public String getReadOnlyDisplayReplacementPropertyName() {
915         return this.readOnlyDisplayReplacementPropertyName;
916     }
917 
918     /**
919      * Returns the alternate display value
920      *
921      * @return the alternate display value set for this field
922      */
923     @BeanTagAttribute(name = "readOnlyDisplayReplacement")
924     public String getReadOnlyDisplayReplacement() {
925         return readOnlyDisplayReplacement;
926     }
927 
928     /**
929      * Setter for the alternative display value
930      *
931      * @param value
932      */
933     public void setReadOnlyDisplayReplacement(String value) {
934         this.readOnlyDisplayReplacement = value;
935     }
936 
937     /**
938      * Returns the additional display value.
939      *
940      * @return the additional display value set for this field
941      */
942     @BeanTagAttribute(name = "readOnlyDispalySuffix")
943     public String getReadOnlyDisplaySuffix() {
944         return readOnlyDisplaySuffix;
945     }
946 
947     /**
948      * Setter for the additional display value
949      *
950      * @param value
951      */
952     public void setReadOnlyDisplaySuffix(String value) {
953         this.readOnlyDisplaySuffix = value;
954     }
955 
956     /**
957      * Gets the readOnlyListDisplayType.
958      *
959      * <p>When this is not set, the list will default to the delimited list display with a default of comma and space
960      * (", ") - if readOnlyListDelimiter is not set as well.  The type can be set as the following:
961      * <ul>
962      * <li>"DELIMITED" - list will be output with delimiters between each item defined by readOnlyListDelimiter</li>
963      * <li>"BREAK" - list will be output with breaks between each item</li>
964      * <li>"OL" - list will be output in ordered list format (numbered)</li>
965      * <li>"UL" - list will be output in unordered list format (bulleted)</li>
966      * </ul>
967      * </p>
968      *
969      * @return the display type to use
970      */
971     public String getReadOnlyListDisplayType() {
972         return readOnlyListDisplayType;
973     }
974 
975     /**
976      * Set the readOnlyListDisplayType
977      *
978      * @param readOnlyListDisplayType
979      */
980     public void setReadOnlyListDisplayType(String readOnlyListDisplayType) {
981         this.readOnlyListDisplayType = readOnlyListDisplayType;
982     }
983 
984     /**
985      * The readOnlyListDelimiter is used to set the delimiter used when "DELIMITED" type is set for
986      * readOnlyListDisplayType
987      *
988      * @return the delimiter to use in readOnly list output with "DELIMITED" type set
989      */
990     public String getReadOnlyListDelimiter() {
991         return readOnlyListDelimiter;
992     }
993 
994     /**
995      * Set the readOnlyListDelimiter
996      *
997      * @param readOnlyListDelimiter
998      */
999     public void setReadOnlyListDelimiter(String readOnlyListDelimiter) {
1000         this.readOnlyListDelimiter = readOnlyListDelimiter;
1001     }
1002 
1003     /**
1004      * Indicates whether the value for the field should be masked (or partially masked) on display
1005      *
1006      * <p>
1007      * If set to true, the field value will be masked by applying the configured {@link #getMaskFormatter()}
1008      * </p>
1009      *
1010      * <p>
1011      * If a KIM permission exists that should be checked to determine whether the value should be masked or not,
1012      * this value should not be set but instead the mask or partialMask property on {@link #getComponentSecurity()}
1013      * should be set to true. This indicates there is a mask permission that should be consulted. If the user
1014      * does not have the permission, this flag will be set to true by the framework and the value masked using
1015      * the mask formatter configured on the security object
1016      * </p>
1017      *
1018      * @return true if the field value should be masked, false if not
1019      */
1020     @BeanTagAttribute(name = "applyMask")
1021     public boolean isApplyMask() {
1022         return applyMask;
1023     }
1024 
1025     /**
1026      * Setter for the apply value mask flag
1027      *
1028      * @param applyMask
1029      */
1030     public void setApplyMask(boolean applyMask) {
1031         this.applyMask = applyMask;
1032     }
1033 
1034     /**
1035      * MaskFormatter instance that will be used to mask the field value when {@link #isApplyMask()} is true
1036      *
1037      * <p>
1038      * Note in cases where the mask is applied due to security (KIM permissions), the mask or partial mask formatter
1039      * configured on {@link #getComponentSecurity()} will be used instead of this mask formatter
1040      * </p>
1041      *
1042      * @return MaskFormatter instance
1043      */
1044     @BeanTagAttribute(name = "maskFormatter", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1045     public MaskFormatter getMaskFormatter() {
1046         return maskFormatter;
1047     }
1048 
1049     /**
1050      * Setter for the MaskFormatter instance to apply when the value is masked
1051      *
1052      * @param maskFormatter
1053      */
1054     public void setMaskFormatter(MaskFormatter maskFormatter) {
1055         this.maskFormatter = maskFormatter;
1056     }
1057 
1058     /**
1059      * Allows specifying hidden property names without having to specify as a
1060      * field in the group config (that might impact layout)
1061      *
1062      * @return hidden property names
1063      */
1064     @BeanTagAttribute(name = "additionalHiddenPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE)
1065     public List<String> getAdditionalHiddenPropertyNames() {
1066         return additionalHiddenPropertyNames;
1067     }
1068 
1069     /**
1070      * Setter for the hidden property names
1071      *
1072      * @param additionalHiddenPropertyNames
1073      */
1074     public void setAdditionalHiddenPropertyNames(List<String> additionalHiddenPropertyNames) {
1075         this.additionalHiddenPropertyNames = additionalHiddenPropertyNames;
1076     }
1077 
1078     /**
1079      * List of property names whose values should be displayed read-only under this field
1080      *
1081      * <p>
1082      * In the attribute field template for each information property name given its values is
1083      * outputted read-only. Informational property values can also be updated dynamically with
1084      * the use of field attribute query
1085      * </p>
1086      *
1087      * <p>
1088      * Simple property names can be given if the property has the same binding parent as this
1089      * field, in which case the binding path will be adjusted by the framework. If the property
1090      * names starts with org.kuali.rice.krad.uif.UifConstants#NO_BIND_ADJUST_PREFIX, no binding
1091      * prefix will be added.
1092      * </p>
1093      *
1094      * @return informational property names
1095      */
1096     @BeanTagAttribute(name = "propertyNamesForAdditionalDisplay", type = BeanTagAttribute.AttributeType.LISTVALUE)
1097     public List<String> getPropertyNamesForAdditionalDisplay() {
1098         return propertyNamesForAdditionalDisplay;
1099     }
1100 
1101     /**
1102      * Setter for the list of informational property names
1103      *
1104      * @param propertyNamesForAdditionalDisplay
1105      */
1106     public void setPropertyNamesForAdditionalDisplay(List<String> propertyNamesForAdditionalDisplay) {
1107         this.propertyNamesForAdditionalDisplay = propertyNamesForAdditionalDisplay;
1108     }
1109 
1110     /**
1111      * Sets HTML escaping for this property value. HTML escaping will be handled in alternate and additional fields
1112      * also.
1113      */
1114     public void setEscapeHtmlInPropertyValue(boolean escapeHtmlInPropertyValue) {
1115         this.escapeHtmlInPropertyValue = escapeHtmlInPropertyValue;
1116     }
1117 
1118     /**
1119      * Returns true if HTML escape allowed for this field
1120      *
1121      * @return true if escaping allowed
1122      */
1123     @BeanTagAttribute(name = "escapeHtmlInPropertyValue")
1124     public boolean isEscapeHtmlInPropertyValue() {
1125         return this.escapeHtmlInPropertyValue;
1126     }
1127 
1128     /**
1129      * Returns true if this field is of type {@code TextAreaControl}.
1130      *
1131      * <p>
1132      * Used to preserve text formatting in a textarea when the view
1133      * is readOnly by enclosing the text in a </pre> tag.
1134      * </p>
1135      *
1136      * @return true if the field is of type {@code TextAreaControl}
1137      */
1138     public boolean isMultiLineReadOnlyDisplay() {
1139         return multiLineReadOnlyDisplay;
1140     }
1141 
1142     /**
1143      * Setter for multiLineReadOnlyDisplay
1144      *
1145      * @param multiLineReadOnlyDisplay
1146      */
1147     public void setMultiLineReadOnlyDisplay(boolean multiLineReadOnlyDisplay) {
1148         this.multiLineReadOnlyDisplay = multiLineReadOnlyDisplay;
1149     }
1150 
1151     /**
1152      * Indicates whether the value for the field is secure
1153      *
1154      * <p>
1155      * A value will be secured if masking has been applied (by configuration or a failed KIM permission) or the field
1156      * has been marked as hidden due to an authorization check
1157      * </p>
1158      *
1159      * @return true if value is secure, false if not
1160      */
1161     public boolean hasSecureValue() {
1162         return isApplyMask() || ((getComponentSecurity().isViewAuthz()
1163                 || getDataFieldSecurity().isViewInLineAuthz()
1164                 || ((getDataFieldSecurity().getAttributeSecurity() != null) && getDataFieldSecurity()
1165                 .getAttributeSecurity().isHide())) && isHidden());
1166     }
1167 
1168     public boolean isRenderFieldset() {
1169         return (!this.isReadOnly()
1170                 && inquiry != null
1171                 && inquiry.isRender()
1172                 && inquiry.getInquiryLink() != null
1173                 && inquiry.getInquiryLink().isRender()) || (help != null
1174                 && help.isRender()
1175                 && help.getHelpAction() != null
1176                 && help.getHelpAction().isRender());
1177     }
1178 
1179     /**
1180      * @see org.kuali.rice.krad.uif.component.Component#completeValidation
1181      */
1182     @Override
1183     public void completeValidation(ValidationTrace tracer) {
1184         tracer.addBean(this);
1185 
1186         // Checks that the property is connected to the field
1187         if (getPropertyName() == null) {
1188             if (!Validator.checkExpressions(this, "propertyName")) {
1189                 String currentValues[] = {"propertyName = " + getPropertyName()};
1190                 tracer.createError("Property name not set", currentValues);
1191             }
1192         }
1193 
1194         // Checks that the default values  present
1195 /*        if (getDefaultValue() != null && getDefaultValues() != null) {
1196             String currentValues[] =
1197                     {"defaultValue =" + getDefaultValue(), "defaultValues Size =" + getDefaultValues().length};
1198             tracer.createWarning("Both Default Value and Default Values set", currentValues);
1199         }*/
1200 
1201         // Checks that a mask formatter is set if the data field is to be masked
1202         if (isApplyMask()) {
1203             if (maskFormatter == null) {
1204                 String currentValues[] = {"applyMask =" + isApplyMask(), "maskFormatter =" + maskFormatter};
1205                 tracer.createWarning("Apply mask is true, but no value is set for maskFormatter", currentValues);
1206             }
1207         }
1208 
1209         super.completeValidation(tracer.getCopy());
1210     }
1211 
1212     /**
1213      * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
1214      */
1215     @Override
1216     protected <T> void copyProperties(T component) {
1217         super.copyProperties(component);
1218         DataField dataFieldCopy = (DataField) component;
1219         dataFieldCopy.setAddHiddenWhenReadOnly(this.addHiddenWhenReadOnly);
1220         dataFieldCopy.setAdditionalHiddenPropertyNames(new ArrayList<String>(this.additionalHiddenPropertyNames));
1221         dataFieldCopy.setApplyMask(this.applyMask);
1222 
1223         if (this.maskFormatter != null) {
1224             dataFieldCopy.setMaskFormatter(this.maskFormatter);
1225         }
1226 
1227         if (this.bindingInfo != null) {
1228             dataFieldCopy.setBindingInfo((BindingInfo) this.bindingInfo.copy());
1229         }
1230 
1231         dataFieldCopy.setDefaultValue(this.defaultValue);
1232 
1233         if (this.defaultValues != null) {
1234             dataFieldCopy.setDefaultValues(this.defaultValues);
1235         }
1236 
1237         dataFieldCopy.setDictionaryAttributeName(this.dictionaryAttributeName);
1238         dataFieldCopy.setDictionaryObjectEntry(this.dictionaryObjectEntry);
1239         dataFieldCopy.setEnableAutoInquiry(this.enableAutoInquiry);
1240         dataFieldCopy.setEscapeHtmlInPropertyValue(this.escapeHtmlInPropertyValue);
1241         dataFieldCopy.setForcedValue(this.forcedValue);
1242         dataFieldCopy.setMultiLineReadOnlyDisplay(this.multiLineReadOnlyDisplay);
1243 
1244         if (this.propertyEditor != null) {
1245             dataFieldCopy.setPropertyEditor(this.propertyEditor);
1246         }
1247 
1248         dataFieldCopy.setPropertyName(this.propertyName);
1249 
1250         if (this.propertyNamesForAdditionalDisplay != null) {
1251             dataFieldCopy.setPropertyNamesForAdditionalDisplay(new ArrayList<String>(
1252                     this.propertyNamesForAdditionalDisplay));
1253         }
1254 
1255         dataFieldCopy.setReadOnlyDisplayReplacement(this.readOnlyDisplayReplacement);
1256         dataFieldCopy.setReadOnlyDisplayReplacementPropertyName(this.readOnlyDisplayReplacementPropertyName);
1257         dataFieldCopy.setReadOnlyDisplaySuffix(this.readOnlyDisplaySuffix);
1258         dataFieldCopy.setReadOnlyDisplaySuffixPropertyName(this.readOnlyDisplaySuffixPropertyName);
1259         dataFieldCopy.setReadOnlyListDelimiter(this.readOnlyListDelimiter);
1260         dataFieldCopy.setReadOnlyListDisplayType(this.readOnlyListDisplayType);
1261         dataFieldCopy.setDefaultValueFinderClass(this.defaultValueFinderClass);
1262 
1263         if (this.help != null) {
1264             dataFieldCopy.setHelp((Help) this.help.copy());
1265         }
1266 
1267         if (this.inquiry != null) {
1268             dataFieldCopy.setInquiry((Inquiry) this.inquiry.copy());
1269         }
1270     }
1271 }