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 }