001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.krad.datadictionary; 017 018import java.io.Serializable; 019import java.util.ArrayList; 020import java.util.HashSet; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.commons.lang.StringUtils; 027import org.kuali.rice.krad.data.KradDataServiceLocator; 028import org.kuali.rice.krad.data.metadata.DataObjectAttribute; 029import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship; 030import org.kuali.rice.krad.data.metadata.DataObjectCollection; 031import org.kuali.rice.krad.data.metadata.DataObjectMetadata; 032import org.kuali.rice.krad.data.metadata.DataObjectRelationship; 033import org.kuali.rice.krad.data.provider.MetadataProvider; 034import org.kuali.rice.krad.data.provider.annotation.UifDisplayHint; 035import org.kuali.rice.krad.data.provider.annotation.UifDisplayHintType; 036import org.kuali.rice.krad.datadictionary.exception.DuplicateEntryException; 037import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 038import org.kuali.rice.krad.datadictionary.state.StateMapping; 039import org.kuali.rice.krad.datadictionary.validator.ValidationTrace; 040import org.kuali.rice.krad.exception.ValidationException; 041import org.springframework.beans.BeanUtils; 042 043/** 044 * Contains common properties and methods for data dictionary entries 045 * 046 * @author Kuali Rice Team (rice.collab@kuali.org) 047 */ 048abstract public class DataDictionaryEntryBase extends DictionaryBeanBase implements DataDictionaryEntry, Serializable { 049 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DataDictionaryEntryBase.class); 050 private static final long serialVersionUID = 5133059101016080533L; 051 052 protected DataObjectMetadata dataObjectMetadata; 053 054 protected List<AttributeDefinition> attributes; 055 protected List<ComplexAttributeDefinition> complexAttributes; 056 protected List<CollectionDefinition> collections; 057 protected List<RelationshipDefinition> relationships; 058 protected Map<String, AttributeDefinition> attributeMap; 059 protected Map<String, ComplexAttributeDefinition> complexAttributeMap; 060 protected Map<String, CollectionDefinition> collectionMap; 061 062 protected Map<String, RelationshipDefinition> relationshipMap; 063 064 protected StateMapping stateMapping; 065 066 public DataDictionaryEntryBase() { 067 this.attributes = new ArrayList<AttributeDefinition>(); 068 this.complexAttributes = new ArrayList<ComplexAttributeDefinition>(); 069 this.collections = new ArrayList<CollectionDefinition>(); 070 this.relationships = new ArrayList<RelationshipDefinition>(); 071 this.attributeMap = new LinkedHashMap<String, AttributeDefinition>(); 072 this.complexAttributeMap = new LinkedHashMap<String, ComplexAttributeDefinition>(); 073 this.collectionMap = new LinkedHashMap<String, CollectionDefinition>(); 074 this.relationshipMap = new LinkedHashMap<String, RelationshipDefinition>(); 075 } 076 077 /* Returns the given entry class (bo class or document class) */ 078 public abstract Class<?> getEntryClass(); 079 080 /** 081 * @param attributeName 082 * @return AttributeDefinition with the given name, or null if none with that name exists 083 */ 084 @Override 085 public AttributeDefinition getAttributeDefinition(String attributeName) { 086 if (StringUtils.isBlank(attributeName)) { 087 throw new IllegalArgumentException("invalid (blank) attributeName"); 088 } 089 return attributeMap.get(attributeName); 090 } 091 092 /** 093 * @return a Map containing all AttributeDefinitions associated with this BusinessObjectEntry, indexed by 094 * attributeName 095 */ 096 @BeanTagAttribute(name = "attributes", type = BeanTagAttribute.AttributeType.LISTBEAN) 097 public List<AttributeDefinition> getAttributes() { 098 return this.attributes; 099 } 100 101 /** 102 * @return the complexAttributes 103 */ 104 public List<ComplexAttributeDefinition> getComplexAttributes() { 105 return this.complexAttributes; 106 } 107 108 /** 109 * @param complexAttributes the complexAttributes to set 110 */ 111 public void setComplexAttributes(List<ComplexAttributeDefinition> complexAttributes) { 112 complexAttributeMap.clear(); 113 for (ComplexAttributeDefinition complexAttribute : complexAttributes) { 114 if (complexAttribute == null) { 115 throw new DataDictionaryException("invalid (null) complexAttributeDefinition on " + this); 116 } 117 String complexAttributeName = complexAttribute.getName(); 118 if (StringUtils.isBlank(complexAttributeName)) { 119 throw new DataDictionaryException("invalid (blank) complexAttributeName on " + this); 120 } 121 122 if (complexAttributeMap.containsKey(complexAttribute)) { 123 throw new DuplicateEntryException("complex attribute '" 124 + complexAttribute 125 + "' already defined as an complex attribute for class '" 126 + getEntryClass().getName() 127 + "'"); 128 } else if (collectionMap.containsKey(complexAttributeName)) { 129 throw new DuplicateEntryException("complex attribute '" 130 + complexAttributeName 131 + "' already defined as a Collection for class '" 132 + getEntryClass().getName() 133 + "'"); 134 } else if (attributeMap.containsKey(complexAttributeName)) { 135 throw new DuplicateEntryException("complex attribute '" 136 + complexAttributeName 137 + "' already defined as an Attribute for class '" 138 + getEntryClass().getName() 139 + "'"); 140 } 141 142 complexAttributeMap.put(complexAttributeName, complexAttribute); 143 144 } 145 146 this.complexAttributes = complexAttributes; 147 } 148 149 /** 150 * @param collectionName 151 * @return CollectionDefinition with the given name, or null if none with that name exists 152 */ 153 public CollectionDefinition getCollectionDefinition(String collectionName) { 154 if (StringUtils.isBlank(collectionName)) { 155 throw new IllegalArgumentException("invalid (blank) collectionName"); 156 } 157 return collectionMap.get(collectionName); 158 } 159 160 /** 161 * @return a Map containing all CollectionDefinitions associated with this BusinessObjectEntry, indexed by 162 * collectionName 163 */ 164 @BeanTagAttribute(name = "collections", type = BeanTagAttribute.AttributeType.LISTBEAN) 165 public List<CollectionDefinition> getCollections() { 166 return this.collections; 167 } 168 169 /** 170 * @param relationshipName 171 * @return RelationshipDefinition with the given name, or null if none with that name exists 172 */ 173 public RelationshipDefinition getRelationshipDefinition(String relationshipName) { 174 if (StringUtils.isBlank(relationshipName)) { 175 throw new IllegalArgumentException("invalid (blank) relationshipName"); 176 } 177 return getRelationshipMap().get(relationshipName); 178 } 179 180 /** 181 * @return a Map containing all RelationshipDefinitions associated with this BusinessObjectEntry, indexed by 182 * relationshipName 183 */ 184 @Override 185 @BeanTagAttribute(name = "relationships", type = BeanTagAttribute.AttributeType.LISTBEAN) 186 public List<RelationshipDefinition> getRelationships() { 187 return this.relationships; 188 } 189 190 /** 191 * Directly validate simple fields, call completeValidation on Definition fields. 192 */ 193 @Override 194 public void completeValidation() { 195 completeValidation( new ValidationTrace() ); 196 } 197 198 protected void embedMetadata() { 199 // Once we get to this point, the providers must also be loaded 200 // See if this DataObjectEntry's class has associated metadata 201 MetadataProvider metadataProvider = KradDataServiceLocator.getProviderRegistry().getMetadataProvider(getEntryClass()); 202 if ( metadataProvider != null ) { 203 dataObjectMetadata = metadataProvider.getMetadataForType(getEntryClass()); 204 if ( dataObjectMetadata == null ) { 205 LOG.warn( "No metadata defined for " + getEntryClass() + " even though provider returned." ); 206 } else { 207 // Since we have metadata, attempt to match it up on property name with the attributes defined 208 // We want to do this before calling the super.completeValidation() as it will validate that the 209 // AttributeDefinition objects have certain values and we want to take advantage of defaulting from 210 // the metadata model 211 injectMetadataIntoAttributes(dataObjectMetadata); 212 injectMetadataIntoCollections(dataObjectMetadata); 213 injectMetadataIntoRelationships(dataObjectMetadata); 214 } 215 } else { 216 LOG.info( "No metadata provider exists which handles: " + getEntryClass()); 217 } 218 } 219 220 /** 221 * Inject the metadata into the relationship definitions. Unlike attributes, in this case 222 * we only add missing relationships. If a relationship was defined for a given attribute 223 * we leave it alone. 224 * 225 * @param dataObjectMetadata 226 */ 227 protected void injectMetadataIntoRelationships(DataObjectMetadata dataObjectMetadata) { 228 List<RelationshipDefinition> relationships = getRelationships(); 229 boolean relationshipsChanged = false; 230 if ( relationships == null ) { 231 relationships = new ArrayList<RelationshipDefinition>(); 232 } 233 for ( DataObjectRelationship rel : dataObjectMetadata.getRelationships() ) { 234 if ( rel.getAttributeRelationships().isEmpty() ) { 235 // If we have no attributes to link with, we don't have anything to contribute 236 continue; 237 } 238 if ( StringUtils.isNotBlank(rel.getName()) ) { 239 RelationshipDefinition relationshipDefinition = getRelationshipDefinition(rel.getName()); 240 // no relationship defined for attribute - proceed and the given relationship parent is not 241 //included in a previous relationship so as not to add duplicates 242 if ( relationshipDefinition == null ){//&& !relationshipParentExists(rel.getName())) { 243 relationshipDefinition = new RelationshipDefinition(); 244 relationshipDefinition.setObjectAttributeName(rel.getName()); 245 relationshipDefinition.setSourceClass(getEntryClass()); 246 relationshipDefinition.setTargetClass(rel.getRelatedType()); 247 for ( DataObjectAttributeRelationship attrRel : rel.getAttributeRelationships() ) { 248 PrimitiveAttributeDefinition attrDef = new PrimitiveAttributeDefinition(); 249 attrDef.setSourceName(attrRel.getParentAttributeName()); 250 attrDef.setTargetName(attrRel.getChildAttributeName()); 251 relationshipDefinition.getPrimitiveAttributes().add(attrDef); 252 } 253 relationshipDefinition.setGeneratedFromMetadata(true); 254 relationshipDefinition.setEmbeddedDataObjectMetadata(true); 255 relationships.add(relationshipDefinition); 256 relationshipsChanged = true; 257 } 258 } else { 259 LOG.warn( "Relationship in metadata model contained blank name attribute: " + rel ); 260 } 261 } 262 } 263 264 protected void injectMetadataIntoCollections(DataObjectMetadata dataObjectMetadata) { 265 List<CollectionDefinition> collections = getCollections(); 266 boolean collectionsChanged = false; 267 if ( collections == null ) { 268 collections = new ArrayList<CollectionDefinition>(); 269 } 270 for ( DataObjectCollection coll : dataObjectMetadata.getCollections() ) { 271 if ( StringUtils.isNotBlank(coll.getName()) ) { 272 // Odd special case where a list attribute has been mapped as a singular attribute in the DD. 273 // Due to validation logic, a given name can not be both a collection and an attribute. 274 if ( getAttributeDefinition(coll.getName()) != null ) { 275 continue; 276 } 277 CollectionDefinition collectionDefinition = getCollectionDefinition(coll.getName()); 278 // no relationship defined for attribute - proceed 279 if ( collectionDefinition == null ) { 280 collectionDefinition = new CollectionDefinition(); 281 collectionDefinition.setName(coll.getName()); 282 collectionDefinition.setDataObjectClass(coll.getRelatedType().getName()); 283 collectionDefinition.setGeneratedFromMetadata(true); 284 collections.add(collectionDefinition); 285 // only need to trigger re-indexing if we add a new collection 286 collectionsChanged = true; 287 } 288 collectionDefinition.setDataObjectCollection(coll); 289 collectionDefinition.setEmbeddedDataObjectMetadata(true); 290 } else { 291 LOG.warn( "Relationship in metadata model contained blank name attribute: " + coll ); 292 } 293 } 294 // now that we are done, we need to set the resulting list back to the entry 295 // This triggers the needed indexing 296 if ( collectionsChanged ) { 297 setCollections(collections); 298 } 299 } 300 301 protected static final Set<String> EXCLUDED_PROPERTY_NAMES = new HashSet<String>(); 302 static { 303 EXCLUDED_PROPERTY_NAMES.add("objectId"); 304 EXCLUDED_PROPERTY_NAMES.add("versionNumber"); 305 } 306 307 protected void injectMetadataIntoAttributes( DataObjectMetadata dataObjectMetadata ) { 308 List<AttributeDefinition> originalDataObjectEntryAttributes = getAttributes(); 309 // this should never happen, but just in case someone was pathological enough to set it to null manually, let's be prepared 310 // We will use this to restore any UIF-Only attributes. 311 if ( originalDataObjectEntryAttributes == null ) { 312 originalDataObjectEntryAttributes = new ArrayList<AttributeDefinition>(); 313 } 314 // This is the list we will set 315 List<AttributeDefinition> dataObjectEntryAttributes = new ArrayList<AttributeDefinition>(); 316 // We are going to loop over the data in the metadata instead of the DD 317 // because we want to add attribute definitions if they do not exist 318 // and we don't care about attributes which only exist in the DD 319 for ( DataObjectAttribute attr : dataObjectMetadata.getAttributes() ) { 320 if ( StringUtils.isBlank(attr.getName())) { 321 LOG.warn( "Attribute in metadata model contained blank name attribute: " + attr ); 322 continue; 323 } 324 // certain old properties we never want to see 325 if ( EXCLUDED_PROPERTY_NAMES.contains( attr.getName() ) ) { 326 continue; 327 } 328 // if we've been told to exclude it, just ignore 329 if ( hasExcludedHint(attr) ) { 330 continue; 331 } 332 333 AttributeDefinition attributeDefinition = getAttributeDefinition(attr.getName()); 334 originalDataObjectEntryAttributes.remove(attributeDefinition); 335 336 if ( attributeDefinition == null ) { 337 attributeDefinition = new AttributeDefinition(); 338 attributeDefinition.setName(attr.getName()); 339 attributeDefinition.setGeneratedFromMetadata(true); 340 } 341 342 attributeDefinition.setDataObjectAttribute(attr); 343 attributeDefinition.setEmbeddedDataObjectMetadata(true); 344 dataObjectEntryAttributes.add(attributeDefinition); 345 } 346 // Add any which remain in this list to the end 347 dataObjectEntryAttributes.addAll(originalDataObjectEntryAttributes); 348 // now that we are done, we need to set the resulting list back to the entry 349 // This triggers the needed indexing 350 setAttributes(dataObjectEntryAttributes); 351 } 352 353 /** 354 * Check the {@link UifDisplayHint}s on an attribute, return true if any of them have the 355 * EXCLUDE type. 356 */ 357 protected boolean hasExcludedHint( DataObjectAttribute attr ) { 358 if ( attr.getDisplayHints() != null ) { 359 for ( UifDisplayHint hint : attr.getDisplayHints() ) { 360 if ( hint.value().equals(UifDisplayHintType.EXCLUDE) ) { 361 return true; 362 } 363 } 364 } 365 return false; 366 } 367 368 @Override 369 public void dataDictionaryPostProcessing() { 370 super.dataDictionaryPostProcessing(); 371 embedMetadata(); 372 if (relationships != null) { 373 relationshipMap.clear(); 374 375 for (RelationshipDefinition relationship : relationships) { 376 if (relationship == null) { 377 LOG.warn("Skipping invalid (null) relationshipDefinition on " + this); 378 continue; 379 } 380 381 String relationshipName = relationship.getObjectAttributeName(); 382 if (StringUtils.isBlank(relationshipName)) { 383 LOG.warn("Skipping invalid relationshipDefinition with blank relationshipName on " + this); 384 continue; 385 } 386 387 relationship.setSourceClass(getEntryClass()); 388 relationshipMap.put(relationshipName, relationship); 389 } 390 } 391 392 //Populate attributes with nested attribute definitions 393 if (complexAttributes != null) { 394 for (ComplexAttributeDefinition complexAttribute : complexAttributes) { 395 if ( complexAttribute != null ) { 396 addNestedAttributes(complexAttribute, complexAttribute.getName()); 397 } 398 } 399 } 400 401 for (AttributeDefinition definition : getAttributes()) { 402 definition.dataDictionaryPostProcessing(); 403 } 404 405 for (CollectionDefinition definition : getCollections()) { 406 definition.dataDictionaryPostProcessing(); 407 } 408 409 for (RelationshipDefinition definition : getRelationships()) { 410 definition.dataDictionaryPostProcessing(); 411 } 412 } 413 414 /** 415 * Directly validate simple fields, call completeValidation on Definition 416 * fields. 417 * 418 * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#completeValidation(org.kuali.rice.krad.datadictionary.validator.ValidationTrace) 419 */ 420 @Override 421 public void completeValidation(ValidationTrace tracer) { 422 if ( getEntryClass() != null ) { 423 if ( LOG.isDebugEnabled() ) { 424 LOG.debug( "Processing Validation for " + this.getClass().getSimpleName() + " for class: " + getEntryClass().getName() ); 425 } 426 tracer.addBean(this.getClass().getSimpleName(), getEntryClass().getSimpleName()); 427 for (AttributeDefinition definition : getAttributes()) { 428 definition.completeValidation(getEntryClass(), null, tracer.getCopy()); 429 } 430 for (CollectionDefinition definition : getCollections()) { 431 definition.completeValidation(getEntryClass(), null, tracer.getCopy()); 432 } 433 for (RelationshipDefinition definition : getRelationships()) { 434 definition.completeValidation(getEntryClass(), null, tracer.getCopy()); 435 } 436 } else { 437 tracer.addBean(this.getClass().getSimpleName(), toString() ); 438 tracer.createError("DataObjectClass is not set, remaining validations aborted", null ); 439 } 440 } 441 442 /** 443 * The attributes element contains attribute 444 * elements. These define the specifications for business object fields. 445 * 446 * JSTL: attributes is a Map which is accessed by a key of "attributes". 447 * This map contains entries with the following keys: 448 * attributeName of first attribute 449 * attributeName of second attribute 450 * etc. 451 * 452 * The corresponding value for each entry is an attribute ExportMap. 453 * By the time the JSTL export happens, all attributeReferences will be 454 * indistinguishable from attributes. 455 * 456 * See AttributesMapBuilder.java 457 * 458 * The attribute element specifies the way in which a business object 459 * field appears on a screen for data entry or display purposes. These 460 * specifications include the following: 461 * The title and formatting of the field 462 * Descriptive information about the field 463 * The edits used at time of data-entry 464 * 465 * DD: See AttributeDefinition.java 466 * 467 * JSTL: attribute is a Map which is accessed using a key which is the attributeName 468 * of an attribute. Each entry contains the following keys: 469 * name (String) 470 * forceUppercase (boolean String) 471 * label (String) 472 * shortLabel (String, copied from label if not present) 473 * maxLength (String) 474 * exclusiveMin (bigdecimal String) 475 * exclusiveMax (bigdecimal String) 476 * validationPattern (Map, optional) 477 * required (boolean String) 478 * control (Map) 479 * summary (String) 480 * description (String) 481 * formatterClass (String, optional) 482 * fullClassName (String) 483 * displayWorkgroup(String, optional) 484 * displayMaskClass(String, optional) 485 * 486 * See AttributesMapBuilder.java 487 * Note: exclusiveMax is mapped from the inclusiveMax element! 488 * The validation logic seems to be assuming inclusiveMax. 489 */ 490 public void setAttributes(List<AttributeDefinition> attributes) { 491 attributeMap.clear(); 492 for (AttributeDefinition attribute : attributes) { 493 if (attribute == null) { 494 throw new IllegalArgumentException("invalid (null) attributeDefinition"); 495 } 496 String attributeName = attribute.getName(); 497 if (StringUtils.isBlank(attributeName)) { 498 throw new ValidationException("invalid (blank) attributeName"); 499 } 500 501 if (attributeMap.containsKey(attributeName)) { 502 throw new DuplicateEntryException("attribute '" 503 + attributeName 504 + "' already defined as an Attribute for class '" 505 + getEntryClass().getName() 506 + "'"); 507 } else if (collectionMap.containsKey(attributeName)) { 508 throw new DuplicateEntryException("attribute '" 509 + attributeName 510 + "' already defined as a Collection for class '" 511 + getEntryClass().getName() 512 + "'"); 513 } else if (complexAttributeMap.containsKey(attributeName)) { 514 throw new DuplicateEntryException("attribute '" 515 + attributeName 516 + "' already defined as an Complex Attribute for class '" 517 + getEntryClass().getName() 518 + "'"); 519 } 520 attributeMap.put(attributeName, attribute); 521 } 522 this.attributes = attributes; 523 } 524 525 /** 526 * The collections element contains collection elements. These define 527 * the lists of other business objects which are related to and 528 * defined in the business objects. 529 * 530 * JSTL: collections is a Map which is accessed by a key of "collections". 531 * This map contains entries with the following keys: 532 * name of first collection 533 * name of second collection 534 * etc. 535 * The corresponding value for each entry is a collection ExportMap. 536 * 537 * The collection element defines the name and description a 538 * list of objects related to the business object. 539 * 540 * DD: See CollectionDefinition.java. 541 * 542 * JSTL: collection is a Map which is accessed using a key which is the 543 * name of the collection. Each entry contains the following keys: 544 * name (String) 545 * label (String) 546 * shortLabel (String, copied from label if missing) 547 * elementLabel (String, copied from contained class if missing) 548 * summary (String) 549 * description (String) 550 * 551 * See CollectionsMapBuilder.java. 552 */ 553 public void setCollections(List<CollectionDefinition> collections) { 554 collectionMap.clear(); 555 for (CollectionDefinition collection : collections) { 556 if (collection == null) { 557 throw new IllegalArgumentException("invalid (null) collectionDefinition"); 558 } 559 String collectionName = collection.getName(); 560 if (StringUtils.isBlank(collectionName)) { 561 throw new ValidationException("invalid (blank) collectionName"); 562 } 563 564 if (collectionMap.containsKey(collectionName)) { 565 throw new DuplicateEntryException("collection '" 566 + collectionName 567 + "' already defined for class '" 568 + getEntryClass().getName() 569 + "'"); 570 } else if (attributeMap.containsKey(collectionName)) { 571 throw new DuplicateEntryException("collection '" 572 + collectionName 573 + "' already defined as an Attribute for class '" 574 + getEntryClass().getName() 575 + "'"); 576 } else if (complexAttributeMap.containsKey(collectionName)) { 577 throw new DuplicateEntryException("collection '" 578 + collectionName 579 + "' already defined as Complex Attribute for class '" 580 + getEntryClass().getName() 581 + "'"); 582 } 583 584 collectionMap.put(collectionName, collection); 585 586 } 587 this.collections = collections; 588 } 589 590 /** 591 * The relationships element contains relationship elements. 592 * These are used to map attribute names to fields in a reference object. 593 * 594 * JSTL: relationships is a Map which is accessed by a key of "relationships". 595 * This map contains entries with the following keys: 596 * objectAttributeName of first relationship 597 * objectAttributeName of second relationship 598 * etc. 599 * The corresponding value for each entry is a relationship ExportMap. 600 * 601 * The relationship element defines how primitive attributes of this 602 * class can be used to retrieve an instance of some related Object instance 603 * DD: See RelationshipDefinition.java. 604 * 605 * JSTL: relationship is a Map which is accessed using a key which is the 606 * objectAttributeName of a relationship. The map contains a single entry 607 * with a key of "primitiveAttributes" and value which is an attributesMap ExportMap. 608 * 609 * The attributesMap ExportMap contains the following keys: 610 * 0 (for first primitiveAttribute) 611 * 1 (for second primitiveAttribute) 612 * etc. 613 * The corresponding value for each entry is an primitiveAttribute ExportMap 614 * which contains the following keys: 615 * "sourceName" 616 * "targetName" 617 * 618 * See RelationshipsMapBuilder.java. 619 */ 620 public void setRelationships(List<RelationshipDefinition> relationships) { 621 this.relationships = relationships; 622 } 623 624 public Set<String> getCollectionNames() { 625 return collectionMap.keySet(); 626 } 627 628 public Set<String> getAttributeNames() { 629 return attributeMap.keySet(); 630 } 631 632 public Set<String> getRelationshipNames() { 633 return relationshipMap.keySet(); 634 } 635 636 /** 637 * recursively add complex attributes 638 * 639 * @param complexAttribute - the complex attribute to add recursively 640 * @param attrPath - a string representation of specifically which attribute (at some depth) is being accessed 641 */ 642 private void addNestedAttributes(ComplexAttributeDefinition complexAttribute, String attrPath) { 643 DataDictionaryEntryBase dataDictionaryEntry = (DataDictionaryEntryBase) complexAttribute.getDataObjectEntry(); 644 645 //Add attributes for the complex attibutes 646 for (AttributeDefinition attribute : dataDictionaryEntry.getAttributes()) { 647 String nestedAttributeName = attrPath + "." + attribute.getName(); 648 AttributeDefinition nestedAttribute = copyAttributeDefinition(attribute); 649 nestedAttribute.setName(nestedAttributeName); 650 651 if (!attributeMap.containsKey(nestedAttributeName)) { 652 this.attributes.add(nestedAttribute); 653 this.attributeMap.put(nestedAttributeName, nestedAttribute); 654 } 655 } 656 657 //Recursively add complex attributes 658 List<ComplexAttributeDefinition> nestedComplexAttributes = dataDictionaryEntry.getComplexAttributes(); 659 if (nestedComplexAttributes != null) { 660 for (ComplexAttributeDefinition nestedComplexAttribute : nestedComplexAttributes) { 661 addNestedAttributes(nestedComplexAttribute, attrPath + "." + nestedComplexAttribute.getName()); 662 } 663 } 664 } 665 666 /** 667 * copy an attribute definition 668 * 669 * @param attrDefToCopy - the attribute to create a copy of 670 * @return a copy of the attribute 671 */ 672 private AttributeDefinition copyAttributeDefinition(AttributeDefinition attrDefToCopy) { 673 AttributeDefinition attrDefCopy = new AttributeDefinition(); 674 675 try { 676 BeanUtils.copyProperties(attrDefToCopy, attrDefCopy, new String[]{"formatterClass"}); 677 678 //BeanUtils doesn't copy properties w/o "get" read methods, manually copy those here 679 attrDefCopy.setRequired(attrDefToCopy.isRequired()); 680 681 } catch (Exception e) { 682 LOG.warn( "Problem copying properties from attribute definition: " + attrDefToCopy, e); 683 } 684 685 return attrDefCopy; 686 } 687 688 /** 689 * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#getStateMapping() 690 */ 691 @Override 692 @BeanTagAttribute(name = "stateMapping", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 693 public StateMapping getStateMapping() { 694 return stateMapping; 695 } 696 697 /** 698 * @see DataDictionaryEntry#setStateMapping(org.kuali.rice.krad.datadictionary.state.StateMapping) 699 */ 700 @Override 701 public void setStateMapping(StateMapping stateMapping) { 702 this.stateMapping = stateMapping; 703 } 704 705 public boolean hasEmbeddedDataObjectMetadata() { 706 return getDataObjectMetadata() != null; 707 } 708 709 public DataObjectMetadata getDataObjectMetadata() { 710 return dataObjectMetadata; 711 } 712 713 public void setDataObjectMetadata(DataObjectMetadata dataObjectMetadata) { 714 this.dataObjectMetadata = dataObjectMetadata; 715 } 716 717 public Map<String, RelationshipDefinition> getRelationshipMap() { 718 if(relationshipMap.isEmpty() && !getRelationships().isEmpty()){ 719 for(RelationshipDefinition rel : getRelationships()){ 720 relationshipMap.put(rel.getObjectAttributeName(),rel); 721 } 722 } 723 return relationshipMap; 724 } 725 726 public void setRelationshipMap(Map<String, RelationshipDefinition> relationshipMap) { 727 this.relationshipMap = relationshipMap; 728 } 729}