View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.datadictionary;
17  
18  import org.apache.commons.lang.ClassUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.kuali.rice.core.api.uif.DataType;
22  import org.kuali.rice.core.api.util.ClassLoaderUtils;
23  import org.kuali.rice.core.web.format.Formatter;
24  import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
25  import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
26  import org.kuali.rice.krad.datadictionary.exception.ClassValidationException;
27  import org.kuali.rice.krad.datadictionary.validation.ValidationPattern;
28  import org.kuali.rice.krad.datadictionary.validation.capability.CaseConstrainable;
29  import org.kuali.rice.krad.datadictionary.validation.capability.Formatable;
30  import org.kuali.rice.krad.datadictionary.validation.capability.HierarchicallyConstrainable;
31  import org.kuali.rice.krad.datadictionary.validation.capability.LengthConstrainable;
32  import org.kuali.rice.krad.datadictionary.validation.capability.MustOccurConstrainable;
33  import org.kuali.rice.krad.datadictionary.validation.capability.PrerequisiteConstrainable;
34  import org.kuali.rice.krad.datadictionary.validation.capability.RangeConstrainable;
35  import org.kuali.rice.krad.datadictionary.validation.capability.ValidCharactersConstrainable;
36  import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
37  import org.kuali.rice.krad.datadictionary.validation.constraint.LookupConstraint;
38  import org.kuali.rice.krad.datadictionary.validation.constraint.MustOccurConstraint;
39  import org.kuali.rice.krad.datadictionary.validation.constraint.PrerequisiteConstraint;
40  import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
41  import org.kuali.rice.krad.keyvalues.KeyValuesFinder;
42  import org.kuali.rice.krad.uif.control.Control;
43  import org.kuali.rice.krad.util.ObjectUtils;
44  
45  import java.beans.PropertyEditor;
46  import java.util.List;
47  
48  /**
49   * A single attribute definition in the DataDictionary, which contains
50   * information relating to the display, validation, and general maintenance of a
51   * specific attribute of an entry.
52   * 
53   * @author Kuali Rice Team (rice.collab@kuali.org)
54   */
55  public class AttributeDefinition extends AttributeDefinitionBase implements CaseConstrainable, PrerequisiteConstrainable, Formatable, HierarchicallyConstrainable, MustOccurConstrainable, LengthConstrainable, RangeConstrainable, ValidCharactersConstrainable {
56      private static final long serialVersionUID = -2490613377818442742L;
57  
58  	protected Boolean forceUppercase = Boolean.FALSE;
59  	
60  	protected DataType dataType;
61  	
62  	protected Integer minLength;
63  	protected Integer maxLength;
64  	protected Boolean unique;
65  
66  	protected String exclusiveMin;
67  	protected String inclusiveMax;
68  
69  	@Deprecated 
70  	protected ValidationPattern validationPattern;
71  
72  	protected ControlDefinition control;
73  
74  	// TODO: rename to control once ControlDefinition is removed
75  	protected Control controlField;
76  
77  	protected String formatterClass;
78      protected PropertyEditor propertyEditor;
79  
80  	protected AttributeSecurity attributeSecurity;
81  	
82  	protected Boolean dynamic;
83  	
84  	// KS-style constraints 
85  	protected String customValidatorClass;
86  	protected ValidCharactersConstraint validCharactersConstraint;	
87      protected CaseConstraint caseConstraint;
88      protected List<PrerequisiteConstraint> dependencyConstraints;
89  	protected List<MustOccurConstraint> mustOccurConstraints;
90  	protected LookupConstraint lookupDefinition;// If the user wants to match
91  		// against two searches, that search must be defined as  well
92  	protected String lookupContextPath;
93  	
94  	//TODO: This may not be required since we now use ComplexAttributeDefinition
95  	protected String childEntryName;
96  	
97  	private KeyValuesFinder optionsFinder;
98  
99  	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 		// FIXME: JLR - need to recreate this functionality using the ValidCharsConstraint logic
221 	}
222 
223 
224 	/**
225 	 * @return control
226 	 */
227 	public ControlDefinition getControl() {
228 		return control;
229 	}
230 
231 	/**
232 	 * The control element defines the manner in which an attribute is displayed
233 	 * and the manner in which the attribute value is entered.
234 	 * 
235 	 * JSTL: control is a Map representing an HTML control. It is accessed using
236 	 * a key of "control". The table below shows the types of entries associated
237 	 * with each type of control.
238 	 * 
239 	 ** Control Type** **Key** **Value** checkbox checkbox boolean String
240 	 * 
241 	 * hidden hidden boolean String
242 	 * 
243 	 * radio radio boolean String valuesFinder valuesFinder class name
244 	 * dataObjectClass String keyAttribute String labelAttribute String
245 	 * includeKeyInLabel boolean String
246 	 * 
247 	 * select select boolean String valuesFinder valuesFinder class name
248 	 * dataObjectClass String keyAttribute String labelAttribute String
249 	 * includeBlankRow boolean String includeKeyInLabel boolean String
250 	 * 
251 	 * apcSelect apcSelect boolean String paramNamespace String
252 	 * parameterDetailType String parameterName String
253 	 * 
254 	 * text text boolean String size String
255 	 * 
256 	 * textarea textarea boolean String rows cols
257 	 * 
258 	 * currency currency boolean String size String formattedMaxLength String
259 	 * 
260 	 * kualiUser kualiUser boolean String universalIdAttributeName String
261 	 * userIdAttributeName String personNameAttributeName String
262 	 * 
263 	 * lookupHidden lookupHidden boolean String
264 	 * 
265 	 * lookupReadonly lookupReadonly boolean String
266 	 * 
267 	 * @param control
268 	 * @throws IllegalArgumentException
269 	 *             if the given control is null
270 	 */
271 	public void setControl(ControlDefinition control) {
272 		if (control == null) {
273 			throw new IllegalArgumentException("invalid (null) control");
274 		}
275 		this.control = control;
276 	}
277 
278 	public boolean hasFormatterClass() {
279 		return (formatterClass != null);
280 	}
281 
282 	@Override
283 	public String getFormatterClass() {
284 		return formatterClass;
285 	}
286 
287 	/**
288 	 * The formatterClass element is used when custom formatting is required for
289 	 * display of the field value. This field specifies the name of the java
290 	 * class to be used for the formatting. About 15 different classes are
291 	 * available including BooleanFormatter, CurrencyFormatter, DateFormatter,
292 	 * etc.
293 	 */
294 	public void setFormatterClass(String formatterClass) {
295 		if (formatterClass == null) {
296 			throw new IllegalArgumentException("invalid (null) formatterClass");
297 		}
298 		this.formatterClass = formatterClass;
299 	}
300 
301     /**
302      * Performs formatting of the field value for display and then converting the value back to its
303      * expected type from a string
304      *
305      * <p>
306      * Note property editors exist and are already registered for the basic Java types and the
307      * common Kuali types such as [@link KualiDecimal}. Registration with this property is only
308      * needed for custom property editors
309      * </p>
310      *
311      * @return PropertyEditor property editor instance to use for this field
312      */
313     public PropertyEditor getPropertyEditor() {
314         return propertyEditor;
315     }
316 
317     /**
318      * Setter for the custom property editor to use for the field
319      *
320      * @param propertyEditor
321      */
322     public void setPropertyEditor(PropertyEditor propertyEditor) {
323         this.propertyEditor = propertyEditor;
324     }
325 
326     /**
327      * Convenience setter for configuring a property editor by class
328      *
329      * @param propertyEditorClass
330      */
331     public void setPropertyEditorClass(Class<? extends PropertyEditor> propertyEditorClass) {
332         this.propertyEditor = ObjectUtils.newInstance(propertyEditorClass);
333     }
334 
335 	/**
336 	 * Directly validate simple fields, call completeValidation on Definition
337 	 * fields.
338 	 * 
339 	 * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#completeValidation()
340 	 */
341 	@Override
342 	public void completeValidation(Class<?> rootObjectClass, Class<?> otherObjectClass) {
343 		try {
344 			if (!DataDictionary.isPropertyOf(rootObjectClass, getName())) {
345 				throw new AttributeValidationException("property '" + getName() + "' is not a property of class '"
346 						+ rootObjectClass.getName() + "' (" + "" + ")");
347 			}
348 
349 			//TODO currently requiring a control or controlField, but this should not be case (AttrField should probably do the check)
350 			if (getControl() == null && getControlField() == null) {
351 				throw new AttributeValidationException("property '" + getName() + "' in class '"
352 						+ rootObjectClass.getName() + " does not have a control defined");
353 			}
354 			
355 			if(getControl() != null) {
356 			    getControl().completeValidation(rootObjectClass, otherObjectClass);
357 			}
358 
359 			if (attributeSecurity != null) {
360 				attributeSecurity.completeValidation(rootObjectClass, otherObjectClass);
361 			}
362 
363 			if (validationPattern != null) {
364 				validationPattern.completeValidation();
365 			}
366 
367 			if (formatterClass != null) {
368 				try {
369 					Class formatterClassObject = ClassUtils.getClass(ClassLoaderUtils.getDefaultClassLoader(),
370 							getFormatterClass());
371 					if (!Formatter.class.isAssignableFrom(formatterClassObject)) {
372 						throw new ClassValidationException("formatterClass is not a valid instance of "
373 								+ Formatter.class.getName() + " instead was: " + formatterClassObject.getName());
374 					}
375 				}
376 				catch (ClassNotFoundException e) {
377 					throw new ClassValidationException("formatterClass could not be found: " + getFormatterClass(), e);
378 				}
379 			}
380 		}
381 		catch (RuntimeException ex) {
382 			Logger.getLogger(getClass()).error(
383 					"Unable to validate attribute " + rootObjectClass + "." + getName() + ": " + ex.getMessage(), ex);
384 			throw ex;
385 		}
386 	}
387 
388 	/**
389 	 * @see java.lang.Object#toString()
390 	 */
391 	@Override
392 	public String toString() {
393 		return "AttributeDefinition for attribute " + getName();
394 	}
395 
396 	/**
397 	 * @return the attributeSecurity
398 	 */
399 	public AttributeSecurity getAttributeSecurity() {
400 		return this.attributeSecurity;
401 	}
402 
403 	/**
404 	 * @param attributeSecurity
405 	 *            the attributeSecurity to set
406 	 */
407 	public void setAttributeSecurity(AttributeSecurity attributeSecurity) {
408 		this.attributeSecurity = attributeSecurity;
409 	}
410 
411 	public boolean hasAttributeSecurity() {
412 		return (attributeSecurity != null);
413 	}
414 
415 	/**
416 	 * This overridden method ...
417 	 * 
418 	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
419 	 */
420 	@Override
421 	public void afterPropertiesSet() throws Exception {
422 		if (StringUtils.isEmpty(name)) {
423 			throw new RuntimeException("blank name for bean: " + id);
424 		}
425 	}
426 
427 	/**
428 	 * @return the unique
429 	 */
430 	public Boolean getUnique() {
431 		return this.unique;
432 	}
433 
434 	/**
435 	 * @param unique
436 	 *            the unique to set
437 	 */
438 	public void setUnique(Boolean unique) {
439 		this.unique = unique;
440 	}
441 
442 	/**
443 	 * Default <code>Control</code> to use when the attribute is to be rendered
444 	 * for the UI. Used by the UIF when a control is not defined for an
445 	 * <code>InputField</code>
446 	 * 
447 	 * @return Control instance
448 	 */
449 	public Control getControlField() {
450 		return this.controlField;
451 	}
452 
453 	/**
454 	 * Setter for the default control
455 	 * 
456 	 * @param controlField
457 	 */
458 	public void setControlField(Control controlField) {
459 		this.controlField = controlField;
460 	}
461 
462 	/**
463 	 * @return the minLength
464 	 */
465 	public Integer getMinLength() {
466 		return this.minLength;
467 	}
468 
469 	/**
470 	 * @param minLength the minLength to set
471 	 */
472 	public void setMinLength(Integer minLength) {
473 		this.minLength = minLength;
474 	}
475 
476 	/**
477 	 * @return the dataType
478 	 */
479 	@Override
480 	public DataType getDataType() {
481 		return this.dataType;
482 	}
483 
484 	/**
485 	 * @param dataType the dataType to set
486 	 */
487 	public void setDataType(DataType dataType) {
488 		this.dataType = dataType;
489 	}
490 	
491 	public void setDataType(String dataType) {
492 		this.dataType = DataType.valueOf(dataType);
493 	}
494 
495 	/**
496 	 * @return the customValidatorClass
497 	 */
498 	public String getCustomValidatorClass() {
499 		return this.customValidatorClass;
500 	}
501 
502 	/**
503 	 * @param customValidatorClass the customValidatorClass to set
504 	 */
505 	public void setCustomValidatorClass(String customValidatorClass) {
506 		this.customValidatorClass = customValidatorClass;
507 	}
508 
509 	/**
510 	 * @return the validChars
511 	 */
512 	@Override
513 	public ValidCharactersConstraint getValidCharactersConstraint() {
514 		return this.validCharactersConstraint;
515 	}
516 
517 	/**
518 	 * @param validCharactersConstraint the validChars to set
519 	 */
520 	public void setValidCharactersConstraint(ValidCharactersConstraint validCharactersConstraint) {
521 		this.validCharactersConstraint = validCharactersConstraint;
522 	}
523 
524 	/**
525 	 * @return the caseConstraint
526 	 */
527 	@Override
528 	public CaseConstraint getCaseConstraint() {
529 		return this.caseConstraint;
530 	}
531 
532 	/**
533 	 * @param caseConstraint the caseConstraint to set
534 	 */
535 	public void setCaseConstraint(CaseConstraint caseConstraint) {
536 		this.caseConstraint = caseConstraint;
537 	}
538 
539 	/**
540 	 * @return the requireConstraint
541 	 */
542 	@Override
543 	public List<PrerequisiteConstraint> getPrerequisiteConstraints() {
544 		return this.dependencyConstraints;
545 	}
546 
547 	/**
548 	 * @param dependencyConstraints the requireConstraint to set
549 	 */
550 	public void setPrerequisiteConstraints(List<PrerequisiteConstraint> dependencyConstraints) {
551 		this.dependencyConstraints = dependencyConstraints;
552 	}
553 
554 	/**
555 	 * @return the occursConstraint
556 	 */
557 	@Override
558 	public List<MustOccurConstraint> getMustOccurConstraints() {
559 		return this.mustOccurConstraints;
560 	}
561 
562 	/**
563 	 * @param mustOccurConstraints the occursConstraint to set
564 	 */
565 	public void setMustOccurConstraints(List<MustOccurConstraint> mustOccurConstraints) {
566 		this.mustOccurConstraints = mustOccurConstraints;
567 	}
568 
569 	/**
570 	 * @return the lookupDefinition
571 	 */
572 	public LookupConstraint getLookupDefinition() {
573 		return this.lookupDefinition;
574 	}
575 
576 	/**
577 	 * @param lookupDefinition the lookupDefinition to set
578 	 */
579 	public void setLookupDefinition(LookupConstraint lookupDefinition) {
580 		this.lookupDefinition = lookupDefinition;
581 	}
582 
583 	/**
584 	 * @return the lookupContextPath
585 	 */
586 	public String getLookupContextPath() {
587 		return this.lookupContextPath;
588 	}
589 
590 	/**
591 	 * @param lookupContextPath the lookupContextPath to set
592 	 */
593 	public void setLookupContextPath(String lookupContextPath) {
594 		this.lookupContextPath = lookupContextPath;
595 	}
596 
597 	/**
598 	 * @return the childEntryName
599 	 */
600 	public String getChildEntryName() {
601 		return this.childEntryName;
602 	}
603 
604 	/**
605 	 * @param childEntryName the childEntryName to set
606 	 */
607 	public void setChildEntryName(String childEntryName) {
608 		this.childEntryName = childEntryName;
609 	}
610 
611 
612 	
613     /**
614      * Instance of <code>KeyValluesFinder</code> that should be invoked to
615      * provide a List of values the field can have. Generally used to provide
616      * the options for a multi-value control or to validate the submitted field
617      * value
618      * 
619      * @return KeyValuesFinder instance
620      */
621     public KeyValuesFinder getOptionsFinder() {
622         return this.optionsFinder;
623     }
624 
625     /**
626      * Setter for the field's KeyValuesFinder instance
627      * 
628      * @param optionsFinder
629      */
630     public void setOptionsFinder(KeyValuesFinder optionsFinder) {
631         this.optionsFinder = optionsFinder;
632     }
633     
634     /**
635      * Setter that takes in the class name for the options finder and creates a
636      * new instance to use as the finder for the attribute field
637      * 
638      * @param optionsFinderClass
639      */
640     public void setOptionsFinderClass(Class<? extends KeyValuesFinder> optionsFinderClass) {
641         this.optionsFinder = ObjectUtils.newInstance(optionsFinderClass);
642     }
643 	
644 	public void setAdditionalDisplayAttributeName(String additionalDisplayAttributeName) {
645 		this.additionalDisplayAttributeName = additionalDisplayAttributeName;
646 	}
647 	
648 	public String getAdditionalDisplayAttributeName() {
649 		return this.additionalDisplayAttributeName;
650 	}
651 
652 	public void setAlternateDisplayAttributeName(String alternateDisplayAttributeName) {
653 		this.alternateDisplayAttributeName = alternateDisplayAttributeName;
654 	}
655 	
656 	public String getAlternateDisplayAttributeName() {
657 		return this.alternateDisplayAttributeName;
658 	}
659 
660 }