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 org.apache.commons.lang.StringUtils;
019import org.kuali.rice.krad.bo.BusinessObject;
020import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
021
022import java.util.ArrayList;
023import 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 */
047public 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