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.service.impl;
017
018import java.beans.PropertyDescriptor;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.commons.beanutils.PropertyUtils;
028import org.apache.commons.lang.StringUtils;
029import org.kuali.rice.core.api.config.property.ConfigContext;
030import org.kuali.rice.kim.api.identity.Person;
031import org.kuali.rice.kim.api.identity.PersonService;
032import org.kuali.rice.kim.api.services.KimApiServiceLocator;
033import org.kuali.rice.krad.bo.BusinessObject;
034import org.kuali.rice.krad.bo.DataObjectRelationship;
035import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
036import org.kuali.rice.krad.bo.PersistableBusinessObject;
037import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
038import org.kuali.rice.krad.dao.BusinessObjectDao;
039import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
040import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
041import org.kuali.rice.krad.service.BusinessObjectService;
042import org.kuali.rice.krad.service.DataObjectMetaDataService;
043import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
044import org.kuali.rice.krad.service.ModuleService;
045import org.kuali.rice.krad.service.PersistenceService;
046import org.kuali.rice.krad.service.PersistenceStructureService;
047import org.kuali.rice.krad.util.KRADConstants;
048import org.kuali.rice.krad.util.LegacyDataFramework;
049import org.kuali.rice.krad.util.ObjectUtils;
050import org.springframework.transaction.annotation.Transactional;
051
052/**
053 * This class is the service implementation for the BusinessObjectService structure. This is the default implementation, that is
054 * delivered with Kuali.
055 *
056 * @deprecated use new KRAD Data framework {@link org.kuali.rice.krad.data.DataObjectService}
057 */
058@Deprecated
059@LegacyDataFramework
060public class BusinessObjectServiceImpl implements BusinessObjectService {
061
062    private PersistenceService persistenceService;
063    private PersistenceStructureService persistenceStructureService;
064    private BusinessObjectDao businessObjectDao;
065    private PersonService personService;
066    private DataObjectMetaDataService dataObjectMetaDataService;
067    private LegacyDataFramework legacyDataFramework;
068
069    private boolean illegalBusinessObjectsForSaveInitialized;
070    private final Set<String> illegalBusinessObjectsForSave = new HashSet<String>();
071
072    @Override
073    @Transactional
074    public <T extends PersistableBusinessObject> T save(T bo) {
075        validateBusinessObjectForSave(bo);
076        return (T) businessObjectDao.save(bo);
077    }
078
079    @Override
080    @Transactional
081    public List<? extends PersistableBusinessObject> save(List<? extends PersistableBusinessObject> businessObjects) {
082        validateBusinessObjectForSave(businessObjects);
083        return businessObjectDao.save(businessObjects);
084    }
085
086    @Override
087    @Transactional
088    public PersistableBusinessObject linkAndSave(PersistableBusinessObject bo) {
089        validateBusinessObjectForSave(bo);
090        persistenceService.linkObjects(bo);
091        return businessObjectDao.save(bo);
092    }
093
094    @Override
095    @Transactional
096    public List<? extends PersistableBusinessObject> linkAndSave(List<? extends PersistableBusinessObject> businessObjects) {
097        validateBusinessObjectForSave(businessObjects);
098        return businessObjectDao.save(businessObjects);
099    }
100
101    protected void validateBusinessObjectForSave(PersistableBusinessObject bo) {
102        if (bo == null) {
103            throw new IllegalArgumentException("Object passed in is null");
104        }
105        if (!isBusinessObjectAllowedForSave(bo)) {
106                throw new IllegalArgumentException("Object passed in is a BusinessObject but has been restricted from save operations according to configuration parameter '" + KRADConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE);
107        }
108    }
109
110    protected void validateBusinessObjectForSave(List<? extends PersistableBusinessObject> businessObjects) {
111        for (PersistableBusinessObject bo : businessObjects) {
112                 if (bo == null) {
113                 throw new IllegalArgumentException("One of the objects in the List is null.");
114             }
115                 if (!isBusinessObjectAllowedForSave(bo)) {
116                         throw new IllegalArgumentException("One of the objects in the List is a BusinessObject but has been restricted from save operations according to configuration parameter '" + KRADConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE
117                                         + "  Passed in type was '" + bo.getClass().getName() + "'.");
118                 }
119        }
120    }
121
122
123    /**
124     * Returns true if the BusinessObjectService should be permitted to save instances of the given PersistableBusinessObject.
125     * Implementation checks a configuration parameter for class names of PersistableBusinessObjects that shouldn't be allowed
126     * to be saved.
127     */
128    protected boolean isBusinessObjectAllowedForSave(PersistableBusinessObject bo) {
129        if (!illegalBusinessObjectsForSaveInitialized) {
130                synchronized (this) {
131                        boolean applyCheck = true;
132                        String applyCheckValue = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.Config.APPLY_ILLEGAL_BUSINESS_OBJECT_FOR_SAVE_CHECK);
133                        if (!StringUtils.isEmpty(applyCheckValue)) {
134                                applyCheck = Boolean.valueOf(applyCheckValue);
135                        }
136                        if (applyCheck) {
137                                String illegalBos = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE);
138                                if (!StringUtils.isEmpty(illegalBos)) {
139                                        String[] illegalBosSplit = illegalBos.split(",");
140                                        for (String illegalBo : illegalBosSplit) {
141                                                illegalBusinessObjectsForSave.add(illegalBo.trim());
142                                        }
143                                }
144                        }
145                }
146                illegalBusinessObjectsForSaveInitialized = true;
147        }
148        return !illegalBusinessObjectsForSave.contains(bo.getClass().getName());
149    }
150
151
152    @Override
153        public <T extends BusinessObject> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
154                return businessObjectDao.findBySinglePrimaryKey(clazz, primaryKey);
155        }
156    @Override
157    public <T extends BusinessObject> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
158        return businessObjectDao.findByPrimaryKey(clazz, primaryKeys);
159    }
160
161    @Override
162    public Object retrieve(Object object) {
163        return businessObjectDao.retrieve(object);
164    }
165
166    @Override
167    public <T extends BusinessObject> Collection<T> findAll(Class<T> clazz) {
168        return businessObjectDao.findAll(clazz);
169    }
170    @Override
171    public <T extends BusinessObject> Collection<T> findAllOrderBy( Class<T> clazz, String sortField, boolean sortAscending ) {
172        final Map<String, ?> emptyParameters = Collections.emptyMap();
173        return businessObjectDao.findMatchingOrderBy(clazz, emptyParameters, sortField, sortAscending );
174    }
175
176    @Override
177    public <T extends BusinessObject> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
178        return businessObjectDao.findMatching(clazz, fieldValues);
179    }
180
181    @Override
182    public int countMatching(Class clazz, Map<String, ?> fieldValues) {
183        return businessObjectDao.countMatching(clazz, fieldValues);
184    }
185
186    @Override
187    public int countMatching(Class clazz, Map<String, ?> positiveFieldValues, Map<String, ?> negativeFieldValues) {
188        return businessObjectDao.countMatching(clazz, positiveFieldValues, negativeFieldValues);
189    }
190    @Override
191    public <T extends BusinessObject> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField, boolean sortAscending) {
192        return businessObjectDao.findMatchingOrderBy(clazz, fieldValues, sortField, sortAscending);
193    }
194    @Override
195    @Transactional
196    public void delete(Object bo) {
197        // just need to make sure erasure does not cause this method to attempt to process a list argument
198        if ( bo instanceof List ) {
199                delete( (List<PersistableBusinessObject>)bo );
200        } else {
201                businessObjectDao.delete(bo);
202        }
203    }
204
205    @Override
206    @Transactional
207    public void delete(List<? extends PersistableBusinessObject> boList) {
208        businessObjectDao.delete(boList);
209    }
210
211    @Override
212    @Transactional
213    public void deleteMatching(Class clazz, Map<String, ?> fieldValues) {
214        businessObjectDao.deleteMatching(clazz, fieldValues);
215    }
216
217    @Override
218    public BusinessObject getReferenceIfExists(BusinessObject bo, String referenceName) {
219        // if either argument is null, then we have nothing to do, complain and abort
220        if (ObjectUtils.isNull(bo)) {
221            throw new IllegalArgumentException("Passed in BusinessObject was null.  No processing can be done.");
222        }
223        if (StringUtils.isEmpty(referenceName)) {
224            throw new IllegalArgumentException("Passed in referenceName was empty or null.  No processing can be done.");
225        }
226
227        // make sure the attribute exists at all, throw exception if not
228        PropertyDescriptor propertyDescriptor;
229        try {
230            propertyDescriptor = PropertyUtils.getPropertyDescriptor(bo, referenceName);
231        }
232        catch (Exception e) {
233            throw new RuntimeException(e);
234        }
235        if (propertyDescriptor == null) {
236            throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + referenceName + "' does not exist " + "on class: '" + bo.getClass().getName() + "'. GFK");
237        }
238
239        // get the class of the attribute name
240        Class referenceClass = null;
241        if(bo instanceof PersistableBusinessObject) {
242            referenceClass = persistenceStructureService.getBusinessObjectAttributeClass(((PersistableBusinessObject)bo).getClass(), referenceName);
243        }
244        if(referenceClass == null) {
245            referenceClass = ObjectUtils.getPropertyType( bo, referenceName, persistenceStructureService );
246        }
247        if ( referenceClass == null ) {
248                referenceClass = propertyDescriptor.getPropertyType();
249        }
250
251        /*
252         * check for Person or EBO references in which case we can just get the reference through propertyutils
253         */
254        if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
255            try {
256                BusinessObject referenceBoExternalizable = (BusinessObject) PropertyUtils.getProperty(bo, referenceName);
257                if (referenceBoExternalizable!=null) {
258                        return referenceBoExternalizable;
259                }
260            } catch (Exception ex) {
261                //throw new RuntimeException("Unable to get property " + referenceName + " from a BO of class: " + bo.getClass().getName(),ex);
262                //Proceed further - get the BO relationship using responsible module service and proceed further
263            }
264        }
265
266        // make sure the class of the attribute descends from BusinessObject,
267        // otherwise throw an exception
268        if (!ExternalizableBusinessObject.class.isAssignableFrom(referenceClass) && !PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
269            throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + referenceName + ") is of class: " + "'" + referenceClass.getName() + "' and is not a " + "descendent of PersistableBusinessObject.  Only descendents of PersistableBusinessObject " + "can be used.");
270        }
271
272        // get the list of foreign-keys for this reference. if the reference
273        // does not exist, or is not a reference-descriptor, an exception will
274        // be thrown here.
275        //DataObjectRelationship boRel = dataObjectMetaDataService.getBusinessObjectRelationship( bo, referenceName );
276        DataObjectRelationship boRel = getDataObjectMetaDataService().getDataObjectRelationship(bo, bo.getClass(),
277                referenceName, "", true, false, false);
278        final Map<String,String> fkMap = boRel != null ? boRel.getParentToChildReferences() : Collections.<String, String>emptyMap();
279
280        boolean allFkeysHaveValues = true;
281        // walk through the foreign keys, testing each one to see if it has a value
282        Map<String,Object> pkMap = new HashMap<String,Object>();
283        for (Map.Entry<String, String> entry : fkMap.entrySet()) {
284            String fkFieldName = entry.getKey();
285            String pkFieldName = entry.getValue();
286
287            // attempt to retrieve the value for the given field
288            Object fkFieldValue;
289            try {
290                fkFieldValue = PropertyUtils.getProperty(bo, fkFieldName);
291            }
292            catch (Exception e) {
293                throw new RuntimeException(e);
294            }
295
296            // determine if there is a value for the field
297            if (ObjectUtils.isNull(fkFieldValue)) {
298                allFkeysHaveValues = false;
299                break; // no reason to continue processing the fkeys
300            }
301            else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
302                if (StringUtils.isEmpty((String) fkFieldValue)) {
303                    allFkeysHaveValues = false;
304                    break;
305                }
306                else {
307                    pkMap.put(pkFieldName, fkFieldValue);
308                }
309            }
310
311            // if there is a value, grab it
312            else {
313                pkMap.put(pkFieldName, fkFieldValue);
314            }
315        }
316
317        BusinessObject referenceBo = null;
318        // only do the retrieval if all Foreign Keys have values
319        if (allFkeysHaveValues) {
320                if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
321                        ModuleService responsibleModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(referenceClass);
322                                if(responsibleModuleService!=null) {
323                                        return responsibleModuleService.<ExternalizableBusinessObject>getExternalizableBusinessObject(referenceClass, pkMap);
324                                }
325                } else
326                        referenceBo = this.<BusinessObject>findByPrimaryKey(referenceClass, pkMap);
327        }
328
329        // return what we have, it'll be null if it was never retrieved
330        return referenceBo;
331    }
332    @Override
333    public void linkUserFields(Object bo) {
334        if (bo == null) {
335            throw new IllegalArgumentException("bo passed in was null");
336        }
337        if ( bo instanceof List ) {
338                linkUserFieldsInBoList( (List<PersistableBusinessObject>) bo );
339        } else {        
340                if ( bo instanceof PersistableBusinessObject ) {
341                        ((PersistableBusinessObject) bo).linkEditableUserFields();
342                        linkUserFieldsInBoList( Collections.<PersistableBusinessObject>singletonList( (PersistableBusinessObject)bo ) );
343                } else if ( bo instanceof PersistableBusinessObjectBaseAdapter ) {
344                        ((PersistableBusinessObjectBaseAdapter) bo).linkEditableUserFields();
345                }       
346        }
347    }
348
349    protected void linkUserFieldsInBoList(List<PersistableBusinessObject> list) {
350
351        // do nothing if there's nothing to process
352        if (list == null) {
353            throw new IllegalArgumentException("List of bos passed in was null");
354        } else if (list.isEmpty()) {
355            return;
356        }
357
358        Person person;
359        for (Object obj : list) {
360                // just need to protect in case non PBO passed in
361                if ( !(obj instanceof PersistableBusinessObject) ) {
362                        continue;
363                }
364                PersistableBusinessObject bo = (PersistableBusinessObject) obj;
365            // get a list of the reference objects on the BO
366            List<DataObjectRelationship> relationships = dataObjectMetaDataService.getDataObjectRelationships(
367                    bo.getClass());
368            for ( DataObjectRelationship rel : relationships ) {
369                if ( Person.class.isAssignableFrom( rel.getRelatedClass() ) ) {
370                    person = (Person) ObjectUtils.getPropertyValue(bo, rel.getParentAttributeName() );
371                    if (person != null) {
372                        // find the universal user ID relationship and link the field
373                        for ( Map.Entry<String,String> entry : rel.getParentToChildReferences().entrySet() ) {
374                            if ( "principalId".equals(entry.getValue())) {
375                                linkUserReference(bo, person, rel.getParentAttributeName(), entry.getKey() );
376                                break;
377                            }
378                        }
379                    }
380                }
381            }
382            if ( persistenceStructureService.isPersistable(bo.getClass())) {
383                    Map<String, Class> references = persistenceStructureService.listReferenceObjectFields(bo);
384
385                    // walk through the ref objects, only doing work if they are Person objects
386                    for ( Map.Entry<String, Class> entry : references.entrySet() ) {
387                        if (Person.class.isAssignableFrom(entry.getValue())) {
388                            person = (Person) ObjectUtils.getPropertyValue(bo, entry.getKey());
389                            if (person != null) {
390                                String fkFieldName = persistenceStructureService.getForeignKeyFieldName(bo.getClass(), entry.getKey(), "principalId");
391                                linkUserReference(bo, person, entry.getKey(), fkFieldName);
392                            }
393                        }
394                    }
395            }
396        }
397    }
398
399    /**
400     *
401     * This method links a single UniveralUser back to the parent BO based on the authoritative principalName.
402     *
403     * @param bo
404     * @param refFieldName
405     */
406    private void linkUserReference(PersistableBusinessObject bo, Person user, String refFieldName, String fkFieldName) {
407
408        // if the UserId field is blank, there's nothing we can do, so quit
409        if (StringUtils.isBlank(user.getPrincipalName())) {
410            return;
411        }
412
413        // attempt to load the user from the user-name, exit quietly if the user isnt found
414        Person userFromService = getPersonService().getPersonByPrincipalName(user.getPrincipalName());
415        if (userFromService == null) {
416            return;
417        }
418
419        // attempt to set the universalId on the parent BO
420        setBoField(bo, fkFieldName, userFromService.getPrincipalId());
421    }
422
423    private void setBoField(PersistableBusinessObject bo, String fieldName, Object fieldValue) {
424        try {
425            ObjectUtils.setObjectProperty(bo, fieldName, fieldValue.getClass(), fieldValue);
426        }
427        catch (Exception e) {
428            throw new RuntimeException("Could not set field [" + fieldName + "] on BO to value: " + fieldValue.toString() + " (see nested exception for details).", e);
429        }
430    }
431
432    @Override
433        public PersistableBusinessObject manageReadOnly(PersistableBusinessObject bo) {
434                return getBusinessObjectDao().manageReadOnly(bo);
435        }
436
437        /**
438     * Gets the businessObjectDao attribute.
439     *
440     * @return Returns the businessObjectDao.
441     */
442    protected BusinessObjectDao getBusinessObjectDao() {
443        return businessObjectDao;
444    }
445
446    /**
447     * Sets the businessObjectDao attribute value.
448     *
449     * @param businessObjectDao The businessObjectDao to set.
450     */
451    public void setBusinessObjectDao(BusinessObjectDao businessObjectDao) {
452        this.businessObjectDao = businessObjectDao;
453    }
454
455    /**
456     * Sets the persistenceStructureService attribute value.
457     *
458     * @param persistenceStructureService The persistenceStructureService to set.
459     */
460    public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
461        this.persistenceStructureService = persistenceStructureService;
462    }
463
464    /**
465     * Sets the kualiUserService attribute value.
466     */
467        public final void setPersonService(PersonService personService) {
468        this.personService = personService;
469    }
470
471        protected PersonService getPersonService() {
472        return personService != null ? personService : (personService = KimApiServiceLocator.getPersonService());
473    }
474
475    /**
476     * Sets the persistenceService attribute value.
477     *
478     * @param persistenceService The persistenceService to set.
479     */
480    public final void setPersistenceService(PersistenceService persistenceService) {
481        this.persistenceService = persistenceService;
482    }
483
484    protected DataObjectMetaDataService getDataObjectMetaDataService() {
485        return dataObjectMetaDataService;
486    }
487
488    public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetadataService) {
489        this.dataObjectMetaDataService = dataObjectMetadataService;
490    }
491
492}