View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.service.impl;
17  
18  import java.beans.PropertyDescriptor;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.commons.beanutils.PropertyUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.kuali.rice.core.config.ConfigContext;
32  import org.kuali.rice.kim.bo.Person;
33  import org.kuali.rice.kim.service.KIMServiceLocator;
34  import org.kuali.rice.kim.service.PersonService;
35  import org.kuali.rice.kns.bo.BusinessObject;
36  import org.kuali.rice.kns.bo.BusinessObjectRelationship;
37  import org.kuali.rice.kns.bo.ExternalizableBusinessObject;
38  import org.kuali.rice.kns.bo.PersistableBusinessObject;
39  import org.kuali.rice.kns.dao.BusinessObjectDao;
40  import org.kuali.rice.kns.exception.ObjectNotABusinessObjectRuntimeException;
41  import org.kuali.rice.kns.exception.ReferenceAttributeDoesntExistException;
42  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
43  import org.kuali.rice.kns.service.BusinessObjectService;
44  import org.kuali.rice.kns.service.KNSServiceLocator;
45  import org.kuali.rice.kns.service.ModuleService;
46  import org.kuali.rice.kns.service.PersistenceService;
47  import org.kuali.rice.kns.service.PersistenceStructureService;
48  import org.kuali.rice.kns.util.KNSConstants;
49  import org.kuali.rice.kns.util.ObjectUtils;
50  import org.springframework.transaction.annotation.Transactional;
51  
52  /**
53   * This class is the service implementation for the BusinessObjectService structure. This is the default implementation, that is
54   * delivered with Kuali.
55   */
56  
57  public class BusinessObjectServiceImpl implements BusinessObjectService {
58  
59      private PersistenceService persistenceService;
60      private PersistenceStructureService persistenceStructureService;
61      private BusinessObjectDao businessObjectDao;
62      private PersonService personService;
63      private BusinessObjectMetaDataService businessObjectMetaDataService;
64  
65      private boolean illegalBusinessObjectsForSaveInitialized = false;
66      private Set<String> illegalBusinessObjectsForSave = new HashSet<String>();
67      
68      /**
69       * @see org.kuali.rice.kns.service.BusinessObjectService#save(org.kuali.bo.BusinessObject)
70       */
71      @Transactional
72      public void save(PersistableBusinessObject bo) {
73      	validateBusinessObjectForSave(bo);
74          businessObjectDao.save(bo);
75      }
76  
77      /**
78       * @see org.kuali.rice.kns.service.BusinessObjectService#save(java.util.List)
79       */
80      @Transactional
81      public void save(List<? extends PersistableBusinessObject> businessObjects) {
82          validateBusinessObjectForSave(businessObjects);
83          businessObjectDao.save(businessObjects);
84      }
85  
86      /**
87       * 
88       * @see org.kuali.rice.kns.service.BusinessObjectService#linkAndSave(org.kuali.rice.kns.bo.BusinessObject)
89       */
90      @Transactional
91      public void linkAndSave(PersistableBusinessObject bo) {
92      	validateBusinessObjectForSave(bo);
93          persistenceService.linkObjects(bo);
94          businessObjectDao.save(bo);
95      }
96  
97      /**
98       * 
99       * @see org.kuali.rice.kns.service.BusinessObjectService#linkAndSave(java.util.List)
100      */
101     @Transactional
102     public void linkAndSave(List<? extends PersistableBusinessObject> businessObjects) {
103         validateBusinessObjectForSave(businessObjects);
104         businessObjectDao.save(businessObjects);
105     }
106 
107     /**
108      * Validates that the 
109      * This method ...
110      * 
111      * @param bo
112      */
113     protected void validateBusinessObjectForSave(PersistableBusinessObject bo) {
114     	if (bo == null) {
115             throw new IllegalArgumentException("Object passed in is null");
116         }
117         if (!isBusinessObjectAllowedForSave(bo)) {
118         	throw new IllegalArgumentException("Object passed in is a BusinessObject but has been restricted from save operations according to configuration parameter '" + KNSConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE);
119         }
120     }
121     
122     protected void validateBusinessObjectForSave(List<? extends PersistableBusinessObject> businessObjects) {
123     	for (PersistableBusinessObject bo : businessObjects) {
124     		 if (bo == null) {
125                  throw new IllegalArgumentException("One of the objects in the List is null.");
126              }
127     		 if (!isBusinessObjectAllowedForSave(bo)) {
128     			 throw new IllegalArgumentException("One of the objects in the List is a BusinessObject but has been restricted from save operations according to configuration parameter '" + KNSConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE
129     					 + "  Passed in type was '" + bo.getClass().getName() + "'.");
130     		 }
131     	}
132     }
133     
134     
135     /**
136      * Returns true if the BusinessObjectService should be permitted to save instances of the given PersistableBusinessObject.
137      * Implementation checks a configuration parameter for class names of PersistableBusinessObjects that shouldn't be allowed
138      * to be saved.
139      */
140     protected boolean isBusinessObjectAllowedForSave(PersistableBusinessObject bo) {
141     	if (!illegalBusinessObjectsForSaveInitialized) {
142     		synchronized (this) {
143     			boolean applyCheck = true;
144     			String applyCheckValue = ConfigContext.getCurrentContextConfig().getProperty(KNSConstants.Config.APPLY_ILLEGAL_BUSINESS_OBJECT_FOR_SAVE_CHECK);
145     			if (!StringUtils.isEmpty(applyCheckValue)) {
146     				applyCheck = new Boolean(applyCheckValue);
147     			}
148     			if (applyCheck) {
149     				String illegalBos = ConfigContext.getCurrentContextConfig().getProperty(KNSConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE);
150     				if (!StringUtils.isEmpty(illegalBos)) {
151     					String[] illegalBosSplit = illegalBos.split(",");
152     					for (String illegalBo : illegalBosSplit) {
153     						illegalBusinessObjectsForSave.add(illegalBo.trim());
154     					}
155     				}
156     			}
157     		}
158     		illegalBusinessObjectsForSaveInitialized = true;
159     	}
160     	return !illegalBusinessObjectsForSave.contains(bo.getClass().getName());
161     }
162     
163     /**
164      * @see org.kuali.rice.kns.service.BusinessObjectService#findByPrimaryKey(java.lang.Class, java.lang.Object)
165      */
166     @SuppressWarnings("unchecked")
167 	public <T> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
168 		return (T)businessObjectDao.findBySinglePrimaryKey(clazz, primaryKey);
169 	}
170 
171 	/**
172      * @see org.kuali.rice.kns.service.BusinessObjectService#findByPrimaryKey(java.lang.Class, java.util.Map)
173      */
174     public PersistableBusinessObject findByPrimaryKey(Class clazz, Map primaryKeys) {
175         return businessObjectDao.findByPrimaryKey(clazz, primaryKeys);
176     }
177 
178     /**
179      * @see org.kuali.rice.kns.service.BusinessObjectService#retrieve(java.lang.Object)
180      */
181     public PersistableBusinessObject retrieve(PersistableBusinessObject object) {
182         return businessObjectDao.retrieve(object);
183     }
184 
185     /**
186      * @see org.kuali.rice.kns.service.BusinessObjectService#findAll(java.lang.Class)
187      */
188     public Collection findAll(Class clazz) {
189         Collection coll = businessObjectDao.findAll(clazz);
190         return new ArrayList(coll);
191     }
192 
193     /**
194      * @see org.kuali.rice.kns.service.BusinessObjectService#findMatching(java.lang.Class, java.util.Map)
195      */
196     public Collection findMatching(Class clazz, Map fieldValues) {
197         return new ArrayList(businessObjectDao.findMatching(clazz, fieldValues));
198     }
199 
200     /**
201      * @see org.kuali.rice.kns.service.BusinessObjectService#countMatching(java.lang.Class, java.util.Map)
202      */
203     public int countMatching(Class clazz, Map fieldValues) {
204         return businessObjectDao.countMatching(clazz, fieldValues);
205     }
206 
207     /**
208      * @see org.kuali.rice.kns.service.BusinessObjectService#countMatching(java.lang.Class, java.util.Map, java.util.Map)
209      */
210     public int countMatching(Class clazz, Map positiveFieldValues, Map negativeFieldValues) {
211         return businessObjectDao.countMatching(clazz, positiveFieldValues, negativeFieldValues);
212     }
213     
214     /**
215      * @see org.kuali.rice.kns.service.BusinessObjectService#findMatchingOrderBy(java.lang.Class, java.util.Map)
216      */
217     public Collection findMatchingOrderBy(Class clazz, Map fieldValues, String sortField, boolean sortAscending) {
218         return new ArrayList(businessObjectDao.findMatchingOrderBy(clazz, fieldValues, sortField, sortAscending));
219     }
220 
221     /**
222      * @see org.kuali.rice.kns.service.BusinessObjectService#delete(org.kuali.bo.BusinessObject)
223      */
224     @Transactional
225     public void delete(PersistableBusinessObject bo) {
226         businessObjectDao.delete(bo);
227     }
228 
229     /**
230      * @see org.kuali.rice.kns.service.BusinessObjectService#delete(java.util.List)
231      */
232     @Transactional
233     public void delete(List<? extends PersistableBusinessObject> boList) {
234         businessObjectDao.delete(boList);
235     }
236 
237 
238     /**
239      * @see org.kuali.rice.kns.service.BusinessObjectService#deleteMatching(java.lang.Class, java.util.Map)
240      */
241     @Transactional
242     public void deleteMatching(Class clazz, Map fieldValues) {
243         businessObjectDao.deleteMatching(clazz, fieldValues);
244     }
245 
246     /**
247      * @see org.kuali.rice.kns.service.BusinessObjectService#getReferenceIfExists(org.kuali.rice.kns.bo.BusinessObject, java.lang.String)
248      */
249     public BusinessObject getReferenceIfExists(BusinessObject bo, String referenceName) {
250 
251         PersistableBusinessObject referenceBo = null;
252         boolean allFkeysHaveValues = true;
253 
254         // if either argument is null, then we have nothing to do, complain and abort
255         if (ObjectUtils.isNull(bo)) {
256             throw new IllegalArgumentException("Passed in BusinessObject was null.  No processing can be done.");
257         }
258         if (StringUtils.isEmpty(referenceName)) {
259             throw new IllegalArgumentException("Passed in referenceName was empty or null.  No processing can be done.");
260         }
261 
262         // make sure the attribute exists at all, throw exception if not
263         PropertyDescriptor propertyDescriptor;
264         try {
265             propertyDescriptor = PropertyUtils.getPropertyDescriptor(bo, referenceName);
266         }
267         catch (Exception e) {
268             throw new RuntimeException(e);
269         }
270         if (propertyDescriptor == null) {
271             throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + referenceName + "' does not exist " + "on class: '" + bo.getClass().getName() + "'. GFK");
272         }
273 
274         // get the class of the attribute name
275         Class referenceClass = ObjectUtils.getPropertyType( bo, referenceName, persistenceStructureService );
276         if ( referenceClass == null ) {
277         	referenceClass = propertyDescriptor.getPropertyType();
278         }
279 
280         /*
281          * check for Person or EBO references in which case we can just get the reference through propertyutils
282          */
283         if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
284             try {
285             	BusinessObject referenceBoExternalizable = (BusinessObject) PropertyUtils.getProperty(bo, referenceName);
286             	if(referenceBoExternalizable!=null)
287             		return referenceBoExternalizable;
288             } catch (Exception ex) {
289                 //throw new RuntimeException("Unable to get property " + referenceName + " from a BO of class: " + bo.getClass().getName(),ex);
290             	//Proceed further - get the BO relationship using responsible module service and proceed further
291             }
292         }
293 
294         // make sure the class of the attribute descends from BusinessObject,
295         // otherwise throw an exception
296         if (!ExternalizableBusinessObject.class.isAssignableFrom(referenceClass) && !PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
297             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.");
298         }
299 
300         // get the list of foreign-keys for this reference. if the reference
301         // does not exist, or is not a reference-descriptor, an exception will
302         // be thrown here.
303         BusinessObjectRelationship boRel = businessObjectMetaDataService.getBusinessObjectRelationship( bo, referenceName );
304         Map<String,String> fkMap = null;
305         if ( boRel != null ) {
306         	fkMap = boRel.getParentToChildReferences();
307         } else {
308         	fkMap = Collections.EMPTY_MAP;
309         }
310 
311         // walk through the foreign keys, testing each one to see if it has a value
312         Map pkMap = new HashMap();
313         for (Iterator iter = fkMap.keySet().iterator(); iter.hasNext();) {
314             String fkFieldName = (String) iter.next();
315             String pkFieldName = (String) fkMap.get(fkFieldName);
316 
317             // attempt to retrieve the value for the given field
318             Object fkFieldValue;
319             try {
320                 fkFieldValue = PropertyUtils.getProperty(bo, fkFieldName);
321             }
322             catch (Exception e) {
323                 throw new RuntimeException(e);
324             }
325 
326             // determine if there is a value for the field
327             if (ObjectUtils.isNull(fkFieldValue)) {
328                 allFkeysHaveValues = false;
329                 break; // no reason to continue processing the fkeys
330             }
331             else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
332                 if (StringUtils.isEmpty((String) fkFieldValue)) {
333                     allFkeysHaveValues = false;
334                     break;
335                 }
336                 else {
337                     pkMap.put(pkFieldName, fkFieldValue);
338                 }
339             }
340 
341             // if there is a value, grab it
342             else {
343                 pkMap.put(pkFieldName, fkFieldValue);
344             }
345         }
346 
347         // only do the retrieval if all Foreign Keys have values
348         if (allFkeysHaveValues) {
349         	if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
350         		ModuleService responsibleModuleService = KNSServiceLocator.getKualiModuleService().getResponsibleModuleService(referenceClass);
351 				if(responsibleModuleService!=null) {
352 					return (BusinessObject)responsibleModuleService.getExternalizableBusinessObject(referenceClass, pkMap);
353 				}
354         	} else
355         		referenceBo = findByPrimaryKey(referenceClass, pkMap);
356         }
357 
358         // return what we have, it'll be null if it was never retrieved
359         return referenceBo;
360     }
361 
362     /**
363      * 
364      * @see org.kuali.rice.kns.service.BusinessObjectService#linkUserFields(org.kuali.rice.kns.bo.BusinessObject)
365      */
366     public void linkUserFields(PersistableBusinessObject bo) {
367         if (bo == null) {
368             throw new IllegalArgumentException("bo passed in was null");
369         }
370 
371         bo.linkEditableUserFields();
372        
373         linkUserFields( Collections.singletonList( bo ) );
374     }
375 
376     /**
377      * 
378      * @see org.kuali.rice.kns.service.BusinessObjectService#linkUserFields(java.util.List)
379      */
380     public void linkUserFields(List<PersistableBusinessObject> bos) {
381 
382         // do nothing if there's nothing to process
383         if (bos == null) {
384             throw new IllegalArgumentException("List of bos passed in was null");
385         }
386         else if (bos.isEmpty()) {
387             return;
388         }
389 
390         Person person = null;
391 
392         for (PersistableBusinessObject bo : bos) {
393             // get a list of the reference objects on the BO
394             List<BusinessObjectRelationship> relationships = businessObjectMetaDataService.getBusinessObjectRelationships( bo );
395             for ( BusinessObjectRelationship rel : relationships ) {
396                 if ( Person.class.isAssignableFrom( rel.getRelatedClass() ) ) {
397                     person = (Person) ObjectUtils.getPropertyValue(bo, rel.getParentAttributeName() );
398                     if (person != null) {
399                         // find the universal user ID relationship and link the field
400                         for ( Map.Entry<String,String> entry : rel.getParentToChildReferences().entrySet() ) {
401                             if ( entry.getValue().equals( "principalId" ) ) {
402                                 linkUserReference(bo, person, rel.getParentAttributeName(), entry.getKey() );
403                                 break;
404                             }
405                         }
406                     }                    
407                 }
408             }
409             if ( persistenceStructureService.isPersistable(bo.getClass())) {
410 	            Map<String, Class> references = persistenceStructureService.listReferenceObjectFields(bo);
411 	
412 	            // walk through the ref objects, only doing work if they are Person objects
413 	            for ( String refField : references.keySet() ) {
414 	                Class<?> refClass = references.get(refField);
415 	                if (Person.class.isAssignableFrom(refClass)) {
416 	                    person = (Person) ObjectUtils.getPropertyValue(bo, refField);
417 	                    if (person != null) {
418 	                        String fkFieldName = persistenceStructureService.getForeignKeyFieldName(bo.getClass(), refField, "principalId");
419 	                        linkUserReference(bo, person, refField, fkFieldName);
420 	                    }
421 	                }
422 	            }
423             }
424         }
425     }
426 
427     /**
428      * 
429      * This method links a single UniveralUser back to the parent BO based on the authoritative principalName.
430      * 
431      * @param bo
432      * @param referenceFieldName
433      * @param referenceClass
434      */
435     private void linkUserReference(PersistableBusinessObject bo, Person user, String refFieldName, String fkFieldName) {
436 
437         // if the UserId field is blank, there's nothing we can do, so quit
438         if (StringUtils.isBlank(user.getPrincipalName())) {
439             return;
440         }
441 
442         // attempt to load the user from the user-name, exit quietly if the user isnt found
443         Person userFromService = getPersonService().getPersonByPrincipalName(user.getPrincipalName());
444         if (userFromService == null) {
445             return;
446         }
447 
448         // attempt to set the universalId on the parent BO
449         setBoField(bo, fkFieldName, userFromService.getPrincipalId());
450     }
451 
452     private void setBoField(PersistableBusinessObject bo, String fieldName, Object fieldValue) {
453         try {
454             ObjectUtils.setObjectProperty(bo, fieldName, fieldValue.getClass(), fieldValue);
455         }
456         catch (Exception e) {
457             throw new RuntimeException("Could not set field [" + fieldName + "] on BO to value: " + fieldValue.toString() + " (see nested exception for details).", e);
458         }
459     }
460 
461     /**
462      * Gets the businessObjectDao attribute.
463      * 
464      * @return Returns the businessObjectDao.
465      */
466     protected BusinessObjectDao getBusinessObjectDao() {
467         return businessObjectDao;
468     }
469 
470     /**
471      * Sets the businessObjectDao attribute value.
472      * 
473      * @param businessObjectDao The businessObjectDao to set.
474      */
475     public void setBusinessObjectDao(BusinessObjectDao businessObjectDao) {
476         this.businessObjectDao = businessObjectDao;
477     }
478 
479     /**
480      * Sets the persistenceStructureService attribute value.
481      * 
482      * @param persistenceStructureService The persistenceStructureService to set.
483      */
484     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
485         this.persistenceStructureService = persistenceStructureService;
486     }
487 
488     /**
489      * Sets the kualiUserService attribute value.
490      */
491     @SuppressWarnings("unchecked")
492 	public final void setPersonService(PersonService personService) {
493         this.personService = personService;
494     }
495 
496     @SuppressWarnings("unchecked")
497 	protected PersonService getPersonService() {
498         return personService != null ? personService : (personService = KIMServiceLocator.getPersonService());
499     }
500 
501     /**
502      * Sets the persistenceService attribute value.
503      * 
504      * @param persistenceService The persistenceService to set.
505      */
506     public final void setPersistenceService(PersistenceService persistenceService) {
507         this.persistenceService = persistenceService;
508     }
509 
510     protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
511         return businessObjectMetaDataService;
512     }
513 
514     public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService boMetadataService) {
515         this.businessObjectMetaDataService = boMetadataService;
516     }
517 
518 }