001    /**
002     * Copyright 2005-2014 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            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    }