001    /*
002     * Copyright 2006-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    
017    package org.kuali.rice.kns.datadictionary;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.kuali.rice.kns.bo.BusinessObject;
024    import org.kuali.rice.kns.datadictionary.exception.AttributeValidationException;
025    
026    /**
027     * A single Relationship definition in the DataDictionary, which contains information concerning which primitive attributes of this
028     * class can be used to retrieve an instance of some related Object instance
029     * 
030                    The relationship element defines how primitive attributes of this
031                    class can be used to retrieve an instance of some related Object instance
032                    DD: See RelationshipDefinition.java.
033    
034                    JSTL: relationship is a Map which is accessed using a key which is the
035                    objectAttributeName of a relationship.  The map contains a single entry
036                    with a key of "primitiveAttributes" and value which is an attributesMap ExportMap.
037    
038                    The attributesMap ExportMap contains the following keys:
039                        * 0   (for first primitiveAttribute)
040                        * 1   (for second primitiveAttribute)
041                        etc.
042                    The corresponding value for each entry is an primitiveAttribute ExportMap
043                    which contains the following keys:
044                        * "sourceName"
045                        * "targetName"
046     * 
047     */
048    public class RelationshipDefinition extends DataDictionaryDefinitionBase {
049        private static final long serialVersionUID = 2946722646095412576L;
050        
051            protected String objectAttributeName; //Same as parentAttributeName of BusinessObjectRelationship
052        protected Class<? extends BusinessObject> sourceClass; //parentClass
053        /**
054         * For 1:1 relationships, this class represents the type of the reference class.  For 1:n references, this class represents the type of the element
055         * of the collection
056         */
057        protected Class<? extends BusinessObject> targetClass; //relatedClass
058    
059            protected List<PrimitiveAttributeDefinition> primitiveAttributes = new ArrayList<PrimitiveAttributeDefinition>(); //parentToChildReferences
060        protected List<SupportAttributeDefinition> supportAttributes = new ArrayList<SupportAttributeDefinition>(); //parentToChildReferences
061    
062    
063        public RelationshipDefinition() {}
064    
065        public String getObjectAttributeName() {
066            return objectAttributeName;
067        }
068    
069        public Class<? extends BusinessObject> getSourceClass() {
070            return sourceClass;
071        }
072    
073        /**
074         * Returns the {@link #targetClass}
075         * 
076         * @param targetClass
077         */
078        public Class<? extends BusinessObject> getTargetClass() {
079            if (targetClass == null) {
080                    Class propertyClass = DataDictionary.getAttributeClass(sourceClass, objectAttributeName);
081                    if (propertyClass == null) {
082                        throw new AttributeValidationException("cannot get valid class for property '" + objectAttributeName + "' as an attribute of '" + sourceClass + "'");
083                    }
084                    if (!BusinessObject.class.isAssignableFrom(propertyClass)) {
085                        throw new AttributeValidationException("property '" + objectAttributeName + "' is not a BusinessObject (" + propertyClass.getName() + ")");
086                    }
087            
088            
089                    targetClass = propertyClass;
090            }
091            return targetClass;
092        }
093    
094        /**
095         * Sets the {@link #targetClass}
096         * 
097         * @param targetClass
098         */
099        public void setTargetClass(Class<? extends BusinessObject> targetClass) {
100                    this.targetClass = targetClass;
101            }
102    
103        /**
104         * Name of the business object property on the containing business object that is linked
105         * by the contained PrimitiveAttributeDefinition objects.
106         */
107        public void setObjectAttributeName(String objectAttributeName) {
108            if (StringUtils.isBlank(objectAttributeName)) {
109                throw new IllegalArgumentException("invalid (blank) objectAttributeName");
110            }
111    
112            this.objectAttributeName = objectAttributeName;
113        }
114    
115        public List<PrimitiveAttributeDefinition> getPrimitiveAttributes() {
116            return primitiveAttributes;
117        }    
118    
119        public List<SupportAttributeDefinition> getSupportAttributes() {
120            return supportAttributes;
121        }
122    
123        public boolean hasIdentifier() {
124            for (SupportAttributeDefinition supportAttributeDefinition : supportAttributes) {
125                if ( supportAttributeDefinition.isIdentifier() ) {
126                    return true;
127                }
128            }
129            return false;
130        }
131        
132        public SupportAttributeDefinition getIdentifier() {
133            for (SupportAttributeDefinition supportAttributeDefinition : supportAttributes) {
134                if ( supportAttributeDefinition.isIdentifier() ) {
135                    return supportAttributeDefinition;
136                }
137            }
138            return null;
139        }
140        
141        /**
142         * Directly validate simple fields, call completeValidation on Definition fields.
143         * 
144         * @see org.kuali.rice.kns.datadictionary.DataDictionaryEntry#completeValidation()
145         */
146        public void completeValidation(Class rootBusinessObjectClass, Class otherBusinessObjectClass) {
147            String propertyName = objectAttributeName;
148            if (!DataDictionary.isPropertyOf(rootBusinessObjectClass, propertyName)) {
149                throw new AttributeValidationException("property '" + propertyName + "' is not an attribute of class '" + rootBusinessObjectClass + "' (" + "" + ")");
150            }
151    
152            getTargetClass(); // performs validation when this is called the first time
153    
154            for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitiveAttributes) {
155                primitiveAttributeDefinition.completeValidation(rootBusinessObjectClass, targetClass);
156            }
157            for (SupportAttributeDefinition supportAttributeDefinition : supportAttributes) {
158                supportAttributeDefinition.completeValidation(rootBusinessObjectClass, targetClass);
159            }
160        }
161    
162    
163        /**
164         * @see java.lang.Object#toString()
165         */
166        @Override
167        public String toString() {
168            return "RelationshipDefinition for relationship " + getObjectAttributeName();
169        }
170    
171        /**
172         * 
173                        The primitiveAttribute element identifies one pair of
174                        corresponding fields in the primary business object and
175                        the related business object.
176    
177                        JSTL: primitiveAttribute is a Map which is accessed by the
178                        sequential key of "0", "1", etc.  Each entry contains the following
179                        keys:
180                            * sourceName (String)
181                            * targetName (String)
182                        The value corresponding to the sourceName key is the attribute name defined
183                        for the primary business object.
184                        The value corresponding to the targetName key is the attribute name for
185                        the object being referenced by objectAttributeName.
186         */
187        public void setPrimitiveAttributes(List<PrimitiveAttributeDefinition> primitiveAttributes) {
188            this.primitiveAttributes = primitiveAttributes;
189        }
190    
191        /**
192                        Support attributes define additional attributes that can be used to generate
193                        lookup field conversions and lookup parameters.
194    
195                        Field conversions and lookup parameters are normally generated using foreign key relationships
196                        defined within OJB and the DD.  Because Person objects are linked in a special way (i.e. they may
197                        come from an external data source and not from the DB, such as LDAP), it is often necessary to define
198                        extra fields that are related to each other, sort of like a supplemental foreign key.
199    
200                        sourceName is the name of the POJO property of the business object
201                        targetName is the name of attribute that corresponds to the sourceName in the looked up BO
202                        identifier when true, only the field marked as an identifier will be passed in as a lookup parameter
203                                   at most one supportAttribute for each relationship should be defined as identifier="true"
204         */
205        public void setSupportAttributes(List<SupportAttributeDefinition> supportAttributes) {
206            this.supportAttributes = supportAttributes;
207        }
208    
209            /**
210             * @param sourceClass the sourceClass to set
211             */
212            public void setSourceClass(Class<? extends BusinessObject> sourceClass) {
213                    this.sourceClass = sourceClass;
214            }
215    }
216