001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.datadictionary;
017
018import org.apache.commons.lang.ClassUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.log4j.Logger;
021import org.kuali.rice.core.api.uif.DataType;
022import org.kuali.rice.core.api.util.ClassLoaderUtils;
023import org.kuali.rice.core.web.format.Formatter;
024import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
025import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
026import org.kuali.rice.krad.datadictionary.exception.ClassValidationException;
027import org.kuali.rice.krad.datadictionary.validation.ValidationPattern;
028import org.kuali.rice.krad.datadictionary.validation.capability.CaseConstrainable;
029import org.kuali.rice.krad.datadictionary.validation.capability.Formatable;
030import org.kuali.rice.krad.datadictionary.validation.capability.HierarchicallyConstrainable;
031import org.kuali.rice.krad.datadictionary.validation.capability.LengthConstrainable;
032import org.kuali.rice.krad.datadictionary.validation.capability.MustOccurConstrainable;
033import org.kuali.rice.krad.datadictionary.validation.capability.PrerequisiteConstrainable;
034import org.kuali.rice.krad.datadictionary.validation.capability.RangeConstrainable;
035import org.kuali.rice.krad.datadictionary.validation.capability.ValidCharactersConstrainable;
036import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
037import org.kuali.rice.krad.datadictionary.validation.constraint.LookupConstraint;
038import org.kuali.rice.krad.datadictionary.validation.constraint.MustOccurConstraint;
039import org.kuali.rice.krad.datadictionary.validation.constraint.PrerequisiteConstraint;
040import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
041import org.kuali.rice.krad.keyvalues.KeyValuesFinder;
042import org.kuali.rice.krad.uif.control.Control;
043import org.kuali.rice.krad.util.ObjectUtils;
044
045import java.beans.PropertyEditor;
046import java.util.List;
047
048/**
049 * A single attribute definition in the DataDictionary, which contains
050 * information relating to the display, validation, and general maintenance of a
051 * specific attribute of an entry.
052 * 
053 * @author Kuali Rice Team (rice.collab@kuali.org)
054 */
055public class AttributeDefinition extends AttributeDefinitionBase implements CaseConstrainable, PrerequisiteConstrainable, Formatable, HierarchicallyConstrainable, MustOccurConstrainable, LengthConstrainable, RangeConstrainable, ValidCharactersConstrainable {
056    private static final long serialVersionUID = -2490613377818442742L;
057
058        protected Boolean forceUppercase = Boolean.FALSE;
059        
060        protected DataType dataType;
061        
062        protected Integer minLength;
063        protected Integer maxLength;
064        protected Boolean unique;
065
066        protected String exclusiveMin;
067        protected String inclusiveMax;
068
069        @Deprecated 
070        protected ValidationPattern validationPattern;
071
072        protected ControlDefinition control;
073
074        // TODO: rename to control once ControlDefinition is removed
075        protected Control controlField;
076
077        protected String formatterClass;
078    protected PropertyEditor propertyEditor;
079
080        protected AttributeSecurity attributeSecurity;
081        
082        protected Boolean dynamic;
083        
084        // KS-style constraints 
085        protected String customValidatorClass;
086        protected ValidCharactersConstraint validCharactersConstraint;  
087    protected CaseConstraint caseConstraint;
088    protected List<PrerequisiteConstraint> dependencyConstraints;
089        protected List<MustOccurConstraint> mustOccurConstraints;
090        protected LookupConstraint lookupDefinition;// If the user wants to match
091                // against two searches, that search must be defined as  well
092        protected String lookupContextPath;
093        
094        //TODO: This may not be required since we now use ComplexAttributeDefinition
095        protected String childEntryName;
096        
097        private KeyValuesFinder optionsFinder;
098
099        protected String alternateDisplayAttributeName;
100    protected String additionalDisplayAttributeName;
101    
102
103        public AttributeDefinition() {
104                // Empty
105        }
106
107        /**
108         * forceUppercase = convert user entry to uppercase and always display
109         * database value as uppercase.
110         */
111        public void setForceUppercase(Boolean forceUppercase) {
112                this.forceUppercase = forceUppercase;
113        }
114
115        public Boolean getForceUppercase() {
116                return this.forceUppercase;
117        }
118
119        @Override
120        public Integer getMaxLength() {
121                return maxLength;
122        }
123
124        /**
125         * The maxLength element determines the maximum size of the field for data
126         * entry edit purposes and for display purposes.
127         */
128        public void setMaxLength(Integer maxLength) {
129                this.maxLength = maxLength;
130        }
131
132        @Override
133        public String getExclusiveMin() {
134                return exclusiveMin;
135        }
136
137        /**
138         * The exclusiveMin element determines the minimum allowable value for data
139         * entry editing purposes. Value can be an integer or decimal value such as
140         * -.001 or 99.
141         */
142        public void setExclusiveMin(String exclusiveMin) {
143                this.exclusiveMin = exclusiveMin;
144        }
145
146        /**
147         * The inclusiveMax element determines the maximum allowable value for data
148         * entry editing purposes. Value can be an integer or decimal value such as
149         * -.001 or 99.
150         * 
151         * JSTL: This field is mapped into the field named "exclusiveMax".
152         */
153        @Override
154        public String getInclusiveMax() {
155                return inclusiveMax;
156        }
157
158        /**
159         * The inclusiveMax element determines the maximum allowable value for data
160         * entry editing purposes. Value can be an integer or decimal value such as
161         * -.001 or 99.
162         * 
163         * JSTL: This field is mapped into the field named "exclusiveMax".
164         */
165        public void setInclusiveMax(String inclusiveMax) {
166                this.inclusiveMax = inclusiveMax;
167        }
168
169        /**
170         * @return true if a validationPattern has been set
171         */
172        public boolean hasValidationPattern() {
173                return (validationPattern != null);
174        }
175
176        public ValidationPattern getValidationPattern() {
177                return this.validationPattern;
178        }
179
180        /**
181         * The validationPattern element defines the allowable character-level or
182         * field-level values for an attribute.
183         * 
184         * JSTL: validationPattern is a Map which is accessed using a key of
185         * "validationPattern". Each entry may contain some of the keys listed
186         * below. The keys that may be present for a given attribute are dependent
187         * upon the type of validationPattern.
188         * 
189         * maxLength (String) exactLength type allowWhitespace allowUnderscore
190         * allowPeriod validChars precision scale allowNegative
191         * 
192         * The allowable keys (in addition to type) for each type are: Type****
193         * ***Keys*** alphanumeric exactLength maxLength allowWhitespace
194         * allowUnderscore allowPeriod
195         * 
196         * alpha exactLength maxLength allowWhitespace
197         * 
198         * anyCharacter exactLength maxLength allowWhitespace
199         * 
200         * charset validChars
201         * 
202         * numeric exactLength maxLength
203         * 
204         * fixedPoint allowNegative precision scale
205         * 
206         * floatingPoint allowNegative
207         * 
208         * date n/a emailAddress n/a javaClass n/a month n/a phoneNumber n/a
209         * timestamp n/a year n/a zipcode n/a
210         * 
211         * Note: maxLength and exactLength are mutually exclusive. If one is
212         * entered, the other may not be entered.
213         * 
214         * Note: See ApplicationResources.properties for exact regex patterns. e.g.
215         * validationPatternRegex.date for regex used in date validation.
216         */
217        public void setValidationPattern(ValidationPattern validationPattern) {
218                this.validationPattern = validationPattern;
219        }
220
221
222        /**
223         * @return control
224         */
225        public ControlDefinition getControl() {
226                return control;
227        }
228
229        /**
230         * The control element defines the manner in which an attribute is displayed
231         * and the manner in which the attribute value is entered.
232         * 
233         * JSTL: control is a Map representing an HTML control. It is accessed using
234         * a key of "control". The table below shows the types of entries associated
235         * with each type of control.
236         * 
237         ** Control Type** **Key** **Value** checkbox checkbox boolean String
238         * 
239         * hidden hidden boolean String
240         * 
241         * radio radio boolean String valuesFinder valuesFinder class name
242         * dataObjectClass String keyAttribute String labelAttribute String
243         * includeKeyInLabel boolean String
244         * 
245         * select select boolean String valuesFinder valuesFinder class name
246         * dataObjectClass String keyAttribute String labelAttribute String
247         * includeBlankRow boolean String includeKeyInLabel boolean String
248         * 
249         * apcSelect apcSelect boolean String paramNamespace String
250         * parameterDetailType String parameterName String
251         * 
252         * text text boolean String size String
253         * 
254         * textarea textarea boolean String rows cols
255         * 
256         * currency currency boolean String size String formattedMaxLength String
257         * 
258         * kualiUser kualiUser boolean String universalIdAttributeName String
259         * userIdAttributeName String personNameAttributeName String
260         * 
261         * lookupHidden lookupHidden boolean String
262         * 
263         * lookupReadonly lookupReadonly boolean String
264         * 
265         * @param control
266         * @throws IllegalArgumentException
267         *             if the given control is null
268         */
269        public void setControl(ControlDefinition control) {
270                if (control == null) {
271                        throw new IllegalArgumentException("invalid (null) control");
272                }
273                this.control = control;
274        }
275
276        public boolean hasFormatterClass() {
277                return (formatterClass != null);
278        }
279
280        @Override
281        public String getFormatterClass() {
282                return formatterClass;
283        }
284
285        /**
286         * The formatterClass element is used when custom formatting is required for
287         * display of the field value. This field specifies the name of the java
288         * class to be used for the formatting. About 15 different classes are
289         * available including BooleanFormatter, CurrencyFormatter, DateFormatter,
290         * etc.
291         */
292        public void setFormatterClass(String formatterClass) {
293                if (formatterClass == null) {
294                        throw new IllegalArgumentException("invalid (null) formatterClass");
295                }
296                this.formatterClass = formatterClass;
297        }
298
299    /**
300     * Performs formatting of the field value for display and then converting the value back to its
301     * expected type from a string
302     *
303     * <p>
304     * Note property editors exist and are already registered for the basic Java types and the
305     * common Kuali types such as [@link KualiDecimal}. Registration with this property is only
306     * needed for custom property editors
307     * </p>
308     *
309     * @return PropertyEditor property editor instance to use for this field
310     */
311    public PropertyEditor getPropertyEditor() {
312        return propertyEditor;
313    }
314
315    /**
316     * Setter for the custom property editor to use for the field
317     *
318     * @param propertyEditor
319     */
320    public void setPropertyEditor(PropertyEditor propertyEditor) {
321        this.propertyEditor = propertyEditor;
322    }
323
324    /**
325     * Convenience setter for configuring a property editor by class
326     *
327     * @param propertyEditorClass
328     */
329    public void setPropertyEditorClass(Class<? extends PropertyEditor> propertyEditorClass) {
330        this.propertyEditor = ObjectUtils.newInstance(propertyEditorClass);
331    }
332
333        /**
334         * Directly validate simple fields, call completeValidation on Definition
335         * fields.
336         * 
337         * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#completeValidation()
338         */
339        @Override
340        public void completeValidation(Class<?> rootObjectClass, Class<?> otherObjectClass) {
341                try {
342                        if (!DataDictionary.isPropertyOf(rootObjectClass, getName())) {
343                                throw new AttributeValidationException("property '" + getName() + "' is not a property of class '"
344                                                + rootObjectClass.getName() + "' (" + "" + ")");
345                        }
346
347                        //TODO currently requiring a control or controlField, but this should not be case (AttrField should probably do the check)
348                        if (getControl() == null && getControlField() == null) {
349                                throw new AttributeValidationException("property '" + getName() + "' in class '"
350                                                + rootObjectClass.getName() + " does not have a control defined");
351                        }
352                        
353                        if(getControl() != null) {
354                            getControl().completeValidation(rootObjectClass, otherObjectClass);
355                        }
356
357                        if (attributeSecurity != null) {
358                                attributeSecurity.completeValidation(rootObjectClass, otherObjectClass);
359                        }
360
361                        if (validationPattern != null) {
362                                validationPattern.completeValidation();
363                        }
364
365                        if (formatterClass != null) {
366                                try {
367                                        Class formatterClassObject = ClassUtils.getClass(ClassLoaderUtils.getDefaultClassLoader(),
368                                                        getFormatterClass());
369                                        if (!Formatter.class.isAssignableFrom(formatterClassObject)) {
370                                                throw new ClassValidationException("formatterClass is not a valid instance of "
371                                                                + Formatter.class.getName() + " instead was: " + formatterClassObject.getName());
372                                        }
373                                }
374                                catch (ClassNotFoundException e) {
375                                        throw new ClassValidationException("formatterClass could not be found: " + getFormatterClass(), e);
376                                }
377                        }
378                }
379                catch (RuntimeException ex) {
380                        Logger.getLogger(getClass()).error(
381                                        "Unable to validate attribute " + rootObjectClass + "." + getName() + ": " + ex.getMessage(), ex);
382                        throw ex;
383                }
384        }
385
386        /**
387         * @see java.lang.Object#toString()
388         */
389        @Override
390        public String toString() {
391                return "AttributeDefinition for attribute " + getName();
392        }
393
394        /**
395         * @return the attributeSecurity
396         */
397        public AttributeSecurity getAttributeSecurity() {
398                return this.attributeSecurity;
399        }
400
401    /**
402    * This overridden method applies validCharacterConstraint if legacy validation pattern in place
403    *
404    * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
405    */
406    @Override
407    public void afterPropertiesSet() throws Exception {
408        if (StringUtils.isEmpty(name)) {
409            throw new RuntimeException("blank name for bean: " + id);
410        }
411    }
412
413    /**
414     * @param attributeSecurity
415         *            the attributeSecurity to set
416         */
417        public void setAttributeSecurity(AttributeSecurity attributeSecurity) {
418                this.attributeSecurity = attributeSecurity;
419        }
420
421        public boolean hasAttributeSecurity() {
422                return (attributeSecurity != null);
423        }
424
425        /**
426         * @return the unique
427         */
428        public Boolean getUnique() {
429                return this.unique;
430        }
431
432        /**
433         * @param unique
434         *            the unique to set
435         */
436        public void setUnique(Boolean unique) {
437                this.unique = unique;
438        }
439
440        /**
441         * Default <code>Control</code> to use when the attribute is to be rendered
442         * for the UI. Used by the UIF when a control is not defined for an
443         * <code>InputField</code>
444         * 
445         * @return Control instance
446         */
447        public Control getControlField() {
448                return this.controlField;
449        }
450
451        /**
452         * Setter for the default control
453         * 
454         * @param controlField
455         */
456        public void setControlField(Control controlField) {
457                this.controlField = controlField;
458        }
459
460        /**
461         * @return the minLength
462         */
463        public Integer getMinLength() {
464                return this.minLength;
465        }
466
467        /**
468         * @param minLength the minLength to set
469         */
470        public void setMinLength(Integer minLength) {
471                this.minLength = minLength;
472        }
473
474        /**
475         * @return the dataType
476         */
477        @Override
478        public DataType getDataType() {
479                return this.dataType;
480        }
481
482        /**
483         * @param dataType the dataType to set
484         */
485        public void setDataType(DataType dataType) {
486                this.dataType = dataType;
487        }
488        
489        public void setDataType(String dataType) {
490                this.dataType = DataType.valueOf(dataType);
491        }
492
493        /**
494         * @return the customValidatorClass
495         */
496        public String getCustomValidatorClass() {
497                return this.customValidatorClass;
498        }
499
500        /**
501         * @param customValidatorClass the customValidatorClass to set
502         */
503        public void setCustomValidatorClass(String customValidatorClass) {
504                this.customValidatorClass = customValidatorClass;
505        }
506
507        /**
508         * @return the validChars
509         */
510        @Override
511        public ValidCharactersConstraint getValidCharactersConstraint() {
512                return this.validCharactersConstraint;
513        }
514
515        /**
516         * @param validCharactersConstraint the validChars to set
517         */
518        public void setValidCharactersConstraint(ValidCharactersConstraint validCharactersConstraint) {
519                this.validCharactersConstraint = validCharactersConstraint;
520        }
521
522        /**
523         * @return the caseConstraint
524         */
525        @Override
526        public CaseConstraint getCaseConstraint() {
527                return this.caseConstraint;
528        }
529
530        /**
531         * @param caseConstraint the caseConstraint to set
532         */
533        public void setCaseConstraint(CaseConstraint caseConstraint) {
534                this.caseConstraint = caseConstraint;
535        }
536
537        /**
538         * @return the requireConstraint
539         */
540        @Override
541        public List<PrerequisiteConstraint> getPrerequisiteConstraints() {
542                return this.dependencyConstraints;
543        }
544
545        /**
546         * @param dependencyConstraints the requireConstraint to set
547         */
548        public void setPrerequisiteConstraints(List<PrerequisiteConstraint> dependencyConstraints) {
549                this.dependencyConstraints = dependencyConstraints;
550        }
551
552        /**
553         * @return the occursConstraint
554         */
555        @Override
556        public List<MustOccurConstraint> getMustOccurConstraints() {
557                return this.mustOccurConstraints;
558        }
559
560        /**
561         * @param mustOccurConstraints the occursConstraint to set
562         */
563        public void setMustOccurConstraints(List<MustOccurConstraint> mustOccurConstraints) {
564                this.mustOccurConstraints = mustOccurConstraints;
565        }
566
567        /**
568         * @return the lookupDefinition
569         */
570        public LookupConstraint getLookupDefinition() {
571                return this.lookupDefinition;
572        }
573
574        /**
575         * @param lookupDefinition the lookupDefinition to set
576         */
577        public void setLookupDefinition(LookupConstraint lookupDefinition) {
578                this.lookupDefinition = lookupDefinition;
579        }
580
581        /**
582         * @return the lookupContextPath
583         */
584        public String getLookupContextPath() {
585                return this.lookupContextPath;
586        }
587
588        /**
589         * @param lookupContextPath the lookupContextPath to set
590         */
591        public void setLookupContextPath(String lookupContextPath) {
592                this.lookupContextPath = lookupContextPath;
593        }
594
595        /**
596         * @return the childEntryName
597         */
598        public String getChildEntryName() {
599                return this.childEntryName;
600        }
601
602        /**
603         * @param childEntryName the childEntryName to set
604         */
605        public void setChildEntryName(String childEntryName) {
606                this.childEntryName = childEntryName;
607        }
608
609
610        
611    /**
612     * Instance of <code>KeyValluesFinder</code> that should be invoked to
613     * provide a List of values the field can have. Generally used to provide
614     * the options for a multi-value control or to validate the submitted field
615     * value
616     * 
617     * @return KeyValuesFinder instance
618     */
619    public KeyValuesFinder getOptionsFinder() {
620        return this.optionsFinder;
621    }
622
623    /**
624     * Setter for the field's KeyValuesFinder instance
625     * 
626     * @param optionsFinder
627     */
628    public void setOptionsFinder(KeyValuesFinder optionsFinder) {
629        this.optionsFinder = optionsFinder;
630    }
631    
632    /**
633     * Setter that takes in the class name for the options finder and creates a
634     * new instance to use as the finder for the attribute field
635     * 
636     * @param optionsFinderClass
637     */
638    public void setOptionsFinderClass(Class<? extends KeyValuesFinder> optionsFinderClass) {
639        this.optionsFinder = ObjectUtils.newInstance(optionsFinderClass);
640    }
641        
642        public void setAdditionalDisplayAttributeName(String additionalDisplayAttributeName) {
643                this.additionalDisplayAttributeName = additionalDisplayAttributeName;
644        }
645        
646        public String getAdditionalDisplayAttributeName() {
647                return this.additionalDisplayAttributeName;
648        }
649
650        public void setAlternateDisplayAttributeName(String alternateDisplayAttributeName) {
651                this.alternateDisplayAttributeName = alternateDisplayAttributeName;
652        }
653        
654        public String getAlternateDisplayAttributeName() {
655                return this.alternateDisplayAttributeName;
656        }
657
658}