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}