001/**
002 * Copyright 2005-2018 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.util.ArrayList;
019import java.util.List;
020
021import org.apache.commons.lang.StringUtils;
022import org.kuali.rice.krad.datadictionary.parse.BeanTag;
023import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
024import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
025
026/**
027 * A single Relationship definition in the DataDictionary, which contains information concerning which primitive
028 * attributes of this
029 * class can be used to retrieve an instance of some related Object instance
030 *
031 * The relationship element defines how primitive attributes of this
032 * class can be used to retrieve an instance of some related Object instance
033 * DD: See RelationshipDefinition.java.
034 *
035 * JSTL: relationship is a Map which is accessed using a key which is the
036 * objectAttributeName of a relationship.  The map contains a single entry
037 * with a key of "primitiveAttributes" and value which is an attributesMap ExportMap.
038 *
039 * The attributesMap ExportMap contains the following keys:
040 * 0   (for first primitiveAttribute)
041 * 1   (for second primitiveAttribute)
042 * etc.
043 * The corresponding value for each entry is an primitiveAttribute ExportMap
044 * which contains the following keys:
045 * "sourceName"
046 * "targetName"
047 */
048@BeanTag(name = "relationshipDefinition")
049public class RelationshipDefinition extends DataDictionaryDefinitionBase {
050    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RelationshipDefinition.class);
051    private static final long serialVersionUID = 2946722646095412576L;
052
053    protected String objectAttributeName; //Same as parentAttributeName of DataObjectRelationship
054    protected Class<?> sourceClass; //parentClass
055
056    /**
057     * For 1:1 relationships, this class represents the type of the reference class.  For 1:n references, this class
058     * represents the type of the element
059     * of the collection
060     */
061    protected Class<?> targetClass; //relatedClass
062
063    protected List<PrimitiveAttributeDefinition> primitiveAttributes = new ArrayList<PrimitiveAttributeDefinition>();
064    //parentToChildReferences
065    protected List<SupportAttributeDefinition> supportAttributes = new ArrayList<SupportAttributeDefinition>();
066    //parentToChildReferences
067
068    public RelationshipDefinition() {}
069
070    @BeanTagAttribute(name = "objectAttributeName")
071    public String getObjectAttributeName() {
072        return objectAttributeName;
073    }
074
075    @BeanTagAttribute(name = "sourceClass")
076    public Class<?> getSourceClass() {
077        return sourceClass;
078    }
079
080    /**
081     * Returns the {@link #targetClass}
082     */
083    @BeanTagAttribute(name = "targetClass")
084    public Class<?> getTargetClass() {
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    @BeanTagAttribute(name = "primitiveAttributes", type = BeanTagAttribute.AttributeType.LISTBEAN)
110    public List<PrimitiveAttributeDefinition> getPrimitiveAttributes() {
111        return primitiveAttributes;
112    }
113
114    @BeanTagAttribute(name = "supportAttributes", type = BeanTagAttribute.AttributeType.LISTBEAN)
115    public List<SupportAttributeDefinition> getSupportAttributes() {
116        return supportAttributes;
117    }
118
119    public boolean hasIdentifier() {
120        for (SupportAttributeDefinition supportAttributeDefinition : supportAttributes) {
121            if (supportAttributeDefinition.isIdentifier()) {
122                return true;
123            }
124        }
125        return false;
126    }
127
128    public SupportAttributeDefinition getIdentifier() {
129        for (SupportAttributeDefinition supportAttributeDefinition : supportAttributes) {
130            if (supportAttributeDefinition.isIdentifier()) {
131                return supportAttributeDefinition;
132            }
133        }
134        return null;
135    }
136
137    /**
138     * This overridden method ...
139     *
140     * @see org.kuali.rice.krad.datadictionary.DictionaryBeanBase#dataDictionaryPostProcessing()
141     */
142    @Override
143    public void dataDictionaryPostProcessing() {
144        super.dataDictionaryPostProcessing();
145        if (targetClass == null) {
146            Class<?> propertyClass = DataDictionary.getAttributeClass(sourceClass, objectAttributeName);
147            if (propertyClass != null) {
148                targetClass = propertyClass;
149            }
150        }
151        for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitiveAttributes) {
152            primitiveAttributeDefinition.dataDictionaryPostProcessing();
153        }
154        for (SupportAttributeDefinition supportAttributeDefinition : supportAttributes) {
155            supportAttributeDefinition.dataDictionaryPostProcessing();
156        }
157    }
158
159    /**
160     * Directly validate simple fields, call completeValidation on Definition fields.
161     *
162     * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#completeValidation()
163     */
164    @Override
165    @Deprecated
166    public void completeValidation(Class rootBusinessObjectClass, Class otherBusinessObjectClass) {
167        completeValidation(rootBusinessObjectClass, otherBusinessObjectClass, new ValidationTrace());
168    }
169
170    /**
171     * Directly validate simple fields
172     *
173     * @see org.kuali.rice.krad.datadictionary.DataDictionaryEntry#completeValidation(org.kuali.rice.krad.datadictionary.validator.ValidationTrace)
174     */
175    @Override
176    public void completeValidation(Class rootBusinessObjectClass, Class otherBusinessObjectClass,
177            ValidationTrace tracer) {
178        tracer.addBean(this.getClass().getSimpleName(), "Attribute: " + getObjectAttributeName());
179        try {
180            if (!DataDictionary.isPropertyOf(rootBusinessObjectClass, getObjectAttributeName())) {
181                String currentValues[] =
182                        {"property = " + getObjectAttributeName(), "Class =" + rootBusinessObjectClass};
183                tracer.createError("Property is not an attribute of the class", currentValues);
184            }
185        } catch (RuntimeException ex) {
186            String currentValues[] = {"attribute = " + getObjectAttributeName(), "Exception = " + ex.getMessage()};
187            tracer.createError("Unable to validate attribute", currentValues);
188            LOG.error( "Exception while validating attribute: " + getObjectAttributeName(), ex );
189        }
190
191        if (targetClass == null) {
192            String currentValues[] =
193                    {"property = " + getObjectAttributeName(), "sourceClass = " + getSourceClass()};
194            tracer.createError("Cannot get valid class for property", currentValues);
195        } else {
196
197            for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitiveAttributes) {
198                primitiveAttributeDefinition.completeValidation(rootBusinessObjectClass, targetClass, tracer.getCopy());
199            }
200            for (SupportAttributeDefinition supportAttributeDefinition : supportAttributes) {
201                supportAttributeDefinition.completeValidation(rootBusinessObjectClass, targetClass, tracer.getCopy());
202            }
203        }
204    }
205
206    /**
207     * The primitiveAttribute element identifies one pair of
208     * corresponding fields in the primary business object and
209     * the related business object.
210     *
211     * JSTL: primitiveAttribute is a Map which is accessed by the
212     * sequential key of "0", "1", etc.  Each entry contains the following
213     * keys:
214     * sourceName (String)
215     * targetName (String)
216     * The value corresponding to the sourceName key is the attribute name defined
217     * for the primary business object.
218     * The value corresponding to the targetName key is the attribute name for
219     * the object being referenced by objectAttributeName.
220     */
221    public void setPrimitiveAttributes(List<PrimitiveAttributeDefinition> primitiveAttributes) {
222        this.primitiveAttributes = primitiveAttributes;
223    }
224
225    /**
226     * Support attributes define additional attributes that can be used to generate
227     * lookup field conversions and lookup parameters.
228     *
229     * Field conversions and lookup parameters are normally generated using foreign key relationships
230     * defined within OJB and the DD.  Because Person objects are linked in a special way (i.e. they may
231     * come from an external data source and not from the DB, such as LDAP), it is often necessary to define
232     * extra fields that are related to each other, sort of like a supplemental foreign key.
233     *
234     * sourceName is the name of the POJO property of the business object
235     * targetName is the name of attribute that corresponds to the sourceName in the looked up BO
236     * identifier when true, only the field marked as an identifier will be passed in as a lookup parameter
237     * at most one supportAttribute for each relationship should be defined as identifier="true"
238     */
239    public void setSupportAttributes(List<SupportAttributeDefinition> supportAttributes) {
240        this.supportAttributes = supportAttributes;
241    }
242
243    /**
244     * @param sourceClass the sourceClass to set
245     */
246    public void setSourceClass(Class<?> sourceClass) {
247        this.sourceClass = sourceClass;
248    }
249
250    @Override
251    public String toString() {
252        StringBuilder builder = new StringBuilder();
253        builder.append("RelationshipDefinition [objectAttributeName=").append(this.objectAttributeName)
254                .append(", sourceClass=").append(this.sourceClass).append(", targetClass=").append(this.targetClass)
255                .append(", primitiveAttributes=").append(this.primitiveAttributes).append(", supportAttributes=")
256                .append(this.supportAttributes).append("]");
257        return builder.toString();
258    }
259}
260