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.LinkedHashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.kuali.rice.krad.datadictionary.exception.DuplicateEntryException;
27  import org.kuali.rice.krad.exception.ValidationException;
28  import org.springframework.beans.BeanUtils;
29  import org.springframework.beans.factory.InitializingBean;
30  
31  /**
32   * Contains common properties and methods for data dictionary entries.
33   * 
34   * @author Kuali Rice Team (rice.collab@kuali.org)
35   */
36  abstract public class DataDictionaryEntryBase implements DataDictionaryEntry, Serializable, InitializingBean {
37      protected List<AttributeDefinition> attributes;
38      protected List<ComplexAttributeDefinition> complexAttributes;
39      protected List<CollectionDefinition> collections;
40      protected List<RelationshipDefinition> relationships;
41      protected Map<String, AttributeDefinition> attributeMap;
42      protected Map<String, ComplexAttributeDefinition> complexAttributeMap;
43      protected Map<String, CollectionDefinition> collectionMap;
44      protected Map<String, RelationshipDefinition> relationshipMap;
45      
46      public DataDictionaryEntryBase() {
47          this.attributes = new ArrayList<AttributeDefinition>();
48          this.complexAttributes = new ArrayList<ComplexAttributeDefinition>();
49          this.collections = new ArrayList<CollectionDefinition>();
50          this.relationships = new ArrayList<RelationshipDefinition>();
51          this.attributeMap = new LinkedHashMap<String, AttributeDefinition>();
52          this.complexAttributeMap = new LinkedHashMap<String, ComplexAttributeDefinition>();
53          this.collectionMap = new LinkedHashMap<String, CollectionDefinition>();
54          this.relationshipMap = new LinkedHashMap<String, RelationshipDefinition>();
55      }
56      
57      /* Returns the given entry class (bo class or document class) */
58      public abstract Class<?> getEntryClass();
59      
60      /**
61       * @param attributeName
62       * @return AttributeDefinition with the given name, or null if none with that name exists
63       */
64      public AttributeDefinition getAttributeDefinition(String attributeName) {
65          if (StringUtils.isBlank(attributeName)) {
66              throw new IllegalArgumentException("invalid (blank) attributeName");
67          }
68          return attributeMap.get(attributeName);
69      }
70  
71      /**
72       * @return a Map containing all AttributeDefinitions associated with this BusinessObjectEntry, indexed by attributeName
73       */
74      public List<AttributeDefinition> getAttributes() {
75          return this.attributes;
76      }
77      
78      /**
79  	 * @return the complexAttributes
80  	 */
81  	public List<ComplexAttributeDefinition> getComplexAttributes() {
82  		return this.complexAttributes;
83  	}
84  
85  	/**
86  	 * @param complexAttributes the complexAttributes to set
87  	 */
88  	public void setComplexAttributes(
89  			List<ComplexAttributeDefinition> complexAttributes) {
90          complexAttributeMap.clear();
91          for ( ComplexAttributeDefinition complexAttribute : complexAttributes ) {
92              if (complexAttribute == null) {
93                  throw new IllegalArgumentException("invalid (null) complexAttributeDefinition");
94              }
95              String complexAttributeName = complexAttribute.getName();
96              if (StringUtils.isBlank(complexAttributeName)) {
97                  throw new ValidationException("invalid (blank) collectionName");
98              }
99  
100             if (complexAttributeMap.containsKey(complexAttribute)){
101                 throw new DuplicateEntryException("complex attribute '" + complexAttribute + "' already defined as an complex attribute for class '" + getEntryClass().getName() + "'");
102             } else if (collectionMap.containsKey(complexAttributeName)) {
103                 throw new DuplicateEntryException("complex attribute '" + complexAttributeName + "' already defined as a Collection for class '" + getEntryClass().getName() + "'");
104             } else if (attributeMap.containsKey(complexAttributeName)) {
105                 throw new DuplicateEntryException("complex attribute '" + complexAttributeName + "' already defined as an Attribute for class '" + getEntryClass().getName() + "'");
106             } 
107 
108             complexAttributeMap.put(complexAttributeName, complexAttribute);
109             
110         }
111 
112 	    this.complexAttributes = complexAttributes;
113 	}
114 
115     /**
116      * @param collectionName
117      * @return CollectionDefinition with the given name, or null if none with that name exists
118      */
119     public CollectionDefinition getCollectionDefinition(String collectionName) {
120         if (StringUtils.isBlank(collectionName)) {
121             throw new IllegalArgumentException("invalid (blank) collectionName");
122         }
123         return collectionMap.get(collectionName);
124     }
125 
126     /**
127      * @return a Map containing all CollectionDefinitions associated with this BusinessObjectEntry, indexed by collectionName
128      */
129     public List<CollectionDefinition> getCollections() {
130         return this.collections;
131     }
132 
133     /**
134      * @param relationshipName
135      * @return RelationshipDefinition with the given name, or null if none with that name exists
136      */
137     public RelationshipDefinition getRelationshipDefinition(String relationshipName) {
138         if (StringUtils.isBlank(relationshipName)) {
139             throw new IllegalArgumentException("invalid (blank) relationshipName");
140         }
141         return relationshipMap.get(relationshipName);
142     }
143 
144     /**
145      * @return a Map containing all RelationshipDefinitions associated with this BusinessObjectEntry, indexed by relationshipName
146      */
147     public List<RelationshipDefinition> getRelationships() {
148         return this.relationships;
149     }
150 
151 
152     /**
153      * Directly validate simple fields, call completeValidation on Definition fields.
154      */
155     public void completeValidation() {
156         
157         for ( AttributeDefinition attributeDefinition : attributes ) {
158             attributeDefinition.completeValidation(getEntryClass(), null);
159         }
160 
161         for ( CollectionDefinition collectionDefinition : collections ) {
162             collectionDefinition.completeValidation(getEntryClass(), null);
163         }
164 
165         for ( RelationshipDefinition relationshipDefinition : relationships ) {
166             relationshipDefinition.completeValidation(getEntryClass(), null);
167         }
168     }
169 
170     /**
171             The attributes element contains attribute 
172             elements.  These define the specifications for business object fields.
173 
174             JSTL: attributes is a Map which is accessed by a key of "attributes".
175             This map contains entries with the following keys:
176                 * attributeName of first attribute
177                 * attributeName of second attribute
178                 etc.
179 
180             The corresponding value for each entry is an attribute ExportMap.
181             By the time the JSTL export happens, all attributeReferences will be
182             indistinguishable from attributes.
183 
184             See AttributesMapBuilder.java
185 
186                 The attribute element specifies the way in which a business object
187                 field appears on a screen for data entry or display purposes.  These
188                 specifications include the following:
189                 * The title and formatting of the field
190                 * Descriptive information about the field
191                 * The edits used at time of data-entry
192 
193                 DD: See AttributeDefinition.java
194 
195                 JSTL: attribute is a Map which is accessed using a key which is the attributeName
196                 of an attribute.  Each entry contains the following keys:
197                     * name (String)
198                     * forceUppercase (boolean String)
199                     * label (String)
200                     * shortLabel (String, copied from label if not present)
201                     * maxLength (String)
202                     * exclusiveMin (bigdecimal String)
203                     * exclusiveMax (bigdecimal String)
204                     * validationPattern (Map, optional)
205                     * required (boolean String)
206                     * control (Map)
207                     * summary (String)
208                     * description (String)
209                     * formatterClass (String, optional)
210                     * fullClassName (String)
211                     * displayWorkgroup(String, optional)
212                     * displayMaskClass(String, optional)
213 
214                 See AttributesMapBuilder.java
215                 Note: exclusiveMax is mapped from the inclusiveMax element!
216                 The validation logic seems to be assuming inclusiveMax.
217      *
218      */
219     public void setAttributes(List<AttributeDefinition> attributes) {
220         attributeMap.clear();
221         for ( AttributeDefinition attribute : attributes ) {
222             if (attribute == null) {
223                 throw new IllegalArgumentException("invalid (null) attributeDefinition");
224             }
225             String attributeName = attribute.getName();
226             if (StringUtils.isBlank(attributeName)) {
227                 throw new ValidationException("invalid (blank) attributeName");
228             }
229 
230             if (attributeMap.containsKey(attributeName)) {
231                 throw new DuplicateEntryException("attribute '" + attributeName + "' already defined as an Attribute for class '" + getEntryClass().getName() + "'");
232             } else if (collectionMap.containsKey(attributeName)) {
233                 throw new DuplicateEntryException("attribute '" + attributeName + "' already defined as a Collection for class '" + getEntryClass().getName() + "'");
234             } else if (complexAttributeMap.containsKey(attributeName)){
235                 throw new DuplicateEntryException("attribute '" + attributeName + "' already defined as an Complex Attribute for class '" + getEntryClass().getName() + "'");
236             }
237             attributeMap.put(attributeName, attribute);            
238         }
239         this.attributes = attributes;
240     }
241 
242     /**
243             The collections element contains collection elements.  These define
244             the lists of other business objects which are related to and
245             defined in the business objects.
246 
247             JSTL: collections is a Map which is accessed by a key of "collections".
248             This map contains entries with the following keys:
249                 * name of first collection
250                 * name of second collection
251                 etc.
252             The corresponding value for each entry is a collection ExportMap.
253 
254             The collection element defines the name and description a
255             list of objects related to the business object.
256 
257             DD: See CollectionDefinition.java.
258 
259             JSTL: collection is a Map which is accessed using a key which is the
260             name of the collection.  Each entry contains the following keys:
261                 * name (String)
262                 * label (String)
263                 * shortLabel (String, copied from label if missing)
264                 * elementLabel (String, copied from contained class if missing)
265                 * summary (String)
266                 * description (String)
267 
268             See CollectionsMapBuilder.java.
269      */
270     public void setCollections(List<CollectionDefinition> collections) {
271         collectionMap.clear();
272         for ( CollectionDefinition collection : collections ) {
273             if (collection == null) {
274                 throw new IllegalArgumentException("invalid (null) collectionDefinition");
275             }
276             String collectionName = collection.getName();
277             if (StringUtils.isBlank(collectionName)) {
278                 throw new ValidationException("invalid (blank) collectionName");
279             }
280 
281             if (collectionMap.containsKey(collectionName)) {
282                 throw new DuplicateEntryException("collection '" + collectionName + "' already defined for class '" + getEntryClass().getName() + "'");
283             } else if (attributeMap.containsKey(collectionName)) {
284                 throw new DuplicateEntryException("collection '" + collectionName + "' already defined as an Attribute for class '" + getEntryClass().getName() + "'");
285             } else if (complexAttributeMap.containsKey(collectionName)){
286                 throw new DuplicateEntryException("collection '" + collectionName + "' already defined as Complex Attribute for class '" + getEntryClass().getName() + "'");
287             }
288 
289             collectionMap.put(collectionName, collection);
290             
291         }
292         this.collections = collections;
293     }
294 
295     /**
296             The relationships element contains relationship elements.
297             These are used to map attribute names to fields in a reference object.
298 
299             JSTL: relationships is a Map which is accessed by a key of "relationships".
300             This map contains entries with the following keys:
301                 * objectAttributeName of first relationship
302                 * objectAttributeName of second relationship
303                 etc.
304             The corresponding value for each entry is a relationship ExportMap.
305 
306             The relationship element defines how primitive attributes of this
307             class can be used to retrieve an instance of some related Object instance
308             DD: See RelationshipDefinition.java.
309 
310             JSTL: relationship is a Map which is accessed using a key which is the
311             objectAttributeName of a relationship.  The map contains a single entry
312             with a key of "primitiveAttributes" and value which is an attributesMap ExportMap.
313 
314             The attributesMap ExportMap contains the following keys:
315                 * 0   (for first primitiveAttribute)
316                 * 1   (for second primitiveAttribute)
317                 etc.
318             The corresponding value for each entry is an primitiveAttribute ExportMap
319             which contains the following keys:
320                 * "sourceName"
321                 * "targetName"
322 
323             See RelationshipsMapBuilder.java.
324             
325      */
326     public void setRelationships(List<RelationshipDefinition> relationships) {
327         this.relationships = relationships;
328     }
329 
330     public Set<String> getCollectionNames() {
331         return collectionMap.keySet();
332     }
333     
334     public Set<String> getAttributeNames() {
335         return attributeMap.keySet();
336     }
337 
338     public Set<String> getRelationshipNames() {
339         return relationshipMap.keySet();
340     }
341     
342     /**
343      * This overridden method ...
344      * 
345      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
346      */
347     public void afterPropertiesSet() throws Exception {
348     	if ( relationships != null ) {
349             relationshipMap.clear();
350             for ( RelationshipDefinition relationship : relationships ) {            
351                 if (relationship == null) {
352                     throw new IllegalArgumentException("invalid (null) relationshipDefinition");
353                 }
354                 String relationshipName = relationship.getObjectAttributeName();
355                 if (StringUtils.isBlank(relationshipName)) {
356                     throw new ValidationException("invalid (blank) relationshipName");
357                 }
358                 relationship.setSourceClass(getEntryClass());
359                 relationshipMap.put(relationshipName, relationship);
360             }
361     	}
362     	
363     	//Populate attributes with nested attribute definitions
364     	if (complexAttributes != null){
365     		for (ComplexAttributeDefinition complexAttribute:complexAttributes){
366     			addNestedAttributes(complexAttribute, complexAttribute.getName());
367     		}
368     	}
369    	}
370     
371     private void addNestedAttributes(ComplexAttributeDefinition complexAttribute, String attrPath){
372     	DataDictionaryEntryBase dataDictionaryEntry = (DataDictionaryEntryBase)complexAttribute.getDataObjectEntry();
373     	
374     	//Add attributes for the complex attibutes
375     	for (AttributeDefinition attribute:dataDictionaryEntry.getAttributes()){
376     		String nestedAttributeName = attrPath + "." + attribute.getName();
377     		AttributeDefinition nestedAttribute = copyAttributeDefinition(attribute);
378     		nestedAttribute.setName(nestedAttributeName);
379     		
380     		if (!attributeMap.containsKey(nestedAttributeName)){
381     			this.attributes.add(nestedAttribute);
382     			this.attributeMap.put(nestedAttributeName, nestedAttribute);
383     		}
384     	}    	
385     	
386     	//Recursively add complex attributes
387     	List<ComplexAttributeDefinition> nestedComplexAttributes = dataDictionaryEntry.getComplexAttributes();
388     	if (nestedComplexAttributes != null){
389 	    	for (ComplexAttributeDefinition nestedComplexAttribute:nestedComplexAttributes){
390 	    		addNestedAttributes(nestedComplexAttribute, attrPath + "." + nestedComplexAttribute.getName());
391 	    	}
392     	}
393     }
394     
395     private AttributeDefinition copyAttributeDefinition(AttributeDefinition attrDefToCopy){
396     	AttributeDefinition attrDefCopy = new AttributeDefinition();
397     	
398     	try {    		
399 			BeanUtils.copyProperties(attrDefToCopy, attrDefCopy, new String[] { "formatterClass" });
400 			
401 			//BeanUtils doesn't copy properties w/o "get" read methods, manually copy those here
402 			attrDefCopy.setRequired(attrDefToCopy.isRequired());
403 			
404 		} catch (Exception e) {
405 			e.printStackTrace();
406 		}
407 		
408 		return attrDefCopy;
409     }
410 }