001    /*
002     * Copyright 2005-2007 The Kuali Foundation
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kns.datadictionary;
017    
018    import java.util.ArrayList;
019    import java.util.LinkedHashMap;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Set;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.kuali.rice.kns.datadictionary.exception.DuplicateEntryException;
026    import org.kuali.rice.kns.exception.ValidationException;
027    import org.springframework.beans.BeanUtils;
028    import org.springframework.beans.factory.InitializingBean;
029    
030    /**
031     * Contains common properties and methods for data dictionary entries.
032     * 
033     * 
034     */
035    abstract public class DataDictionaryEntryBase implements DataDictionaryEntry, InitializingBean {
036    
037        protected List<AttributeDefinition> attributes;
038        protected List<ComplexAttributeDefinition> complexAttributes;
039        protected List<CollectionDefinition> collections;
040        protected List<RelationshipDefinition> relationships;
041        protected Map<String, AttributeDefinition> attributeMap;
042        protected Map<String, ComplexAttributeDefinition> complexAttributeMap;
043        protected Map<String, CollectionDefinition> collectionMap;
044        protected Map<String, RelationshipDefinition> relationshipMap;
045        
046        public DataDictionaryEntryBase() {
047            this.attributes = new ArrayList<AttributeDefinition>();
048            this.collections = new ArrayList<CollectionDefinition>();
049            this.relationships = new ArrayList<RelationshipDefinition>();
050            this.attributeMap = new LinkedHashMap<String, AttributeDefinition>();
051            this.collectionMap = new LinkedHashMap<String, CollectionDefinition>();
052            this.relationshipMap = new LinkedHashMap<String, RelationshipDefinition>();
053        }
054        
055        /* Returns the given entry class (bo class or document class) */
056        public abstract Class<?> getEntryClass();
057        
058        /**
059         * @param attributeName
060         * @return AttributeDefinition with the given name, or null if none with that name exists
061         */
062        public AttributeDefinition getAttributeDefinition(String attributeName) {
063            if (StringUtils.isBlank(attributeName)) {
064                throw new IllegalArgumentException("invalid (blank) attributeName");
065            }
066            return attributeMap.get(attributeName);
067        }
068    
069        /**
070         * @return a Map containing all AttributeDefinitions associated with this BusinessObjectEntry, indexed by attributeName
071         */
072        public List<AttributeDefinition> getAttributes() {
073            return this.attributes;
074        }
075        
076        /**
077             * @return the complexAttributes
078             */
079            public List<ComplexAttributeDefinition> getComplexAttributes() {
080                    return this.complexAttributes;
081            }
082    
083            /**
084             * @param complexAttributes the complexAttributes to set
085             */
086            public void setComplexAttributes(
087                            List<ComplexAttributeDefinition> complexAttributes) {
088                    this.complexAttributes = complexAttributes;
089            }
090    
091        /**
092         * @param collectionName
093         * @return CollectionDefinition with the given name, or null if none with that name exists
094         */
095        public CollectionDefinition getCollectionDefinition(String collectionName) {
096            if (StringUtils.isBlank(collectionName)) {
097                throw new IllegalArgumentException("invalid (blank) collectionName");
098            }
099            return collectionMap.get(collectionName);
100        }
101    
102        /**
103         * @return a Map containing all CollectionDefinitions associated with this BusinessObjectEntry, indexed by collectionName
104         */
105        public List<CollectionDefinition> getCollections() {
106            return this.collections;
107        }
108    
109        /**
110         * @param relationshipName
111         * @return RelationshipDefinition with the given name, or null if none with that name exists
112         */
113        public RelationshipDefinition getRelationshipDefinition(String relationshipName) {
114            if (StringUtils.isBlank(relationshipName)) {
115                throw new IllegalArgumentException("invalid (blank) relationshipName");
116            }
117            return relationshipMap.get(relationshipName);
118        }
119    
120        /**
121         * @return a Map containing all RelationshipDefinitions associated with this BusinessObjectEntry, indexed by relationshipName
122         */
123        public List<RelationshipDefinition> getRelationships() {
124            return this.relationships;
125        }
126    
127    
128        /**
129         * Directly validate simple fields, call completeValidation on Definition fields.
130         */
131        public void completeValidation() {
132            
133            for ( AttributeDefinition attributeDefinition : attributes ) {
134                attributeDefinition.completeValidation(getEntryClass(), null);
135            }
136    
137            for ( CollectionDefinition collectionDefinition : collections ) {
138                collectionDefinition.completeValidation(getEntryClass(), null);
139            }
140    
141            for ( RelationshipDefinition relationshipDefinition : relationships ) {
142                relationshipDefinition.completeValidation(getEntryClass(), null);
143            }
144        }
145    
146        /**
147                The attributes element contains attribute 
148                elements.  These define the specifications for business object fields.
149    
150                JSTL: attributes is a Map which is accessed by a key of "attributes".
151                This map contains entries with the following keys:
152                    * attributeName of first attribute
153                    * attributeName of second attribute
154                    etc.
155    
156                The corresponding value for each entry is an attribute ExportMap.
157                By the time the JSTL export happens, all attributeReferences will be
158                indistinguishable from attributes.
159    
160                See AttributesMapBuilder.java
161    
162                    The attribute element specifies the way in which a business object
163                    field appears on a screen for data entry or display purposes.  These
164                    specifications include the following:
165                    * The title and formatting of the field
166                    * Descriptive information about the field
167                    * The edits used at time of data-entry
168    
169                    DD: See AttributeDefinition.java
170    
171                    JSTL: attribute is a Map which is accessed using a key which is the attributeName
172                    of an attribute.  Each entry contains the following keys:
173                        * name (String)
174                        * forceUppercase (boolean String)
175                        * label (String)
176                        * shortLabel (String, copied from label if not present)
177                        * maxLength (String)
178                        * exclusiveMin (bigdecimal String)
179                        * exclusiveMax (bigdecimal String)
180                        * validationPattern (Map, optional)
181                        * required (boolean String)
182                        * control (Map)
183                        * summary (String)
184                        * description (String)
185                        * formatterClass (String, optional)
186                        * fullClassName (String)
187                        * displayWorkgroup(String, optional)
188                        * displayMaskClass(String, optional)
189    
190                    See AttributesMapBuilder.java
191                    Note: exclusiveMax is mapped from the inclusiveMax element!
192                    The validation logic seems to be assuming inclusiveMax.
193         *
194         */
195        public void setAttributes(List<AttributeDefinition> attributes) {
196            attributeMap.clear();
197            for ( AttributeDefinition attribute : attributes ) {
198                if (attribute == null) {
199                    throw new IllegalArgumentException("invalid (null) attributeDefinition");
200                }
201                String attributeName = attribute.getName();
202                if (StringUtils.isBlank(attributeName)) {
203                    throw new ValidationException("invalid (blank) attributeName");
204                }
205    
206                if (attributeMap.containsKey(attributeName)) {
207                    throw new DuplicateEntryException("collection '" + attributeName + "' already defined as an Attribute for class '" + getEntryClass().getName() + "'");
208                } else if (collectionMap.containsKey(attributeName)) {
209                    throw new DuplicateEntryException("attribute '" + attributeName + "' already defined as a Collection for class '" + getEntryClass().getName() + "'");
210                }
211    
212                attributeMap.put(attributeName, attribute);            
213            }
214            this.attributes = attributes;
215        }
216    
217        /**
218                The collections element contains collection elements.  These define
219                the lists of other business objects which are related to and
220                defined in the business objects.
221    
222                JSTL: collections is a Map which is accessed by a key of "collections".
223                This map contains entries with the following keys:
224                    * name of first collection
225                    * name of second collection
226                    etc.
227                The corresponding value for each entry is a collection ExportMap.
228    
229                The collection element defines the name and description a
230                list of objects related to the business object.
231    
232                DD: See CollectionDefinition.java.
233    
234                JSTL: collection is a Map which is accessed using a key which is the
235                name of the collection.  Each entry contains the following keys:
236                    * name (String)
237                    * label (String)
238                    * shortLabel (String, copied from label if missing)
239                    * elementLabel (String, copied from contained class if missing)
240                    * summary (String)
241                    * description (String)
242    
243                See CollectionsMapBuilder.java.
244         */
245        public void setCollections(List<CollectionDefinition> collections) {
246            collectionMap.clear();
247            for ( CollectionDefinition collection : collections ) {
248                if (collection == null) {
249                    throw new IllegalArgumentException("invalid (null) collectionDefinition");
250                }
251                String collectionName = collection.getName();
252                if (StringUtils.isBlank(collectionName)) {
253                    throw new ValidationException("invalid (blank) collectionName");
254                }
255    
256                if (collectionMap.containsKey(collectionName)) {
257                    throw new DuplicateEntryException("collection '" + collectionName + "' already defined for class '" + getEntryClass().getName() + "'");
258                } else if (attributeMap.containsKey(collectionName)) {
259                    throw new DuplicateEntryException("collection '" + collectionName + "' already defined as an Attribute for class '" + getEntryClass().getName() + "'");
260                }
261    
262                collectionMap.put(collectionName, collection);
263                
264            }
265            this.collections = collections;
266        }
267    
268        /**
269                The relationships element contains relationship elements.
270                These are used to map attribute names to fields in a reference object.
271    
272                JSTL: relationships is a Map which is accessed by a key of "relationships".
273                This map contains entries with the following keys:
274                    * objectAttributeName of first relationship
275                    * objectAttributeName of second relationship
276                    etc.
277                The corresponding value for each entry is a relationship ExportMap.
278    
279                The relationship element defines how primitive attributes of this
280                class can be used to retrieve an instance of some related Object instance
281                DD: See RelationshipDefinition.java.
282    
283                JSTL: relationship is a Map which is accessed using a key which is the
284                objectAttributeName of a relationship.  The map contains a single entry
285                with a key of "primitiveAttributes" and value which is an attributesMap ExportMap.
286    
287                The attributesMap ExportMap contains the following keys:
288                    * 0   (for first primitiveAttribute)
289                    * 1   (for second primitiveAttribute)
290                    etc.
291                The corresponding value for each entry is an primitiveAttribute ExportMap
292                which contains the following keys:
293                    * "sourceName"
294                    * "targetName"
295    
296                See RelationshipsMapBuilder.java.
297                
298         */
299        public void setRelationships(List<RelationshipDefinition> relationships) {
300            this.relationships = relationships;
301        }
302    
303        public Set<String> getCollectionNames() {
304            return collectionMap.keySet();
305        }
306        
307        public Set<String> getAttributeNames() {
308            return attributeMap.keySet();
309        }
310    
311        public Set<String> getRelationshipNames() {
312            return relationshipMap.keySet();
313        }
314        
315        /**
316         * This overridden method ...
317         * 
318         * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
319         */
320        public void afterPropertiesSet() throws Exception {
321            if ( relationships != null ) {
322                relationshipMap.clear();
323                for ( RelationshipDefinition relationship : relationships ) {            
324                    if (relationship == null) {
325                        throw new IllegalArgumentException("invalid (null) relationshipDefinition");
326                    }
327                    String relationshipName = relationship.getObjectAttributeName();
328                    if (StringUtils.isBlank(relationshipName)) {
329                        throw new ValidationException("invalid (blank) relationshipName");
330                    }
331                    relationship.setSourceClass(getEntryClass());
332                    relationshipMap.put(relationshipName, relationship);
333                }
334            }
335            
336            //Populate attributes with nested attribute definitions
337            if (complexAttributes != null){
338                    for (ComplexAttributeDefinition complexAttribute:complexAttributes){
339                            addNestedAttributes(complexAttribute, complexAttribute.getName());
340                    }
341            }
342            }
343        
344        private void addNestedAttributes(ComplexAttributeDefinition complexAttribute, String attrPath){
345            DataDictionaryEntryBase dataDictionaryEntry = (DataDictionaryEntryBase)complexAttribute.getDataObjectEntry();
346            
347            //Add attributes for the complex attibutes
348            for (AttributeDefinition attribute:dataDictionaryEntry.getAttributes()){
349                    String nestedAttributeName = attrPath + "." + attribute.getName();
350                    AttributeDefinition nestedAttribute = copyAttributeDefinition(attribute);
351                    nestedAttribute.setName(nestedAttributeName);
352                    
353                    if (!attributeMap.containsValue(nestedAttributeName)){
354                            this.attributes.add(nestedAttribute);
355                            this.attributeMap.put(nestedAttributeName, nestedAttribute);
356                    }
357            }       
358            
359            //Recursively add complex attributes
360            List<ComplexAttributeDefinition> nestedComplexAttributes = dataDictionaryEntry.getComplexAttributes();
361            if (nestedComplexAttributes != null){
362                    for (ComplexAttributeDefinition nestedComplexAttribute:nestedComplexAttributes){
363                            addNestedAttributes(nestedComplexAttribute, attrPath);
364                    }
365            }
366        }
367        
368        private AttributeDefinition copyAttributeDefinition(AttributeDefinition attrDefToCopy){
369            AttributeDefinition attrDefCopy = new AttributeDefinition();
370            
371            try {
372                    
373                            BeanUtils.copyProperties(attrDefToCopy, attrDefToCopy, new String[] { "formatterClass" });
374                    } catch (Exception e) {
375                            e.printStackTrace();
376                    }
377                    
378                    return attrDefCopy;
379        }
380    }