View Javadoc
1   /**
2    * Copyright 2005-2016 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             
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 }