001 /**
002 * Copyright 2005-2014 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.krad.datadictionary;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.kuali.rice.krad.datadictionary.exception.DuplicateEntryException;
020 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
021 import org.kuali.rice.krad.datadictionary.state.StateMapping;
022 import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
023 import org.kuali.rice.krad.exception.ValidationException;
024 import org.springframework.beans.BeanUtils;
025 import org.springframework.beans.factory.InitializingBean;
026
027 import java.io.Serializable;
028 import java.util.ArrayList;
029 import java.util.LinkedHashMap;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Set;
033
034 /**
035 * Contains common properties and methods for data dictionary entries
036 *
037 * @author Kuali Rice Team (rice.collab@kuali.org)
038 */
039 abstract public class DataDictionaryEntryBase extends DictionaryBeanBase implements DataDictionaryEntry, Serializable, InitializingBean {
040 protected List<AttributeDefinition> attributes;
041 protected List<ComplexAttributeDefinition> complexAttributes;
042 protected List<CollectionDefinition> collections;
043 protected List<RelationshipDefinition> relationships;
044 protected Map<String, AttributeDefinition> attributeMap;
045 protected Map<String, ComplexAttributeDefinition> complexAttributeMap;
046 protected Map<String, CollectionDefinition> collectionMap;
047 protected Map<String, RelationshipDefinition> relationshipMap;
048
049 protected StateMapping stateMapping;
050
051 public DataDictionaryEntryBase() {
052 this.attributes = new ArrayList<AttributeDefinition>();
053 this.complexAttributes = new ArrayList<ComplexAttributeDefinition>();
054 this.collections = new ArrayList<CollectionDefinition>();
055 this.relationships = new ArrayList<RelationshipDefinition>();
056 this.attributeMap = new LinkedHashMap<String, AttributeDefinition>();
057 this.complexAttributeMap = new LinkedHashMap<String, ComplexAttributeDefinition>();
058 this.collectionMap = new LinkedHashMap<String, CollectionDefinition>();
059 this.relationshipMap = new LinkedHashMap<String, RelationshipDefinition>();
060 }
061
062 /* Returns the given entry class (bo class or document class) */
063 public abstract Class<?> getEntryClass();
064
065 /**
066 * @param attributeName
067 * @return AttributeDefinition with the given name, or null if none with that name exists
068 */
069 public AttributeDefinition getAttributeDefinition(String attributeName) {
070 if (StringUtils.isBlank(attributeName)) {
071 throw new IllegalArgumentException("invalid (blank) attributeName");
072 }
073 return attributeMap.get(attributeName);
074 }
075
076 /**
077 * @return a Map containing all AttributeDefinitions associated with this BusinessObjectEntry, indexed by
078 * attributeName
079 */
080 @BeanTagAttribute(name = "attributes", type = BeanTagAttribute.AttributeType.LISTBEAN)
081 public List<AttributeDefinition> getAttributes() {
082 return this.attributes;
083 }
084
085 /**
086 * @return the complexAttributes
087 */
088 public List<ComplexAttributeDefinition> getComplexAttributes() {
089 return this.complexAttributes;
090 }
091
092 /**
093 * @param complexAttributes the complexAttributes to set
094 */
095 public void setComplexAttributes(List<ComplexAttributeDefinition> complexAttributes) {
096 complexAttributeMap.clear();
097 for (ComplexAttributeDefinition complexAttribute : complexAttributes) {
098 if (complexAttribute == null) {
099 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 }