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