View Javadoc
1   /**
2    * Copyright 2005-2014 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 java.io.Serializable;
19  import java.util.ArrayList;
20  import java.util.HashSet;
21  import java.util.LinkedHashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.rice.krad.data.KradDataServiceLocator;
28  import org.kuali.rice.krad.data.metadata.DataObjectAttribute;
29  import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
30  import org.kuali.rice.krad.data.metadata.DataObjectCollection;
31  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
32  import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
33  import org.kuali.rice.krad.data.provider.MetadataProvider;
34  import org.kuali.rice.krad.data.provider.annotation.UifDisplayHint;
35  import org.kuali.rice.krad.data.provider.annotation.UifDisplayHintType;
36  import org.kuali.rice.krad.datadictionary.exception.DuplicateEntryException;
37  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
38  import org.kuali.rice.krad.datadictionary.state.StateMapping;
39  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
40  import org.kuali.rice.krad.exception.ValidationException;
41  import org.springframework.beans.BeanUtils;
42  
43  /**
44   * Contains common properties and methods for data dictionary entries
45   *
46   * @author Kuali Rice Team (rice.collab@kuali.org)
47   */
48  abstract public class DataDictionaryEntryBase extends DictionaryBeanBase implements DataDictionaryEntry, Serializable {
49      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DataDictionaryEntryBase.class);
50      private static final long serialVersionUID = 5133059101016080533L;
51  
52      protected DataObjectMetadata dataObjectMetadata;
53  
54      protected List<AttributeDefinition> attributes;
55      protected List<ComplexAttributeDefinition> complexAttributes;
56      protected List<CollectionDefinition> collections;
57      protected List<RelationshipDefinition> relationships;
58      protected Map<String, AttributeDefinition> attributeMap;
59      protected Map<String, ComplexAttributeDefinition> complexAttributeMap;
60      protected Map<String, CollectionDefinition> collectionMap;
61  
62      protected Map<String, RelationshipDefinition> relationshipMap;
63  
64      protected StateMapping stateMapping;
65  
66      public DataDictionaryEntryBase() {
67          this.attributes = new ArrayList<AttributeDefinition>();
68          this.complexAttributes = new ArrayList<ComplexAttributeDefinition>();
69          this.collections = new ArrayList<CollectionDefinition>();
70          this.relationships = new ArrayList<RelationshipDefinition>();
71          this.attributeMap = new LinkedHashMap<String, AttributeDefinition>();
72          this.complexAttributeMap = new LinkedHashMap<String, ComplexAttributeDefinition>();
73          this.collectionMap = new LinkedHashMap<String, CollectionDefinition>();
74          this.relationshipMap = new LinkedHashMap<String, RelationshipDefinition>();
75      }
76  
77      /* Returns the given entry class (bo class or document class) */
78      public abstract Class<?> getEntryClass();
79  
80      /**
81       * @param attributeName
82       * @return AttributeDefinition with the given name, or null if none with that name exists
83       */
84      @Override
85      public AttributeDefinition getAttributeDefinition(String attributeName) {
86          if (StringUtils.isBlank(attributeName)) {
87              throw new IllegalArgumentException("invalid (blank) attributeName");
88          }
89          return attributeMap.get(attributeName);
90      }
91  
92      /**
93       * @return a Map containing all AttributeDefinitions associated with this BusinessObjectEntry, indexed by
94       *         attributeName
95       */
96      @BeanTagAttribute(name = "attributes", type = BeanTagAttribute.AttributeType.LISTBEAN)
97      public List<AttributeDefinition> getAttributes() {
98          return this.attributes;
99      }
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             for (RelationshipDefinition relationship : relationships) {
375                 if (relationship == null) {
376                     LOG.warn("Skipping invalid (null) relationshipDefinition on " + this);
377                     continue;
378                 }
379                 String relationshipName = relationship.getObjectAttributeName();
380                 if (StringUtils.isBlank(relationshipName)) {
381                     LOG.warn("Skipping invalid relationshipDefinition with blank relationshipName on " + this);
382                     continue;
383                 }
384                 relationship.setSourceClass(getEntryClass());
385                 relationshipMap.put(relationshipName, relationship);
386             }
387         }
388 
389         //Populate attributes with nested attribute definitions
390         if (complexAttributes != null) {
391             for (ComplexAttributeDefinition complexAttribute : complexAttributes) {
392                 if ( complexAttribute != null ) {
393                     addNestedAttributes(complexAttribute, complexAttribute.getName());
394                 }
395             }
396         }
397         for (AttributeDefinition definition : getAttributes()) {
398             definition.dataDictionaryPostProcessing();
399         }
400         for (CollectionDefinition definition : getCollections()) {
401             definition.dataDictionaryPostProcessing();
402         }
403         for (RelationshipDefinition definition : getRelationships()) {
404             definition.dataDictionaryPostProcessing();
405         }
406     }
407 
408     /**
409      * Directly validate simple fields, call completeValidation on Definition
410      * fields.
411      *
412      * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#completeValidation(org.kuali.rice.krad.datadictionary.validator.ValidationTrace)
413      */
414     @Override
415     public void completeValidation(ValidationTrace tracer) {
416         if ( getEntryClass() != null ) {
417             if ( LOG.isDebugEnabled() ) {
418                 LOG.debug( "Processing Validation for " + this.getClass().getSimpleName() + " for class: " + getEntryClass().getName() );
419             }
420             tracer.addBean(this.getClass().getSimpleName(), getEntryClass().getSimpleName());
421             for (AttributeDefinition definition : getAttributes()) {
422                 definition.completeValidation(getEntryClass(), null, tracer.getCopy());
423             }
424             for (CollectionDefinition definition : getCollections()) {
425                 definition.completeValidation(getEntryClass(), null, tracer.getCopy());
426             }
427             for (RelationshipDefinition definition : getRelationships()) {
428                 definition.completeValidation(getEntryClass(), null, tracer.getCopy());
429             }
430         } else {
431             tracer.addBean(this.getClass().getSimpleName(), toString() );
432             tracer.createError("DataObjectClass is not set,  remaining validations aborted", null );
433         }
434     }
435 
436     /**
437      * The attributes element contains attribute
438      * elements.  These define the specifications for business object fields.
439      *
440      * JSTL: attributes is a Map which is accessed by a key of "attributes".
441      * This map contains entries with the following keys:
442      * attributeName of first attribute
443      * attributeName of second attribute
444      * etc.
445      *
446      * The corresponding value for each entry is an attribute ExportMap.
447      * By the time the JSTL export happens, all attributeReferences will be
448      * indistinguishable from attributes.
449      *
450      * See AttributesMapBuilder.java
451      *
452      * The attribute element specifies the way in which a business object
453      * field appears on a screen for data entry or display purposes.  These
454      * specifications include the following:
455      * The title and formatting of the field
456      * Descriptive information about the field
457      * The edits used at time of data-entry
458      *
459      * DD: See AttributeDefinition.java
460      *
461      * JSTL: attribute is a Map which is accessed using a key which is the attributeName
462      * of an attribute.  Each entry contains the following keys:
463      * name (String)
464      * forceUppercase (boolean String)
465      * label (String)
466      * shortLabel (String, copied from label if not present)
467      * maxLength (String)
468      * exclusiveMin (bigdecimal String)
469      * exclusiveMax (bigdecimal String)
470      * validationPattern (Map, optional)
471      * required (boolean String)
472      * control (Map)
473      * summary (String)
474      * description (String)
475      * formatterClass (String, optional)
476      * fullClassName (String)
477      * displayWorkgroup(String, optional)
478      * displayMaskClass(String, optional)
479      *
480      * See AttributesMapBuilder.java
481      * Note: exclusiveMax is mapped from the inclusiveMax element!
482      * The validation logic seems to be assuming inclusiveMax.
483      */
484     public void setAttributes(List<AttributeDefinition> attributes) {
485         attributeMap.clear();
486         for (AttributeDefinition attribute : attributes) {
487             if (attribute == null) {
488                 throw new IllegalArgumentException("invalid (null) attributeDefinition");
489             }
490             String attributeName = attribute.getName();
491             if (StringUtils.isBlank(attributeName)) {
492                 throw new ValidationException("invalid (blank) attributeName");
493             }
494 
495             if (attributeMap.containsKey(attributeName)) {
496                 throw new DuplicateEntryException("attribute '"
497                         + attributeName
498                         + "' already defined as an Attribute for class '"
499                         + getEntryClass().getName()
500                         + "'");
501             } else if (collectionMap.containsKey(attributeName)) {
502                 throw new DuplicateEntryException("attribute '"
503                         + attributeName
504                         + "' already defined as a Collection for class '"
505                         + getEntryClass().getName()
506                         + "'");
507             } else if (complexAttributeMap.containsKey(attributeName)) {
508                 throw new DuplicateEntryException("attribute '"
509                         + attributeName
510                         + "' already defined as an Complex Attribute for class '"
511                         + getEntryClass().getName()
512                         + "'");
513             }
514             attributeMap.put(attributeName, attribute);
515         }
516         this.attributes = attributes;
517     }
518 
519     /**
520      * The collections element contains collection elements.  These define
521      * the lists of other business objects which are related to and
522      * defined in the business objects.
523      *
524      * JSTL: collections is a Map which is accessed by a key of "collections".
525      * This map contains entries with the following keys:
526      * name of first collection
527      * name of second collection
528      * etc.
529      * The corresponding value for each entry is a collection ExportMap.
530      *
531      * The collection element defines the name and description a
532      * list of objects related to the business object.
533      *
534      * DD: See CollectionDefinition.java.
535      *
536      * JSTL: collection is a Map which is accessed using a key which is the
537      * name of the collection.  Each entry contains the following keys:
538      * name (String)
539      * label (String)
540      * shortLabel (String, copied from label if missing)
541      * elementLabel (String, copied from contained class if missing)
542      * summary (String)
543      * description (String)
544      *
545      * See CollectionsMapBuilder.java.
546      */
547     public void setCollections(List<CollectionDefinition> collections) {
548         collectionMap.clear();
549         for (CollectionDefinition collection : collections) {
550             if (collection == null) {
551                 throw new IllegalArgumentException("invalid (null) collectionDefinition");
552             }
553             String collectionName = collection.getName();
554             if (StringUtils.isBlank(collectionName)) {
555                 throw new ValidationException("invalid (blank) collectionName");
556             }
557 
558             if (collectionMap.containsKey(collectionName)) {
559                 throw new DuplicateEntryException("collection '"
560                         + collectionName
561                         + "' already defined for class '"
562                         + getEntryClass().getName()
563                         + "'");
564             } else if (attributeMap.containsKey(collectionName)) {
565                 throw new DuplicateEntryException("collection '"
566                         + collectionName
567                         + "' already defined as an Attribute for class '"
568                         + getEntryClass().getName()
569                         + "'");
570             } else if (complexAttributeMap.containsKey(collectionName)) {
571                 throw new DuplicateEntryException("collection '"
572                         + collectionName
573                         + "' already defined as Complex Attribute for class '"
574                         + getEntryClass().getName()
575                         + "'");
576             }
577 
578             collectionMap.put(collectionName, collection);
579 
580         }
581         this.collections = collections;
582     }
583 
584     /**
585      * The relationships element contains relationship elements.
586      * These are used to map attribute names to fields in a reference object.
587      *
588      * JSTL: relationships is a Map which is accessed by a key of "relationships".
589      * This map contains entries with the following keys:
590      * objectAttributeName of first relationship
591      * objectAttributeName of second relationship
592      * etc.
593      * The corresponding value for each entry is a relationship ExportMap.
594      *
595      * The relationship element defines how primitive attributes of this
596      * class can be used to retrieve an instance of some related Object instance
597      * DD: See RelationshipDefinition.java.
598      *
599      * JSTL: relationship is a Map which is accessed using a key which is the
600      * objectAttributeName of a relationship.  The map contains a single entry
601      * with a key of "primitiveAttributes" and value which is an attributesMap ExportMap.
602      *
603      * The attributesMap ExportMap contains the following keys:
604      * 0   (for first primitiveAttribute)
605      * 1   (for second primitiveAttribute)
606      * etc.
607      * The corresponding value for each entry is an primitiveAttribute ExportMap
608      * which contains the following keys:
609      * "sourceName"
610      * "targetName"
611      *
612      * See RelationshipsMapBuilder.java.
613      */
614     public void setRelationships(List<RelationshipDefinition> relationships) {
615         this.relationships = relationships;
616     }
617 
618     public Set<String> getCollectionNames() {
619         return collectionMap.keySet();
620     }
621 
622     public Set<String> getAttributeNames() {
623         return attributeMap.keySet();
624     }
625 
626     public Set<String> getRelationshipNames() {
627         return relationshipMap.keySet();
628     }
629 
630     /**
631      * recursively add complex attributes
632      *
633      * @param complexAttribute - the complex attribute to add recursively
634      * @param attrPath - a string representation of specifically which attribute (at some depth) is being accessed
635      */
636     private void addNestedAttributes(ComplexAttributeDefinition complexAttribute, String attrPath) {
637         DataDictionaryEntryBase dataDictionaryEntry = (DataDictionaryEntryBase) complexAttribute.getDataObjectEntry();
638 
639         //Add attributes for the complex attibutes
640         for (AttributeDefinition attribute : dataDictionaryEntry.getAttributes()) {
641             String nestedAttributeName = attrPath + "." + attribute.getName();
642             AttributeDefinition nestedAttribute = copyAttributeDefinition(attribute);
643             nestedAttribute.setName(nestedAttributeName);
644 
645             if (!attributeMap.containsKey(nestedAttributeName)) {
646                 this.attributes.add(nestedAttribute);
647                 this.attributeMap.put(nestedAttributeName, nestedAttribute);
648             }
649         }
650 
651         //Recursively add complex attributes
652         List<ComplexAttributeDefinition> nestedComplexAttributes = dataDictionaryEntry.getComplexAttributes();
653         if (nestedComplexAttributes != null) {
654             for (ComplexAttributeDefinition nestedComplexAttribute : nestedComplexAttributes) {
655                 addNestedAttributes(nestedComplexAttribute, attrPath + "." + nestedComplexAttribute.getName());
656             }
657         }
658     }
659 
660     /**
661      * copy an attribute definition
662      *
663      * @param attrDefToCopy - the attribute to create a copy of
664      * @return a copy of the attribute
665      */
666     private AttributeDefinition copyAttributeDefinition(AttributeDefinition attrDefToCopy) {
667         AttributeDefinition attrDefCopy = new AttributeDefinition();
668 
669         try {
670             BeanUtils.copyProperties(attrDefToCopy, attrDefCopy, new String[]{"formatterClass"});
671 
672             //BeanUtils doesn't copy properties w/o "get" read methods, manually copy those here
673             attrDefCopy.setRequired(attrDefToCopy.isRequired());
674 
675         } catch (Exception e) {
676             LOG.warn( "Problem copying properties from attribute definition: " + attrDefToCopy, e);
677         }
678 
679         return attrDefCopy;
680     }
681 
682     /**
683      * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#getStateMapping()
684      */
685     @Override
686     @BeanTagAttribute(name = "stateMapping", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
687     public StateMapping getStateMapping() {
688         return stateMapping;
689     }
690 
691     /**
692      * @see DataDictionaryEntry#setStateMapping(org.kuali.rice.krad.datadictionary.state.StateMapping)
693      */
694     @Override
695     public void setStateMapping(StateMapping stateMapping) {
696         this.stateMapping = stateMapping;
697     }
698 
699     public boolean hasEmbeddedDataObjectMetadata() {
700         return getDataObjectMetadata() != null;
701     }
702 
703     public DataObjectMetadata getDataObjectMetadata() {
704         return dataObjectMetadata;
705     }
706 
707     public void setDataObjectMetadata(DataObjectMetadata dataObjectMetadata) {
708         this.dataObjectMetadata = dataObjectMetadata;
709     }
710 
711     public Map<String, RelationshipDefinition> getRelationshipMap() {
712         if(relationshipMap.isEmpty() && !getRelationships().isEmpty()){
713             for(RelationshipDefinition rel : getRelationships()){
714                 relationshipMap.put(rel.getObjectAttributeName(),rel);
715             }
716         }
717         return relationshipMap;
718     }
719 
720     public void setRelationshipMap(Map<String, RelationshipDefinition> relationshipMap) {
721         this.relationshipMap = relationshipMap;
722     }
723 }