001/**
002 * Copyright 2005-2015 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.field;
017
018import java.beans.PropertyEditor;
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022
023import org.apache.commons.lang.StringEscapeUtils;
024import org.apache.commons.lang.StringUtils;
025import org.apache.commons.lang3.ArrayUtils;
026import org.kuali.rice.core.api.exception.RiceRuntimeException;
027import org.kuali.rice.core.api.util.type.TypeUtils;
028import org.kuali.rice.krad.bo.DataObjectRelationship;
029import org.kuali.rice.krad.bo.KualiCode;
030import org.kuali.rice.krad.datadictionary.AttributeDefinition;
031import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
032import org.kuali.rice.krad.datadictionary.parse.BeanTag;
033import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
034import org.kuali.rice.krad.datadictionary.parse.BeanTags;
035import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
036import org.kuali.rice.krad.datadictionary.validator.Validator;
037import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
038import org.kuali.rice.krad.uif.UifConstants;
039import org.kuali.rice.krad.uif.component.BindingInfo;
040import org.kuali.rice.krad.uif.component.ComponentSecurity;
041import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
042import org.kuali.rice.krad.uif.lifecycle.ViewPostMetadata;
043import org.kuali.rice.krad.uif.util.ComponentFactory;
044import org.kuali.rice.krad.uif.util.LifecycleAwareList;
045import org.kuali.rice.krad.uif.util.LifecycleElement;
046import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
047import org.kuali.rice.krad.uif.util.ViewModelUtils;
048import org.kuali.rice.krad.uif.view.View;
049import org.kuali.rice.krad.uif.view.ViewModel;
050import org.kuali.rice.krad.uif.widget.Help;
051import org.kuali.rice.krad.uif.widget.Inquiry;
052import org.kuali.rice.krad.uif.widget.Tooltip;
053import org.kuali.rice.krad.util.KRADPropertyConstants;
054import org.kuali.rice.krad.util.KRADUtils;
055import org.kuali.rice.krad.valuefinder.ValueFinder;
056import org.kuali.rice.krad.web.form.InquiryForm;
057
058/**
059 * Field that renders data from the application, such as the value of a data object property
060 *
061 * @author Kuali Rice Team (rice.collab@kuali.org)
062 */
063@BeanTags({@BeanTag(name = "data", parent = "Uif-DataField"),
064        @BeanTag(name = "dataLabelTop", parent = "Uif-DataField-LabelTop"),
065        @BeanTag(name = "dataLabelRight", parent = "Uif-DataField-LabelRight"),
066        @BeanTag(name = "dataNoLabel", parent = "Uif-DataField-withoutLabel")})
067public class DataFieldBase extends FieldBase implements DataField {
068    private static final long serialVersionUID = -4129678891948564724L;
069
070    // binding
071    private String propertyName;
072    private BindingInfo bindingInfo;
073
074    private String dictionaryAttributeName;
075    private String dictionaryObjectEntry;
076
077    // value props
078    private Object defaultValue;
079    private Class<? extends ValueFinder> defaultValueFinderClass;
080    private List<Object> defaultValues;
081    private String forcedValue;
082
083    private PropertyEditor propertyEditor;
084
085    private boolean addHiddenWhenReadOnly;
086
087    // read only display properties
088    protected String readOnlyDisplayReplacementPropertyName;
089    protected String readOnlyDisplaySuffixPropertyName;
090
091    private String readOnlyDisplayReplacement;
092    private String readOnlyDisplaySuffix;
093
094    private String readOnlyListDisplayType;
095    private String readOnlyListDelimiter;
096
097    private boolean applyMask;
098    private MaskFormatter maskFormatter;
099
100    private List<String> additionalHiddenPropertyNames;
101    private List<String> propertyNamesForAdditionalDisplay;
102
103    private boolean escapeHtmlInPropertyValue;
104    private boolean multiLineReadOnlyDisplay;
105
106    // widgets
107    private Inquiry inquiry;
108    private boolean enableAutoInquiry;
109
110    private Help help;
111
112    // Optional span render flags
113    private boolean renderInfoMessageSpan;
114    private boolean renderMarkerIconSpan;
115
116    private String sortAs;
117
118    public DataFieldBase() {
119        super();
120
121        enableAutoInquiry = true;
122        escapeHtmlInPropertyValue = true;
123
124        additionalHiddenPropertyNames = Collections.emptyList();
125        propertyNamesForAdditionalDisplay = Collections.emptyList();
126    }
127
128    /**
129     * {@inheritDoc}
130     */
131    @Override
132    public void performInitialization(Object model) {
133        super.performInitialization(model);
134
135        if (bindingInfo != null) {
136            bindingInfo.setDefaults(ViewLifecycle.getView(), getPropertyName());
137        }
138    }
139    
140    /**
141     * {@inheritDoc}
142     */
143    @Override
144    public void afterEvaluateExpression() {
145        // set to true before calling super.
146        if (getReadOnly() == null) {
147            setReadOnly(true);
148        }
149        
150        super.afterEvaluateExpression();
151    }
152
153    /**
154     * {@inheritDoc}
155     */
156    @Override
157    public void performApplyModel(Object model, LifecycleElement parent) {
158        super.performApplyModel(model, parent);
159
160        if (enableAutoInquiry && this.inquiry == null && Boolean.TRUE.equals(getReadOnly()) && hasAutoInquiryRelationship()) {
161            this.inquiry = ComponentFactory.getInquiry();
162        }
163
164        if (isAddHiddenWhenReadOnly()) {
165            setReadOnly(true);
166            getAdditionalHiddenPropertyNames().add(getPropertyName());
167        }
168    }
169
170    /**
171     * {@inheritDoc}
172     */
173    @Override
174    public void performFinalize(Object model, LifecycleElement parent) {
175        super.performFinalize(model, parent);
176
177        // adjust the path for hidden fields and add as accessible paths
178        List<String> hiddenPropertyPaths = new ArrayList<String>();
179        for (String hiddenPropertyName : getAdditionalHiddenPropertyNames()) {
180            String hiddenPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(hiddenPropertyName);
181            hiddenPropertyPaths.add(hiddenPropertyPath);
182
183            if (isRender() || StringUtils.isNotBlank(getProgressiveRender())) {
184                ViewLifecycle.getViewPostMetadata().addAccessibleBindingPath(hiddenPropertyPath);
185            }
186        }
187        this.additionalHiddenPropertyNames = hiddenPropertyPaths;
188
189        // adjust paths on informational property names
190        List<String> informationalPropertyPaths = new ArrayList<String>();
191        for (String infoPropertyName : getPropertyNamesForAdditionalDisplay()) {
192            String infoPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(infoPropertyName);
193            informationalPropertyPaths.add(infoPropertyPath);
194        }
195        this.propertyNamesForAdditionalDisplay = informationalPropertyPaths;
196
197        // process read-only lists and additional and alternate display values
198        boolean hasPropertyEditor = getPropertyEditor() != null;
199        boolean hasReadOnlyDisplayReplacement = StringUtils.isNotBlank(getReadOnlyDisplayReplacement());
200        boolean hasReadOnlyDisplayReplacementPropertyName = StringUtils.isNotBlank(
201                getReadOnlyDisplayReplacementPropertyName());
202        String bindingPath = getBindingInfo().getBindingPath();
203        Class<?> type = StringUtils.isNotEmpty(bindingPath) ? ObjectPropertyUtils.getPropertyType(model, bindingPath) : null;
204        boolean isReadOnlyList = Boolean.TRUE.equals(getReadOnly()) && type != null && List.class.isAssignableFrom(type);
205
206        if (!hasPropertyEditor && !hasReadOnlyDisplayReplacement && !hasReadOnlyDisplayReplacementPropertyName && isReadOnlyList) {
207            Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, bindingPath);
208            List<?> list = fieldValue != null ? (List<?>) fieldValue : Collections.emptyList();
209
210            processReadOnlyListDisplay(model, list);
211        } else {
212            setAlternateAndAdditionalDisplayValue(ViewLifecycle.getView(), model);
213        }
214
215        if (this.getFieldLabel() != null && StringUtils.isNotBlank(this.getId())) {
216            this.getFieldLabel().setLabelForComponentId(this.getId() + UifConstants.IdSuffixes.CONTROL);
217        }
218
219        if (model instanceof ViewModel) {
220            View view = ViewLifecycle.getView();
221            if(((ViewModel) model).isApplyDefaultValues()) {
222
223                // apply default field values to view
224                view.getViewHelperService().populateDefaultValueForField(model, this,
225                        this.getBindingInfo().getBindingPath());
226
227                // ensure default values are only applied once
228                ((ViewModel) model).setApplyDefaultValues(false);
229            }
230        }
231        
232        ViewPostMetadata viewPostMetadata = ViewLifecycle.getViewPostMetadata();
233        if (isRender() && viewPostMetadata != null) {
234            viewPostMetadata.addRenderedPropertyPath(getBindingInfo().getBindingPath());
235        }
236    }
237
238    /**
239     * This method is called when the list is readOnly as determined in DataField's performFinalize method.  This
240     * method
241     * should be overridden to perform any additional processing to the values before calling
242     * generateReadOnlyListDisplayReplacement.  The default implementation calls it directly with the originalList.
243     *
244     * @param model the model
245     * @param originalList originalList of values
246     */
247    protected void processReadOnlyListDisplay(Object model, List<?> originalList) {
248        this.setReadOnlyDisplayReplacement(generateReadOnlyListDisplayReplacement(originalList));
249    }
250
251    /**
252     * Generates the html to be used and sets the readOnlyDisplayReplacement for DataFields that contain lists and
253     * do not have their own readOnlyDisplayReplacement defined.  The type of html generated is based on the options
254     * set on readOnlyListDisplayType and readOnlyListDelimiter.
255     *
256     * @param list the list to be converted to readOnly html
257     */
258    protected String generateReadOnlyListDisplayReplacement(List<?> list) {
259        //Default to delimited if nothing is set
260        if (getReadOnlyListDisplayType() == null) {
261            this.setReadOnlyListDisplayType(UifConstants.ReadOnlyListTypes.DELIMITED.name());
262        }
263
264        String generatedHtml = "";
265
266        //begin generation setup
267        if (!list.isEmpty()) {
268            if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.UL.name())) {
269                generatedHtml = "<ul class='uif-readOnlyStringList'>";
270            } else if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.OL.name())) {
271                generatedHtml = "<ol class='uif-readOnlyStringList'>";
272            } else if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.BREAK.name())) {
273                setReadOnlyListDelimiter("<br/>");
274            } else if (this.getReadOnlyListDelimiter() == null) {
275                setReadOnlyListDelimiter(", ");
276            }
277        }
278
279        //iterate through each value
280        for (Object value : list) {
281            //if blank skip
282            if (!TypeUtils.isSimpleType(value.getClass()) || StringUtils.isBlank(value.toString())) {
283                continue;
284            }
285
286            //handle mask if any
287            if (isApplyMask()) {
288                value = getMaskFormatter().maskValue(value);
289            }
290
291            //TODO the value should use the formatted text property value we would expect to see instead of toString
292            //two types - delimited and html list
293            if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.UL.name())
294                    || getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.OL.name())) {
295                generatedHtml = generatedHtml + "<li>" + StringEscapeUtils.escapeHtml(value.toString()) + "</li>";
296            } else {
297                //no matching needed - delimited is always the fallback and break uses same logic
298                generatedHtml = generatedHtml + StringEscapeUtils.escapeHtml(value.toString())
299                        + this.getReadOnlyListDelimiter();
300            }
301        }
302
303        //end the generation
304        if (!list.isEmpty()) {
305            if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.UL.name())) {
306                generatedHtml = generatedHtml + "</ul>";
307            } else if (getReadOnlyListDisplayType().equalsIgnoreCase(UifConstants.ReadOnlyListTypes.OL.name())) {
308                generatedHtml = generatedHtml + "</ol>";
309            } else {
310                generatedHtml = StringUtils.removeEnd(generatedHtml, this.getReadOnlyListDelimiter());
311            }
312        }
313
314        if (StringUtils.isBlank(generatedHtml)) {
315            generatedHtml = "&nbsp;";
316        }
317
318        return generatedHtml;
319    }
320
321    /**
322     * Sets alternate and additional property value for this field.
323     *
324     * <p>
325     * If <code>AttributeSecurity</code> present in this field, make sure the current user has permission to view the
326     * field value. If user doesn't have permission to view the value, mask the value as configured and set it
327     * as alternate value for display. If security doesn't exists for this field but
328     * <code>alternateDisplayPropertyName</code> present, get its value and format it based on that
329     * fields formatting and set for display.
330     * </p>
331     *
332     * <p>
333     * For additional display value, if <code>AttributeSecurity</code> not present, sets the value if
334     * <code>additionalDisplayPropertyName</code> present. If not present, check whether this field is a
335     * <code>KualiCode</code> and get the relationship configured in the datadictionary file and set the name
336     * additional display value which will be displayed along with the code. If additional display property not
337     * present, check whether this field is has <code>MultiValueControlBase</code>. If yes, get the Label
338     * for the value and set it as additional display value.
339     * </p>
340     *
341     * @param view the current view instance
342     * @param model model instance
343     */
344    protected void setAlternateAndAdditionalDisplayValue(View view, Object model) {
345        // if alternate or additional display values set don't use property names
346        if (StringUtils.isNotBlank(readOnlyDisplayReplacement) || StringUtils.isNotBlank(readOnlyDisplaySuffix)) {
347            return;
348        }
349
350        // check whether field value needs to be masked, and if so apply masking as alternateDisplayValue
351        if (isApplyMask()) {
352            Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
353            if (getMaskFormatter() != null) {
354                readOnlyDisplayReplacement = getMaskFormatter().maskValue(fieldValue);
355            }
356
357            return;
358        }
359
360        // if not read only, return without trying to set alternate and additional values
361        if (!Boolean.TRUE.equals(getReadOnly())) {
362            return;
363        }
364
365        // if field is not secure, check for alternate and additional display properties
366        if (StringUtils.isNotBlank(getReadOnlyDisplayReplacementPropertyName())) {
367            String alternateDisplayPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(
368                    getReadOnlyDisplayReplacementPropertyName());
369
370            Object alternateFieldValue = ObjectPropertyUtils.getPropertyValue(model, alternateDisplayPropertyPath);
371            if (alternateFieldValue != null) {
372                // TODO: format by type
373                readOnlyDisplayReplacement = alternateFieldValue.toString();
374            }
375        }
376
377        // perform automatic translation for code references if enabled on view
378        if (StringUtils.isBlank(getReadOnlyDisplaySuffixPropertyName()) && view.isTranslateCodesOnReadOnlyDisplay()) {
379            // check for any relationship present for this field and it's of type KualiCode
380            Class<?> parentObjectClass = ViewModelUtils.getParentObjectClassForMetadata(view, model, this);
381            DataObjectRelationship relationship =
382                    KRADServiceLocatorWeb.getLegacyDataAdapter().getDataObjectRelationship(null,
383                            parentObjectClass, getBindingInfo().getBindingName(), "", true, false, false);
384
385            if (relationship != null
386                    && getPropertyName().startsWith(relationship.getParentAttributeName())
387                    && KualiCode.class.isAssignableFrom(relationship.getRelatedClass())) {
388                readOnlyDisplaySuffixPropertyName =
389                        relationship.getParentAttributeName() + "." + KRADPropertyConstants.NAME;
390            }
391        }
392
393        // now check for an additional display property and if set get the value
394        if (StringUtils.isNotBlank(getReadOnlyDisplaySuffixPropertyName())) {
395            String additionalDisplayPropertyPath = getBindingInfo().getPropertyAdjustedBindingPath(
396                    getReadOnlyDisplaySuffixPropertyName());
397
398            Object additionalFieldValue = ObjectPropertyUtils.getPropertyValue(model, additionalDisplayPropertyPath);
399            if (additionalFieldValue != null) {
400                // TODO: format by type
401                readOnlyDisplaySuffix = additionalFieldValue.toString();
402            }
403        }
404    }
405
406    /**
407     * {@inheritDoc}
408     */
409    @Override
410    public void copyFromAttributeDefinition(AttributeDefinition attributeDefinition) {
411        // label
412        if (StringUtils.isEmpty(getLabel())) {
413            setLabel(attributeDefinition.getLabel());
414        }
415
416        // short label
417        if (StringUtils.isEmpty(getShortLabel())) {
418            setShortLabel(attributeDefinition.getShortLabel());
419        }
420
421        // security
422        if ((attributeDefinition.getAttributeSecurity() != null) && ((getDataFieldSecurity() == null) || (
423                getDataFieldSecurity().getAttributeSecurity() == null))) {
424            initializeComponentSecurity();
425
426            getDataFieldSecurity().setAttributeSecurity(attributeDefinition.getAttributeSecurity());
427        }
428
429        // alternate property name
430        if (getReadOnlyDisplayReplacementPropertyName() == null && StringUtils.isNotBlank(
431                attributeDefinition.getAlternateDisplayAttributeName())) {
432            setReadOnlyDisplayReplacementPropertyName(attributeDefinition.getAlternateDisplayAttributeName());
433        }
434
435        // additional property display name
436        if (getReadOnlyDisplaySuffixPropertyName() == null && StringUtils.isNotBlank(
437                attributeDefinition.getAdditionalDisplayAttributeName())) {
438            setReadOnlyDisplaySuffixPropertyName(attributeDefinition.getAdditionalDisplayAttributeName());
439        }
440
441        // property editor
442        if (getPropertyEditor() == null) {
443            setPropertyEditor(attributeDefinition.getPropertyEditor());
444        }
445    }
446
447    /**
448     * {@inheritDoc}
449     */
450    @Override
451    public boolean isInputAllowed() {
452        return false;
453    }
454
455    /**
456     * {@inheritDoc}
457     */
458    @Override
459    @BeanTagAttribute
460    public String getPropertyName() {
461        return this.propertyName;
462    }
463
464    /**
465     * {@inheritDoc}
466     */
467    @Override
468    public void setPropertyName(String propertyName) {
469        this.propertyName = propertyName;
470    }
471
472    /**
473     * {@inheritDoc}
474     */
475    @Override
476    @BeanTagAttribute
477    public PropertyEditor getPropertyEditor() {
478        return propertyEditor;
479    }
480
481    /**
482     * {@inheritDoc}
483     */
484    @Override
485    public void setPropertyEditor(PropertyEditor propertyEditor) {
486        this.propertyEditor = propertyEditor;
487    }
488
489    /**
490     * {@inheritDoc}
491     */
492    @Override
493    public void setPropertyEditorClass(Class<? extends PropertyEditor> propertyEditorClass) {
494        this.propertyEditor = KRADUtils.createNewObjectFromClass(propertyEditorClass);
495    }
496
497    /**
498     * {@inheritDoc}
499     */
500    @Override
501    @BeanTagAttribute
502    public BindingInfo getBindingInfo() {
503        return this.bindingInfo;
504    }
505
506    /**
507     * {@inheritDoc}
508     */
509    @Override
510    public void setBindingInfo(BindingInfo bindingInfo) {
511        this.bindingInfo = bindingInfo;
512    }
513
514    /**
515     * {@inheritDoc}
516     */
517    @Override
518    public String getName() {
519        return this.getBindingInfo().getBindingPath();
520    }
521
522    /**
523     * {@inheritDoc}
524     */
525    @Override
526    @BeanTagAttribute
527    public String getDictionaryAttributeName() {
528        return this.dictionaryAttributeName;
529    }
530
531    /**
532     * {@inheritDoc}
533     */
534    @Override
535    public void setDictionaryAttributeName(String dictionaryAttributeName) {
536        this.dictionaryAttributeName = dictionaryAttributeName;
537    }
538
539    /**
540     * {@inheritDoc}
541     */
542    @Override
543    @BeanTagAttribute
544    public String getDictionaryObjectEntry() {
545        return this.dictionaryObjectEntry;
546    }
547
548    /**
549     * {@inheritDoc}
550     */
551    @Override
552    public void setDictionaryObjectEntry(String dictionaryObjectEntry) {
553        this.dictionaryObjectEntry = dictionaryObjectEntry;
554    }
555
556    /**
557     * {@inheritDoc}
558     */
559    @Override
560    @BeanTagAttribute
561    public Object getDefaultValue() {
562        return this.defaultValue;
563    }
564
565    /**
566     * {@inheritDoc}
567     */
568    @Override
569    public void setDefaultValue(Object defaultValue) {
570        this.defaultValue = defaultValue;
571    }
572
573    /**
574     * {@inheritDoc}
575     */
576    @Override
577    @BeanTagAttribute
578    public Class<? extends ValueFinder> getDefaultValueFinderClass() {
579        return this.defaultValueFinderClass;
580    }
581
582    /**
583     * {@inheritDoc}
584     */
585    @Override
586    public void setDefaultValueFinderClass(Class<? extends ValueFinder> defaultValueFinderClass) {
587        this.defaultValueFinderClass = defaultValueFinderClass;
588    }
589
590    /**
591     * {@inheritDoc}
592     */
593    @Override
594    @BeanTagAttribute
595    public List<Object> getDefaultValues() {
596        return this.defaultValues;
597    }
598
599    /**
600     * {@inheritDoc}
601     */
602    public void setDefaultValues(List<Object> defaultValues) {
603        this.defaultValues = defaultValues;
604    }
605
606    /**
607     * {@inheritDoc}
608     */
609    @Override
610    public String getForcedValue() {
611        return forcedValue;
612    }
613
614    /**
615     * {@inheritDoc}
616     */
617    @Override
618    public void setForcedValue(String forcedValue) {
619        this.forcedValue = forcedValue;
620    }
621
622    /**
623     * {@inheritDoc}
624     */
625    @Override
626    @BeanTagAttribute
627    public String getHelpSummary() {
628        return this.help.getTooltipHelpContent();
629    }
630
631    /**
632     * {@inheritDoc}
633     */
634    @Override
635    public void setHelpSummary(String helpSummary) {
636        this.help.setTooltipHelpContent(helpSummary);
637    }
638
639    /**
640     * {@inheritDoc}
641     */
642    @Override
643    public DataFieldSecurity getDataFieldSecurity() {
644        return (DataFieldSecurity) super.getComponentSecurity();
645    }
646
647    /**
648     * {@inheritDoc}
649     */
650    @Override
651    public void setComponentSecurity(ComponentSecurity componentSecurity) {
652        if ((componentSecurity != null) && !(componentSecurity instanceof DataFieldSecurity)) {
653            throw new RiceRuntimeException("Component security for DataField should be instance of DataFieldSecurity");
654        }
655
656        super.setComponentSecurity(componentSecurity);
657    }
658
659    /**
660     * @see org.kuali.rice.krad.uif.component.ComponentBase#initializeComponentSecurity()
661     */
662    @Override
663    protected void initializeComponentSecurity() {
664        if (getComponentSecurity() == null) {
665            setComponentSecurity(KRADUtils.createNewObjectFromClass(DataFieldSecurity.class));
666        }
667    }
668
669    /**
670     * {@inheritDoc}
671     */
672    @Override
673    @BeanTagAttribute
674    public boolean isAddHiddenWhenReadOnly() {
675        return addHiddenWhenReadOnly;
676    }
677
678    /**
679     * {@inheritDoc}
680     */
681    @Override
682    public void setAddHiddenWhenReadOnly(boolean addHiddenWhenReadOnly) {
683        this.addHiddenWhenReadOnly = addHiddenWhenReadOnly;
684    }
685
686    /**
687     * {@inheritDoc}
688     */
689    @Override
690    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
691    public Inquiry getInquiry() {
692        return this.inquiry;
693    }
694
695    /**
696     * {@inheritDoc}
697     */
698    @Override
699    public void setInquiry(Inquiry inquiry) {
700        this.inquiry = inquiry;
701    }
702
703    /**
704     * {@inheritDoc}
705     */
706    @Override
707    public boolean isEnableAutoInquiry() {
708        return enableAutoInquiry;
709    }
710
711    /**
712     * {@inheritDoc}
713     */
714    @Override
715    public void setEnableAutoInquiry(boolean enableAutoInquiry) {
716        this.enableAutoInquiry = enableAutoInquiry;
717    }
718
719    /**
720     * {@inheritDoc}
721     */
722    @Override
723    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
724    public Help getHelp() {
725        return this.help;
726    }
727
728    /**
729     * {@inheritDoc}
730     */
731    @Override
732    public void setHelp(Help help) {
733        this.help = help;
734    }
735
736    /**
737     * {@inheritDoc}
738     */
739    @Override
740    @BeanTagAttribute
741    public boolean isRenderInfoMessageSpan() {
742        return renderInfoMessageSpan;
743    }
744
745    /**
746     * {@inheritDoc}
747     */
748    @Override
749    public void setRenderInfoMessageSpan(boolean renderInfoMessageSpan) {
750        this.renderInfoMessageSpan = renderInfoMessageSpan;
751    }
752
753    /**
754     * {@inheritDoc}
755     */
756    @Override
757    @BeanTagAttribute
758    public boolean isRenderMarkerIconSpan() {
759        return renderMarkerIconSpan;
760    }
761
762    /**
763     * {@inheritDoc}
764     */
765    @Override
766    public void setRenderMarkerIconSpan(boolean renderMarkerIconSpan) {
767        this.renderMarkerIconSpan = renderMarkerIconSpan;
768    }
769
770    /**
771     * {@inheritDoc}
772     */
773    @Override
774    public void setTooltipOfComponent(Tooltip tooltip) {
775        getFieldLabel().setToolTip(tooltip);
776    }
777
778    /**
779     * {@inheritDoc}
780     */
781    @Override
782    public String getHelpTitle() {
783        return this.getLabel();
784    }
785
786    /**
787     * {@inheritDoc}
788     */
789    @Override
790    @BeanTagAttribute
791    public String getReadOnlyDisplaySuffixPropertyName() {
792        return this.readOnlyDisplaySuffixPropertyName;
793    }
794
795    /**
796     * {@inheritDoc}
797     */
798    @Override
799    public void setReadOnlyDisplaySuffixPropertyName(String readOnlyDisplaySuffixPropertyName) {
800        this.readOnlyDisplaySuffixPropertyName = readOnlyDisplaySuffixPropertyName;
801    }
802
803    /**
804     * {@inheritDoc}
805     */
806    @Override
807    @BeanTagAttribute
808    public String getReadOnlyDisplayReplacementPropertyName() {
809        return this.readOnlyDisplayReplacementPropertyName;
810    }
811
812    /**
813     * {@inheritDoc}
814     */
815    @Override
816    public void setReadOnlyDisplayReplacementPropertyName(String readOnlyDisplayReplacementPropertyName) {
817        this.readOnlyDisplayReplacementPropertyName = readOnlyDisplayReplacementPropertyName;
818    }
819
820    /**
821     * {@inheritDoc}
822     */
823    @Override
824    @BeanTagAttribute
825    public String getReadOnlyDisplayReplacement() {
826        return readOnlyDisplayReplacement;
827    }
828
829    /**
830     * {@inheritDoc}
831     */
832    @Override
833    public void setReadOnlyDisplayReplacement(String value) {
834        this.readOnlyDisplayReplacement = value;
835    }
836
837    /**
838     * {@inheritDoc}
839     */
840    @Override
841    @BeanTagAttribute
842    public String getReadOnlyDisplaySuffix() {
843        return readOnlyDisplaySuffix;
844    }
845
846    /**
847     * {@inheritDoc}
848     */
849    @Override
850    public void setReadOnlyDisplaySuffix(String value) {
851        this.readOnlyDisplaySuffix = value;
852    }
853
854    /**
855     * {@inheritDoc}
856     */
857    @Override
858    @BeanTagAttribute
859    public String getReadOnlyListDisplayType() {
860        return readOnlyListDisplayType;
861    }
862
863    /**
864     * {@inheritDoc}
865     */
866    @Override
867    public void setReadOnlyListDisplayType(String readOnlyListDisplayType) {
868        this.readOnlyListDisplayType = readOnlyListDisplayType;
869    }
870
871    /**
872     * {@inheritDoc}
873     */
874    @Override
875    @BeanTagAttribute
876    public String getReadOnlyListDelimiter() {
877        return readOnlyListDelimiter;
878    }
879
880    /**
881     * {@inheritDoc}
882     */
883    @Override
884    public void setReadOnlyListDelimiter(String readOnlyListDelimiter) {
885        this.readOnlyListDelimiter = readOnlyListDelimiter;
886    }
887
888    /**
889     * {@inheritDoc}
890     */
891    @Override
892    @BeanTagAttribute
893    public boolean isApplyMask() {
894        return applyMask;
895    }
896
897    /**
898     * {@inheritDoc}
899     */
900    @Override
901    public void setApplyMask(boolean applyMask) {
902        this.applyMask = applyMask;
903    }
904
905    /**
906     * {@inheritDoc}
907     */
908    @Override
909    @BeanTagAttribute
910    public MaskFormatter getMaskFormatter() {
911        return maskFormatter;
912    }
913
914    /**
915     * {@inheritDoc}
916     */
917    @Override
918    public void setMaskFormatter(MaskFormatter maskFormatter) {
919        this.maskFormatter = maskFormatter;
920    }
921
922    /**
923     * {@inheritDoc}
924     */
925    @Override
926    @BeanTagAttribute
927    public List<String> getAdditionalHiddenPropertyNames() {
928        if (additionalHiddenPropertyNames == Collections.EMPTY_LIST && isMutable(true)) {
929            additionalHiddenPropertyNames = new LifecycleAwareList<String>(this);
930        }
931        
932        return additionalHiddenPropertyNames;
933    }
934
935    /**
936     * {@inheritDoc}
937     */
938    @Override
939    public void setAdditionalHiddenPropertyNames(List<String> additionalHiddenPropertyNames) {
940        if (additionalHiddenPropertyNames == null) {
941            this.additionalHiddenPropertyNames = Collections.emptyList();
942        } else {
943            this.additionalHiddenPropertyNames =
944                    new LifecycleAwareList<String>(this, additionalHiddenPropertyNames);
945        }
946    }
947
948    /**
949     * {@inheritDoc}
950     */
951    @Override
952    @BeanTagAttribute
953    public List<String> getPropertyNamesForAdditionalDisplay() {
954        if (propertyNamesForAdditionalDisplay == Collections.EMPTY_LIST && isMutable(true)) {
955            propertyNamesForAdditionalDisplay = new LifecycleAwareList<String>(this);
956        }
957        
958        return propertyNamesForAdditionalDisplay;
959    }
960
961    /**
962     * {@inheritDoc}
963     */
964    @Override
965    public void setPropertyNamesForAdditionalDisplay(List<String> propertyNamesForAdditionalDisplay) {
966        if (propertyNamesForAdditionalDisplay == null) {
967            this.propertyNamesForAdditionalDisplay = Collections.emptyList();
968        } else {
969            this.propertyNamesForAdditionalDisplay =
970                    new LifecycleAwareList<String>(this, propertyNamesForAdditionalDisplay);
971        }
972    }
973    
974    /**
975     * {@inheritDoc}
976     */
977    @Override
978    @BeanTagAttribute
979    public boolean isEscapeHtmlInPropertyValue() {
980        return this.escapeHtmlInPropertyValue;
981    }
982
983    /**
984     * {@inheritDoc}
985     */
986    @Override
987    public void setEscapeHtmlInPropertyValue(boolean escapeHtmlInPropertyValue) {
988        this.escapeHtmlInPropertyValue = escapeHtmlInPropertyValue;
989    }
990
991    /**
992     * {@inheritDoc}
993     */
994    @Override
995    @BeanTagAttribute
996    public boolean isMultiLineReadOnlyDisplay() {
997        return multiLineReadOnlyDisplay;
998    }
999
1000    /**
1001     * {@inheritDoc}
1002     */
1003    @Override
1004    public void setMultiLineReadOnlyDisplay(boolean multiLineReadOnlyDisplay) {
1005        this.multiLineReadOnlyDisplay = multiLineReadOnlyDisplay;
1006    }
1007
1008    /**
1009     * {@inheritDoc}
1010     */
1011    @Override
1012    public boolean hasSecureValue() {
1013        boolean hasHideAuthz = false;
1014
1015        if (getDataFieldSecurity() != null) {
1016            boolean isViewAuthz = false;
1017            boolean isViewInLineAuthz = false;
1018            boolean isHide = false;
1019
1020            if (getDataFieldSecurity().isViewAuthz() != null) {
1021                isViewAuthz = getDataFieldSecurity().isViewAuthz().booleanValue();
1022            }
1023
1024            if (getDataFieldSecurity().isViewInLineAuthz() != null) {
1025                isViewInLineAuthz = getDataFieldSecurity().isViewInLineAuthz().booleanValue();
1026            }
1027
1028            if (getDataFieldSecurity().getAttributeSecurity() != null) {
1029                isHide = getDataFieldSecurity().getAttributeSecurity().isHide();
1030            }
1031
1032            hasHideAuthz = isViewAuthz || isViewInLineAuthz || isHide;
1033        }
1034
1035        return isApplyMask() || (hasHideAuthz && isHidden());
1036    }
1037
1038    /**
1039     * {@inheritDoc}
1040     */
1041    @Override
1042    public boolean isRenderFieldset() {
1043        return (!Boolean.TRUE.equals(this.getReadOnly())
1044                && inquiry != null
1045                && inquiry.isRender()
1046                && inquiry.getInquiryLink() != null
1047                && inquiry.getInquiryLink().isRender()) || (help != null
1048                && help.isRender()
1049                && help.getHelpAction() != null
1050                && help.getHelpAction().isRender());
1051    }
1052
1053    /**
1054     * {@inheritDoc}
1055     */
1056    @Override
1057    @BeanTagAttribute(name = "sortAs")
1058    public String getSortAs() {
1059        return sortAs;
1060    }
1061
1062    /**
1063     * {@inheritDoc}
1064     */
1065    @Override
1066    public void setSortAs(String sortAs) {
1067        if (!(sortAs.equals(UifConstants.TableToolsValues.CURRENCY) || sortAs.equals(UifConstants.TableToolsValues.DATE) || sortAs.equals(UifConstants.TableToolsValues.NUMERIC) || sortAs.equals(UifConstants.TableToolsValues.STRING))) {
1068            throw new IllegalArgumentException("invalid sortAs value of " + sortAs + ", allowed: " + UifConstants.TableToolsValues.CURRENCY + "|" + UifConstants.TableToolsValues.DATE + "|" + UifConstants.TableToolsValues.NUMERIC + "|" + UifConstants.TableToolsValues.STRING);
1069        }
1070        this.sortAs = sortAs;
1071    }
1072
1073    /**
1074     * {@inheritDoc}
1075     */
1076    @Override
1077    public void completeValidation(ValidationTrace tracer) {
1078        tracer.addBean(this);
1079
1080        // Checks that the property is connected to the field
1081        if (getPropertyName() == null) {
1082            if (!Validator.checkExpressions(this, "propertyName")) {
1083                String currentValues[] = {"propertyName = " + getPropertyName()};
1084                tracer.createError("Property name not set", currentValues);
1085            }
1086        }
1087
1088        // Checks that the default values  present
1089/*        if (getDefaultValue() != null && getDefaultValues() != null) {
1090            String currentValues[] =
1091                    {"defaultValue =" + getDefaultValue(), "defaultValues Size =" + getDefaultValues().length};
1092            tracer.createWarning("Both Default Value and Default Values set", currentValues);
1093        }*/
1094
1095        // Checks that a mask formatter is set if the data field is to be masked
1096        if (isApplyMask()) {
1097            if (maskFormatter == null) {
1098                String currentValues[] = {"applyMask =" + isApplyMask(), "maskFormatter =" + maskFormatter};
1099                tracer.createWarning("Apply mask is true, but no value is set for maskFormatter", currentValues);
1100            }
1101        }
1102
1103        super.completeValidation(tracer.getCopy());
1104    }
1105
1106    /**
1107     * Determines whether or not to create an automatic inquiry widget for this field within the current lifecycle.
1108     * 
1109     * @return True if an automatic inquiry widget should be created for this field on the current lifecycle.
1110     */
1111    protected boolean hasAutoInquiryRelationship() {
1112        if (getBindingInfo() == null) {
1113            return false;
1114        }
1115
1116        View view = ViewLifecycle.getView();
1117        Object model = ViewLifecycle.getModel();
1118        Object object = ViewModelUtils.getParentObjectForMetadata(view, model, this);
1119
1120        // Do checks for inquiry when read only
1121        if (Boolean.TRUE.equals(getReadOnly())) {
1122            String bindingPath = getBindingInfo().getBindingPath();
1123
1124            if (StringUtils.isBlank(bindingPath) || bindingPath.equals("null")) {
1125                return false;
1126            }
1127
1128            Object propertyValue;
1129
1130            // do not render the inquiry if the field value is null
1131            try {
1132                propertyValue = ObjectPropertyUtils.getPropertyValue(model, bindingPath);
1133
1134                if ((propertyValue == null) || StringUtils.isBlank(propertyValue.toString())) {
1135                    return false;
1136                }
1137            } catch (Exception e) {
1138                // if we can't get the value just swallow the exception and don't set an inquiry
1139                return false;
1140            }
1141
1142            // do not render the inquiry link if it is the same as its parent
1143            if (view.getViewTypeName() == UifConstants.ViewType.INQUIRY) {
1144                InquiryForm inquiryForm = (InquiryForm) model;
1145                String[] parameterValues = inquiryForm.getInitialRequestParameters().get(propertyName);
1146
1147                // do not render the inquiry if current data object is the same as its parent and the request parameter
1148                // contains the parent field value
1149                if (StringUtils.equals(inquiryForm.getDataObjectClassName(), dictionaryObjectEntry)
1150                        && ArrayUtils.contains(parameterValues, propertyValue.toString())) {
1151                    return false;
1152                }
1153            }
1154        }
1155
1156        return object != null && KRADServiceLocatorWeb.getViewDictionaryService().isInquirable(object.getClass());
1157    }
1158
1159}