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.StringUtils; 019 import org.kuali.rice.krad.datadictionary.exception.DuplicateEntryException; 020 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 021 import org.kuali.rice.krad.datadictionary.state.StateMapping; 022 import org.kuali.rice.krad.datadictionary.validator.ValidationTrace; 023 import org.kuali.rice.krad.exception.ValidationException; 024 import org.springframework.beans.BeanUtils; 025 import org.springframework.beans.factory.InitializingBean; 026 027 import java.io.Serializable; 028 import java.util.ArrayList; 029 import java.util.LinkedHashMap; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Set; 033 034 /** 035 * Contains common properties and methods for data dictionary entries 036 * 037 * @author Kuali Rice Team (rice.collab@kuali.org) 038 */ 039 abstract public class DataDictionaryEntryBase extends DictionaryBeanBase implements DataDictionaryEntry, Serializable, InitializingBean { 040 protected List<AttributeDefinition> attributes; 041 protected List<ComplexAttributeDefinition> complexAttributes; 042 protected List<CollectionDefinition> collections; 043 protected List<RelationshipDefinition> relationships; 044 protected Map<String, AttributeDefinition> attributeMap; 045 protected Map<String, ComplexAttributeDefinition> complexAttributeMap; 046 protected Map<String, CollectionDefinition> collectionMap; 047 protected Map<String, RelationshipDefinition> relationshipMap; 048 049 protected StateMapping stateMapping; 050 051 public DataDictionaryEntryBase() { 052 this.attributes = new ArrayList<AttributeDefinition>(); 053 this.complexAttributes = new ArrayList<ComplexAttributeDefinition>(); 054 this.collections = new ArrayList<CollectionDefinition>(); 055 this.relationships = new ArrayList<RelationshipDefinition>(); 056 this.attributeMap = new LinkedHashMap<String, AttributeDefinition>(); 057 this.complexAttributeMap = new LinkedHashMap<String, ComplexAttributeDefinition>(); 058 this.collectionMap = new LinkedHashMap<String, CollectionDefinition>(); 059 this.relationshipMap = new LinkedHashMap<String, RelationshipDefinition>(); 060 } 061 062 /* Returns the given entry class (bo class or document class) */ 063 public abstract Class<?> getEntryClass(); 064 065 /** 066 * @param attributeName 067 * @return AttributeDefinition with the given name, or null if none with that name exists 068 */ 069 public AttributeDefinition getAttributeDefinition(String attributeName) { 070 if (StringUtils.isBlank(attributeName)) { 071 throw new IllegalArgumentException("invalid (blank) attributeName"); 072 } 073 return attributeMap.get(attributeName); 074 } 075 076 /** 077 * @return a Map containing all AttributeDefinitions associated with this BusinessObjectEntry, indexed by 078 * attributeName 079 */ 080 @BeanTagAttribute(name = "attributes", type = BeanTagAttribute.AttributeType.LISTBEAN) 081 public List<AttributeDefinition> getAttributes() { 082 return this.attributes; 083 } 084 085 /** 086 * @return the complexAttributes 087 */ 088 public List<ComplexAttributeDefinition> getComplexAttributes() { 089 return this.complexAttributes; 090 } 091 092 /** 093 * @param complexAttributes the complexAttributes to set 094 */ 095 public void setComplexAttributes(List<ComplexAttributeDefinition> complexAttributes) { 096 complexAttributeMap.clear(); 097 for (ComplexAttributeDefinition complexAttribute : complexAttributes) { 098 if (complexAttribute == null) { 099 throw new IllegalArgumentException("invalid (null) complexAttributeDefinition"); 100 } 101 String complexAttributeName = complexAttribute.getName(); 102 if (StringUtils.isBlank(complexAttributeName)) { 103 throw new ValidationException("invalid (blank) collectionName"); 104 } 105 106 if (complexAttributeMap.containsKey(complexAttribute)) { 107 throw new DuplicateEntryException("complex attribute '" 108 + complexAttribute 109 + "' already defined as an complex attribute for class '" 110 + getEntryClass().getName() 111 + "'"); 112 } else if (collectionMap.containsKey(complexAttributeName)) { 113 throw new DuplicateEntryException("complex attribute '" 114 + complexAttributeName 115 + "' already defined as a Collection for class '" 116 + getEntryClass().getName() 117 + "'"); 118 } else if (attributeMap.containsKey(complexAttributeName)) { 119 throw new DuplicateEntryException("complex attribute '" 120 + complexAttributeName 121 + "' already defined as an Attribute for class '" 122 + getEntryClass().getName() 123 + "'"); 124 } 125 126 complexAttributeMap.put(complexAttributeName, complexAttribute); 127 128 } 129 130 this.complexAttributes = complexAttributes; 131 } 132 133 /** 134 * @param collectionName 135 * @return CollectionDefinition with the given name, or null if none with that name exists 136 */ 137 public CollectionDefinition getCollectionDefinition(String collectionName) { 138 if (StringUtils.isBlank(collectionName)) { 139 throw new IllegalArgumentException("invalid (blank) collectionName"); 140 } 141 return collectionMap.get(collectionName); 142 } 143 144 /** 145 * @return a Map containing all CollectionDefinitions associated with this BusinessObjectEntry, indexed by 146 * collectionName 147 */ 148 @BeanTagAttribute(name = "collections", type = BeanTagAttribute.AttributeType.LISTBEAN) 149 public List<CollectionDefinition> getCollections() { 150 return this.collections; 151 } 152 153 /** 154 * @param relationshipName 155 * @return RelationshipDefinition with the given name, or null if none with that name exists 156 */ 157 public RelationshipDefinition getRelationshipDefinition(String relationshipName) { 158 if (StringUtils.isBlank(relationshipName)) { 159 throw new IllegalArgumentException("invalid (blank) relationshipName"); 160 } 161 return relationshipMap.get(relationshipName); 162 } 163 164 /** 165 * @return a Map containing all RelationshipDefinitions associated with this BusinessObjectEntry, indexed by 166 * relationshipName 167 */ 168 @BeanTagAttribute(name = "relationships", type = BeanTagAttribute.AttributeType.LISTBEAN) 169 public List<RelationshipDefinition> getRelationships() { 170 return this.relationships; 171 } 172 173 /** 174 * Directly validate simple fields, call completeValidation on Definition fields. 175 */ 176 public void completeValidation() { 177 178 for (AttributeDefinition attributeDefinition : attributes) { 179 attributeDefinition.completeValidation(getEntryClass(), null); 180 } 181 182 for (CollectionDefinition collectionDefinition : collections) { 183 collectionDefinition.completeValidation(getEntryClass(), null); 184 } 185 186 for (RelationshipDefinition relationshipDefinition : relationships) { 187 relationshipDefinition.completeValidation(getEntryClass(), null); 188 } 189 } 190 191 /** 192 * Directly validate simple fields, call completeValidation on Definition 193 * fields. 194 * 195 * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#completeValidation(org.kuali.rice.krad.datadictionary.validator.ValidationTrace) 196 */ 197 public void completeValidation(ValidationTrace tracer) { 198 for (AttributeDefinition definition : getAttributes()) { 199 definition.completeValidation(getEntryClass(), null, tracer.getCopy()); 200 } 201 for (CollectionDefinition definition : getCollections()) { 202 definition.completeValidation(getEntryClass(), null, tracer.getCopy()); 203 } 204 for (RelationshipDefinition definition : getRelationships()) { 205 definition.completeValidation(getEntryClass(), null, tracer.getCopy()); 206 } 207 } 208 209 /** 210 * The attributes element contains attribute 211 * elements. These define the specifications for business object fields. 212 * 213 * JSTL: attributes is a Map which is accessed by a key of "attributes". 214 * This map contains entries with the following keys: 215 * attributeName of first attribute 216 * attributeName of second attribute 217 * etc. 218 * 219 * The corresponding value for each entry is an attribute ExportMap. 220 * By the time the JSTL export happens, all attributeReferences will be 221 * indistinguishable from attributes. 222 * 223 * See AttributesMapBuilder.java 224 * 225 * The attribute element specifies the way in which a business object 226 * field appears on a screen for data entry or display purposes. These 227 * specifications include the following: 228 * The title and formatting of the field 229 * Descriptive information about the field 230 * The edits used at time of data-entry 231 * 232 * DD: See AttributeDefinition.java 233 * 234 * JSTL: attribute is a Map which is accessed using a key which is the attributeName 235 * of an attribute. Each entry contains the following keys: 236 * name (String) 237 * forceUppercase (boolean String) 238 * label (String) 239 * shortLabel (String, copied from label if not present) 240 * maxLength (String) 241 * exclusiveMin (bigdecimal String) 242 * exclusiveMax (bigdecimal String) 243 * validationPattern (Map, optional) 244 * required (boolean String) 245 * control (Map) 246 * summary (String) 247 * description (String) 248 * formatterClass (String, optional) 249 * fullClassName (String) 250 * displayWorkgroup(String, optional) 251 * displayMaskClass(String, optional) 252 * 253 * See AttributesMapBuilder.java 254 * Note: exclusiveMax is mapped from the inclusiveMax element! 255 * The validation logic seems to be assuming inclusiveMax. 256 */ 257 public void setAttributes(List<AttributeDefinition> attributes) { 258 attributeMap.clear(); 259 for (AttributeDefinition attribute : attributes) { 260 if (attribute == null) { 261 throw new IllegalArgumentException("invalid (null) attributeDefinition"); 262 } 263 String attributeName = attribute.getName(); 264 if (StringUtils.isBlank(attributeName)) { 265 throw new ValidationException("invalid (blank) attributeName"); 266 } 267 268 if (attributeMap.containsKey(attributeName)) { 269 throw new DuplicateEntryException("attribute '" 270 + attributeName 271 + "' already defined as an Attribute for class '" 272 + getEntryClass().getName() 273 + "'"); 274 } else if (collectionMap.containsKey(attributeName)) { 275 throw new DuplicateEntryException("attribute '" 276 + attributeName 277 + "' already defined as a Collection for class '" 278 + getEntryClass().getName() 279 + "'"); 280 } else if (complexAttributeMap.containsKey(attributeName)) { 281 throw new DuplicateEntryException("attribute '" 282 + attributeName 283 + "' already defined as an Complex Attribute for class '" 284 + getEntryClass().getName() 285 + "'"); 286 } 287 attributeMap.put(attributeName, attribute); 288 } 289 this.attributes = attributes; 290 } 291 292 /** 293 * The collections element contains collection elements. These define 294 * the lists of other business objects which are related to and 295 * defined in the business objects. 296 * 297 * JSTL: collections is a Map which is accessed by a key of "collections". 298 * This map contains entries with the following keys: 299 * name of first collection 300 * name of second collection 301 * etc. 302 * The corresponding value for each entry is a collection ExportMap. 303 * 304 * The collection element defines the name and description a 305 * list of objects related to the business object. 306 * 307 * DD: See CollectionDefinition.java. 308 * 309 * JSTL: collection is a Map which is accessed using a key which is the 310 * name of the collection. Each entry contains the following keys: 311 * name (String) 312 * label (String) 313 * shortLabel (String, copied from label if missing) 314 * elementLabel (String, copied from contained class if missing) 315 * summary (String) 316 * description (String) 317 * 318 * See CollectionsMapBuilder.java. 319 */ 320 public void setCollections(List<CollectionDefinition> collections) { 321 collectionMap.clear(); 322 for (CollectionDefinition collection : collections) { 323 if (collection == null) { 324 throw new IllegalArgumentException("invalid (null) collectionDefinition"); 325 } 326 String collectionName = collection.getName(); 327 if (StringUtils.isBlank(collectionName)) { 328 throw new ValidationException("invalid (blank) collectionName"); 329 } 330 331 if (collectionMap.containsKey(collectionName)) { 332 throw new DuplicateEntryException("collection '" 333 + collectionName 334 + "' already defined for class '" 335 + getEntryClass().getName() 336 + "'"); 337 } else if (attributeMap.containsKey(collectionName)) { 338 throw new DuplicateEntryException("collection '" 339 + collectionName 340 + "' already defined as an Attribute for class '" 341 + getEntryClass().getName() 342 + "'"); 343 } else if (complexAttributeMap.containsKey(collectionName)) { 344 throw new DuplicateEntryException("collection '" 345 + collectionName 346 + "' already defined as Complex Attribute for class '" 347 + getEntryClass().getName() 348 + "'"); 349 } 350 351 collectionMap.put(collectionName, collection); 352 353 } 354 this.collections = collections; 355 } 356 357 /** 358 * The relationships element contains relationship elements. 359 * These are used to map attribute names to fields in a reference object. 360 * 361 * JSTL: relationships is a Map which is accessed by a key of "relationships". 362 * This map contains entries with the following keys: 363 * objectAttributeName of first relationship 364 * objectAttributeName of second relationship 365 * etc. 366 * The corresponding value for each entry is a relationship ExportMap. 367 * 368 * The relationship element defines how primitive attributes of this 369 * class can be used to retrieve an instance of some related Object instance 370 * DD: See RelationshipDefinition.java. 371 * 372 * JSTL: relationship is a Map which is accessed using a key which is the 373 * objectAttributeName of a relationship. The map contains a single entry 374 * with a key of "primitiveAttributes" and value which is an attributesMap ExportMap. 375 * 376 * The attributesMap ExportMap contains the following keys: 377 * 0 (for first primitiveAttribute) 378 * 1 (for second primitiveAttribute) 379 * etc. 380 * The corresponding value for each entry is an primitiveAttribute ExportMap 381 * which contains the following keys: 382 * "sourceName" 383 * "targetName" 384 * 385 * See RelationshipsMapBuilder.java. 386 */ 387 public void setRelationships(List<RelationshipDefinition> relationships) { 388 this.relationships = relationships; 389 } 390 391 public Set<String> getCollectionNames() { 392 return collectionMap.keySet(); 393 } 394 395 public Set<String> getAttributeNames() { 396 return attributeMap.keySet(); 397 } 398 399 public Set<String> getRelationshipNames() { 400 return relationshipMap.keySet(); 401 } 402 403 /** 404 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() 405 */ 406 public void afterPropertiesSet() throws Exception { 407 if (relationships != null) { 408 relationshipMap.clear(); 409 for (RelationshipDefinition relationship : relationships) { 410 if (relationship == null) { 411 throw new IllegalArgumentException("invalid (null) relationshipDefinition"); 412 } 413 String relationshipName = relationship.getObjectAttributeName(); 414 if (StringUtils.isBlank(relationshipName)) { 415 throw new ValidationException("invalid (blank) relationshipName"); 416 } 417 relationship.setSourceClass(getEntryClass()); 418 relationshipMap.put(relationshipName, relationship); 419 } 420 } 421 422 //Populate attributes with nested attribute definitions 423 if (complexAttributes != null) { 424 for (ComplexAttributeDefinition complexAttribute : complexAttributes) { 425 addNestedAttributes(complexAttribute, complexAttribute.getName()); 426 } 427 } 428 } 429 430 /** 431 * recursively add complex attributes 432 * 433 * @param complexAttribute - the complex attribute to add recursively 434 * @param attrPath - a string representation of specifically which attribute (at some depth) is being accessed 435 */ 436 private void addNestedAttributes(ComplexAttributeDefinition complexAttribute, String attrPath) { 437 DataDictionaryEntryBase dataDictionaryEntry = (DataDictionaryEntryBase) complexAttribute.getDataObjectEntry(); 438 439 //Add attributes for the complex attibutes 440 for (AttributeDefinition attribute : dataDictionaryEntry.getAttributes()) { 441 String nestedAttributeName = attrPath + "." + attribute.getName(); 442 AttributeDefinition nestedAttribute = copyAttributeDefinition(attribute); 443 nestedAttribute.setName(nestedAttributeName); 444 445 if (!attributeMap.containsKey(nestedAttributeName)) { 446 this.attributes.add(nestedAttribute); 447 this.attributeMap.put(nestedAttributeName, nestedAttribute); 448 } 449 } 450 451 //Recursively add complex attributes 452 List<ComplexAttributeDefinition> nestedComplexAttributes = dataDictionaryEntry.getComplexAttributes(); 453 if (nestedComplexAttributes != null) { 454 for (ComplexAttributeDefinition nestedComplexAttribute : nestedComplexAttributes) { 455 addNestedAttributes(nestedComplexAttribute, attrPath + "." + nestedComplexAttribute.getName()); 456 } 457 } 458 } 459 460 /** 461 * copy an attribute definition 462 * 463 * @param attrDefToCopy - the attribute to create a copy of 464 * @return a copy of the attribute 465 */ 466 private AttributeDefinition copyAttributeDefinition(AttributeDefinition attrDefToCopy) { 467 AttributeDefinition attrDefCopy = new AttributeDefinition(); 468 469 try { 470 BeanUtils.copyProperties(attrDefToCopy, attrDefCopy, new String[]{"formatterClass"}); 471 472 //BeanUtils doesn't copy properties w/o "get" read methods, manually copy those here 473 attrDefCopy.setRequired(attrDefToCopy.isRequired()); 474 475 } catch (Exception e) { 476 e.printStackTrace(); 477 } 478 479 return attrDefCopy; 480 } 481 482 /** 483 * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#getStateMapping() 484 */ 485 @BeanTagAttribute(name = "stateMapping", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 486 public StateMapping getStateMapping() { 487 return stateMapping; 488 } 489 490 /** 491 * @see DataDictionaryEntry#setStateMapping(org.kuali.rice.krad.datadictionary.state.StateMapping) 492 */ 493 public void setStateMapping(StateMapping stateMapping) { 494 this.stateMapping = stateMapping; 495 } 496 }