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