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 }