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