View Javadoc

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