View Javadoc

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