View Javadoc

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