001/**
002 * Copyright 2005-2017 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.kns.service.impl;
017
018import java.lang.reflect.InvocationTargetException;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.commons.lang.StringUtils;
027import org.kuali.rice.kns.datadictionary.FieldDefinition;
028import org.kuali.rice.kns.datadictionary.InquirySectionDefinition;
029import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
030import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
031import org.kuali.rice.kns.service.KNSServiceLocator;
032import org.kuali.rice.krad.bo.BusinessObject;
033import org.kuali.rice.krad.bo.DataObjectRelationship;
034import org.kuali.rice.krad.bo.PersistableBusinessObject;
035import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
036import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition;
037import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
038import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition;
039import org.kuali.rice.krad.service.DataDictionaryService;
040import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
041import org.kuali.rice.krad.service.ModuleService;
042import org.kuali.rice.krad.service.PersistenceStructureService;
043import org.kuali.rice.krad.service.impl.DataObjectMetaDataServiceImpl;
044import org.kuali.rice.krad.util.LegacyDataFramework;
045import org.kuali.rice.krad.util.ObjectUtils;
046import org.kuali.rice.krad.valuefinder.ValueFinder;
047
048/**
049 *
050 * Implementation of the <code>BusinessObjectMetaDataService</code> which uses
051 * the following services to gather its meta data:
052 *
053 * @see BusinessObjectDictionaryService
054 * @see DataDictionaryService
055 * @see PersistenceStructureService
056 *
057 * @deprecated Only use {@link DataObjectMetaDataServiceImpl} if still using legacy data framework, otherwise use new
058 *             KRAD Data framework.
059 */
060@Deprecated // Replaced by new metadata provider
061@LegacyDataFramework
062public class BusinessObjectMetaDataServiceImpl extends DataObjectMetaDataServiceImpl implements BusinessObjectMetaDataService {
063        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
064                        .getLogger(BusinessObjectMetaDataServiceImpl.class);
065
066        private BusinessObjectDictionaryService businessObjectDictionaryService;
067
068        @Override
069    public Collection<String> getCollectionNames(Object bo) {
070                return getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(bo.getClass().getName())
071                                .getCollectionNames();
072        }
073
074        @Override
075    public Collection<String> getInquirableFieldNames(Class boClass, String sectionTitle) {
076                return businessObjectDictionaryService.getInquiryFieldNames(boClass, sectionTitle);
077        }
078
079        @Override
080    public List<String> getLookupableFieldNames(Class boClass) {
081                return businessObjectDictionaryService.getLookupFieldNames(boClass);
082        }
083
084        @Override
085    public String getLookupFieldDefaultValue(Class businessObjectClass, String attributeName) {
086                return businessObjectDictionaryService.getLookupFieldDefaultValue(businessObjectClass, attributeName);
087        }
088
089        @Override
090    public Class getLookupFieldDefaultValueFinderClass(Class businessObjectClass, String attributeName) {
091                return businessObjectDictionaryService
092                                .getLookupFieldDefaultValueFinderClass(businessObjectClass, attributeName);
093        }
094
095        /** {@inheritDoc} */
096        @Override
097    public String getLookupFieldQuickfinderParameterString(Class businessObjectClass, String attributeName) {
098                return businessObjectDictionaryService.getLookupFieldQuickfinderParameterString(businessObjectClass,
099                                attributeName);
100        }
101
102        /** {@inheritDoc} */
103        @Override
104    public Class<? extends ValueFinder> getLookupFieldQuickfinderParameterStringBuilderClass(Class businessObjectClass,
105                        String attributeName) {
106                return businessObjectDictionaryService.getLookupFieldQuickfinderParameterStringBuilderClass(
107                                businessObjectClass, attributeName);
108        }
109
110        @Override
111    public boolean isAttributeInquirable(Class boClass, String attributeName, String sectionTitle) {
112                Collection sections = businessObjectDictionaryService.getInquirySections(boClass);
113                boolean isInquirable = true;
114
115                Iterator iter = sections.iterator();
116
117                while (iter.hasNext()) {
118                        InquirySectionDefinition def = (InquirySectionDefinition) iter.next();
119                        for (FieldDefinition field : def.getInquiryFields()) {
120                                if (field.getAttributeName().equalsIgnoreCase(attributeName)) {
121                                        isInquirable = !field.isNoInquiry();
122                                }
123                        }
124                }
125                if (isInquirable) {
126                        Object obj = null;
127                        if (boClass != null && BusinessObject.class.isAssignableFrom(boClass)) {
128                                obj = ObjectUtils.createNewObjectFromClass(boClass);
129                        }
130
131                        if (obj != null) {
132                                BusinessObject bo = (BusinessObject) obj;
133                                Class clazz = getNestedBOClass(bo, attributeName);
134                                if (clazz != null && BusinessObject.class.isAssignableFrom(clazz)) {
135                                        return businessObjectDictionaryService.isInquirable(clazz);
136                                }
137                                else {
138                                        return false;
139                                }
140                        }
141                        else {
142                                return false;
143                        }
144
145                }
146
147                return isInquirable;
148        }
149
150        @Override
151    public boolean isInquirable(Class boClass) {
152                boolean inquirable = false;
153                ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(boClass);
154                if (moduleService != null && moduleService.isExternalizable(boClass)) {
155                        inquirable = moduleService.isExternalizableBusinessObjectInquirable(boClass);
156                }
157                else {
158                        Boolean isLookupable = businessObjectDictionaryService.isInquirable(boClass);
159                        if (isLookupable != null) {
160                                inquirable = isLookupable.booleanValue();
161                        }
162                }
163                return inquirable;
164        }
165
166        @Override
167    public boolean isAttributeLookupable(Class boClass, String attributeName) {
168                Object obj = null;
169                if (boClass != null && BusinessObject.class.isAssignableFrom(boClass)) {
170                        obj = ObjectUtils.createNewObjectFromClass(boClass);
171                }
172                if (obj != null) {
173                        BusinessObject bo = (BusinessObject) obj;
174                        DataObjectRelationship relationship = getBusinessObjectRelationship(bo, attributeName);
175
176                        if (relationship != null && relationship.getRelatedClass() != null
177                                        && BusinessObject.class.isAssignableFrom(relationship.getRelatedClass())) {
178                                return isLookupable(relationship.getRelatedClass());
179                        }
180                        else {
181                                return false;
182                        }
183                }
184                else {
185                        return false;
186                }
187        }
188
189        @Override
190    public boolean isLookupable(Class boClass) {
191                boolean lookupable = false;
192                ModuleService moduleService =  getKualiModuleService().getResponsibleModuleService(boClass);
193                if (moduleService != null && moduleService.isExternalizable(boClass)) {
194                        lookupable = moduleService.isExternalizableBusinessObjectLookupable(boClass);
195                }
196                else {
197                        Boolean isLookupable = businessObjectDictionaryService.isLookupable(boClass);
198                        if (isLookupable != null) {
199                                lookupable = isLookupable.booleanValue();
200                        }
201                }
202                return lookupable;
203        }
204
205        @Override
206    public DataObjectRelationship getBusinessObjectRelationship(Object bo, String attributeName) {
207                return getBusinessObjectRelationship(bo, bo.getClass(), attributeName, "", true);
208        }
209
210        // TODO: four different exit points?!
211        @Override
212    public DataObjectRelationship getBusinessObjectRelationship(RelationshipDefinition ddReference,
213            Object bo, Class boClass, String attributeName, String attributePrefix, boolean keysOnly) {
214
215                DataObjectRelationship relationship = null;
216
217                // if it is nested then replace the bo and attributeName with the
218                // sub-refs
219                if (ObjectUtils.isNestedAttribute(attributeName)) {
220                        if (ddReference != null) {
221                                relationship = new DataObjectRelationship(boClass, ddReference.getObjectAttributeName(),
222                                                ddReference.getTargetClass());
223                                for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
224                                        if (StringUtils.isNotBlank(attributePrefix)) {
225                                                relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
226                                                                def.getTargetName());
227                                        }
228                                        else {
229                                                relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
230                                        }
231                                }
232                                if (!keysOnly) {
233                                        for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
234                                                if (StringUtils.isNotBlank(attributePrefix)) {
235                                                        relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
236                                                                        def.getTargetName());
237                                                        if (def.isIdentifier()) {
238                                                                relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
239                                                        }
240                                                }
241                                                else {
242                                                        relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
243                                                        if (def.isIdentifier()) {
244                                                                relationship.setUserVisibleIdentifierKey(def.getSourceName());
245                                                        }
246                                                }
247                                        }
248                                }
249                                return relationship;
250                        }
251                        // recurse down to the next object to find the relationship
252
253                        String localPrefix = StringUtils.substringBefore(attributeName, ".");
254                        String localAttributeName = StringUtils.substringAfter(attributeName, ".");
255                        if (bo == null) {
256                                bo = (BusinessObject) ObjectUtils.createNewObjectFromClass(boClass);
257                        }
258                        Class nestedClass = ObjectUtils.getPropertyType(bo, localPrefix, getPersistenceStructureService());
259                        String fullPrefix = localPrefix;
260                        if (StringUtils.isNotBlank(attributePrefix)) {
261                                fullPrefix = attributePrefix + "." + localPrefix;
262                        }
263                        if (BusinessObject.class.isAssignableFrom(nestedClass)) {
264                                relationship = getBusinessObjectRelationship(null, nestedClass, localAttributeName, fullPrefix,
265                                                keysOnly);
266
267                                // Since it was a nested property, we need to set the "parent" object
268                                // back to the parent BO - the code above returns the next level down
269                if (relationship != null) {
270                                    relationship.setParentClass(boClass);
271                }
272                        }
273                        return relationship;
274                }
275                int maxSize = Integer.MAX_VALUE;
276                // try persistable reference first
277                if (PersistableBusinessObject.class.isAssignableFrom(boClass)
278                                && getPersistenceStructureService().isPersistable(boClass)) {
279                        Map<String, DataObjectRelationship> rels = getPersistenceStructureService().getRelationshipMetadata(boClass,
280                                        attributeName, attributePrefix);
281                        if (rels.size() > 0) {
282                                for (DataObjectRelationship rel : rels.values()) {
283                                        if (rel.getParentToChildReferences().size() < maxSize && isLookupable(rel.getRelatedClass())) {
284                                                maxSize = rel.getParentToChildReferences().size();
285                                                relationship = rel;
286                                        }
287                                }
288                        }
289                }
290                else {
291                        ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService()
292                                        .getResponsibleModuleService(boClass);
293                        if (moduleService != null && moduleService.isExternalizable(boClass)) {
294                                relationship = getRelationshipMetadata(boClass, attributeName, attributePrefix);
295                                // relationship =
296                                // moduleService.getBusinessObjectRelationship(boClass,
297                                // attributeName, attributePrefix);
298                                if (relationship != null) {
299                                        return relationship;
300                                }
301                        }
302                }
303
304                // then check the DD for relationships defined there
305                // TODO move out to a separate method
306                // so that the logic for finding the relationships is similar to
307                // primitiveReference
308                if (ddReference != null && BusinessObject.class.isAssignableFrom(ddReference.getTargetClass()) && isLookupable(ddReference.getTargetClass()) && bo != null
309                                && ddReference.getPrimitiveAttributes().size() < maxSize) {
310                        relationship = new DataObjectRelationship(boClass, ddReference.getObjectAttributeName(),
311                                        ddReference.getTargetClass());
312                        for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
313                                relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
314                        }
315                        if (!keysOnly) {
316                                for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
317                                        relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
318                                }
319                        }
320                }
321
322                return relationship;
323
324        }
325
326
327
328        @Override
329    public RelationshipDefinition getBusinessObjectRelationshipDefinition(Class c, String attributeName) {
330                return getDictionaryRelationship(c, attributeName);
331        }
332
333        @Override
334    public RelationshipDefinition getBusinessObjectRelationshipDefinition(Object bo, String attributeName) {
335                return getBusinessObjectRelationshipDefinition(bo.getClass(), attributeName);
336        }
337
338        @Override
339    public DataObjectRelationship getBusinessObjectRelationship(Object bo, Class boClass,
340                        String attributeName, String attributePrefix, boolean keysOnly) {
341                RelationshipDefinition ddReference = getBusinessObjectRelationshipDefinition(boClass, attributeName);
342                return getBusinessObjectRelationship(ddReference, bo, boClass, attributeName, attributePrefix, keysOnly);
343        }
344
345        /**
346         *
347         * This method retrieves the business object class for a specific attribute
348         *
349         * @param bo
350         * @param attributeName
351         * @return a business object class for a specific attribute
352         */
353        private Class getNestedBOClass(BusinessObject bo, String attributeName) {
354
355                String[] nestedAttributes = StringUtils.split(attributeName, ".");
356                String attributeRefName = "";
357                Class clazz = null;
358                if (nestedAttributes.length > 1) {
359                        String attributeStringSoFar = "";
360                        for (int i = 0; i < nestedAttributes.length - 1; i++) {
361                                try {
362                                        // we need to build a string of the attribute names
363                                        // depending on which iteration we're in.
364                                        // so if the original attributeName string we're using is
365                                        // "a.b.c.d.e", then first iteration would use
366                                        // "a", 2nd "a.b", 3rd "a.b.c", etc.
367                                        if (i != 0) {
368                                                attributeStringSoFar = attributeStringSoFar + ".";
369                                        }
370                                        attributeStringSoFar = attributeStringSoFar + nestedAttributes[i];
371                                        clazz = ObjectUtils.easyGetPropertyType(bo, attributeStringSoFar);
372                                }
373                                catch (InvocationTargetException ite) {
374                                        LOG.info(ite);
375                                        return null;
376                                }
377                                catch (NoSuchMethodException nsme) {
378                                        LOG.info(nsme);
379                                        return null;
380                                }
381                                catch (IllegalAccessException iae) {
382                                        LOG.info(iae);
383                                        return null;
384                                }
385                        }
386                }
387                return clazz;
388        }
389
390        @Override
391    public List<DataObjectRelationship> getBusinessObjectRelationships(Object bo) {
392                if (bo == null) {
393                        return null;
394                }
395                return getBusinessObjectRelationships(bo.getClass());
396        }
397
398        @Override
399    @SuppressWarnings("unchecked")
400        public List<DataObjectRelationship> getBusinessObjectRelationships(Class<? extends Object> boClass) {
401                if (boClass == null) {
402                        return null;
403                }
404
405                Map<String, Class> referenceClasses = null;
406                if (PersistableBusinessObject.class.isAssignableFrom(boClass)
407                                && getPersistenceStructureService().isPersistable(boClass)) {
408                        referenceClasses = getPersistenceStructureService().listReferenceObjectFields(boClass);
409                }
410                DataDictionaryEntry ddEntry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
411                                boClass.getName());
412                List<RelationshipDefinition> ddRelationships = (ddEntry == null ? new ArrayList<RelationshipDefinition>()
413                                : ddEntry.getRelationships());
414                List<DataObjectRelationship> relationships = new ArrayList<DataObjectRelationship>();
415
416                // loop over all relationships
417                if (referenceClasses != null) {
418                        for (Map.Entry<String, Class> entry : referenceClasses.entrySet()) {
419                                if (isLookupable(entry.getValue())) {
420                                        Map<String, String> fkToPkRefs = getPersistenceStructureService().getForeignKeysForReference(boClass,
421                                                        entry.getKey());
422                                        DataObjectRelationship rel = new DataObjectRelationship(boClass, entry.getKey(),
423                                                        entry.getValue());
424                                        for (Map.Entry<String, String> ref : fkToPkRefs.entrySet()) {
425                                                rel.getParentToChildReferences().put(ref.getKey(), ref.getValue());
426                                        }
427                                        relationships.add(rel);
428                                }
429                        }
430                }
431
432                for (RelationshipDefinition rd : ddRelationships) {
433                        if (isLookupable(rd.getTargetClass())) {
434                                DataObjectRelationship rel = new DataObjectRelationship(boClass, rd.getObjectAttributeName(),
435                                                rd.getTargetClass());
436                                for (PrimitiveAttributeDefinition def : rd.getPrimitiveAttributes()) {
437                                        rel.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
438                                }
439                                relationships.add(rel);
440                        }
441                }
442
443                return relationships;
444        }
445
446        /***************************************************************************
447         * @see org.kuali.rice.kns.service.BusinessObjectMetaDataService#getReferencesForForeignKey(java.lang.Class,
448         *      java.lang.String)
449         */
450        @Override
451    public Map<String, Class> getReferencesForForeignKey(Object bo, String attributeName) {
452                List<DataObjectRelationship> dataObjectRelationships = getBusinessObjectRelationships(bo);
453                Map<String, Class> referencesForForeignKey = new HashMap<String, Class>();
454                for (DataObjectRelationship dataObjectRelationship : dataObjectRelationships) {
455                        if (dataObjectRelationship != null && dataObjectRelationship.getParentToChildReferences() != null
456                                        && dataObjectRelationship.getParentToChildReferences().containsKey(attributeName)) {
457                                referencesForForeignKey.put(dataObjectRelationship.getParentAttributeName(),
458                                                dataObjectRelationship.getRelatedClass());
459                        }
460                }
461                return referencesForForeignKey;
462        }
463
464        @Override
465    public String getForeignKeyFieldName(Class businessObjectClass, String attributeName, String targetName) {
466
467                String fkName = "";
468
469                // first try DD-based relationships
470                RelationshipDefinition relationshipDefinition = getDictionaryRelationship(businessObjectClass, attributeName);
471
472                if (relationshipDefinition != null) {
473                        List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
474                        for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
475                                if (primitiveAttributeDefinition.getTargetName().equals(targetName)) {
476                                        fkName = primitiveAttributeDefinition.getSourceName();
477                                        break;
478                                }
479                        }
480                }
481
482                // if we can't find anything in the DD, then try the persistence service
483                if (StringUtils.isBlank(fkName) && PersistableBusinessObject.class.isAssignableFrom(businessObjectClass)
484                                && getPersistenceStructureService().isPersistable(businessObjectClass)) {
485                        fkName = getPersistenceStructureService().getForeignKeyFieldName(businessObjectClass, attributeName,
486                                        targetName);
487                }
488                return fkName;
489        }
490
491    /**
492     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#hasLocalLookup
493     */
494    @Override
495    public boolean hasLocalLookup(Class<?> dataObjectClass) {
496        boolean hasLookup = super.hasLocalLookup(dataObjectClass);
497
498        // if no krad lookup check for kns
499        if (!hasLookup) {
500            Boolean isLookupable = getBusinessObjectDictionaryService().isLookupable(dataObjectClass);
501            if (isLookupable != null) {
502                hasLookup = isLookupable.booleanValue();
503            }
504        }
505
506        return hasLookup;
507    }
508
509    /**
510     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#hasLocalInquiry
511     */
512    @Override
513    public boolean hasLocalInquiry(Class<?> dataObjectClass) {
514        boolean hasInquiry = super.hasLocalInquiry(dataObjectClass);
515
516        // if no krad inquiry check for kns
517        if (!hasInquiry) {
518            Boolean isInquirable = getBusinessObjectDictionaryService().isInquirable(dataObjectClass);
519            if (isInquirable != null) {
520                hasInquiry = isInquirable.booleanValue();
521            }
522        }
523
524        return hasInquiry;
525    }
526
527    /**
528     * Gets the businessObjectDictionaryService attribute.
529     *
530     * @return Returns the businessObjectDictionaryService.
531     */
532    protected BusinessObjectDictionaryService getBusinessObjectDictionaryService() {
533        if (businessObjectDictionaryService == null) {
534            businessObjectDictionaryService = KNSServiceLocator.getBusinessObjectDictionaryService();
535        }
536
537        return businessObjectDictionaryService;
538    }
539
540        /**
541         * Sets the businessObjectDictionaryService attribute value.
542         *
543         * @param businessObjectDictionaryService
544         *            The BusinessObjectDictionaryService to set.
545         */
546        public void setBusinessObjectDictionaryService(BusinessObjectDictionaryService businessObjectDictionaryService) {
547                this.businessObjectDictionaryService = businessObjectDictionaryService;
548        }
549}