View Javadoc
1   /**
2    * Copyright 2005-2015 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 java.beans.PropertyDescriptor;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.commons.beanutils.PropertyUtils;
28  import org.apache.commons.lang.StringUtils;
29  import org.kuali.rice.core.api.config.property.ConfigContext;
30  import org.kuali.rice.kim.api.identity.Person;
31  import org.kuali.rice.kim.api.identity.PersonService;
32  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
33  import org.kuali.rice.krad.bo.BusinessObject;
34  import org.kuali.rice.krad.bo.DataObjectRelationship;
35  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
36  import org.kuali.rice.krad.bo.PersistableBusinessObject;
37  import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
38  import org.kuali.rice.krad.dao.BusinessObjectDao;
39  import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
40  import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
41  import org.kuali.rice.krad.service.BusinessObjectService;
42  import org.kuali.rice.krad.service.DataObjectMetaDataService;
43  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
44  import org.kuali.rice.krad.service.ModuleService;
45  import org.kuali.rice.krad.service.PersistenceService;
46  import org.kuali.rice.krad.service.PersistenceStructureService;
47  import org.kuali.rice.krad.util.KRADConstants;
48  import org.kuali.rice.krad.util.LegacyDataFramework;
49  import org.kuali.rice.krad.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   * @deprecated use new KRAD Data framework {@link org.kuali.rice.krad.data.DataObjectService}
57   */
58  @Deprecated
59  @LegacyDataFramework
60  public class BusinessObjectServiceImpl implements BusinessObjectService {
61  
62      private PersistenceService persistenceService;
63      private PersistenceStructureService persistenceStructureService;
64      private BusinessObjectDao businessObjectDao;
65      private PersonService personService;
66      private DataObjectMetaDataService dataObjectMetaDataService;
67      private LegacyDataFramework legacyDataFramework;
68  
69      private boolean illegalBusinessObjectsForSaveInitialized;
70      private final Set<String> illegalBusinessObjectsForSave = new HashSet<String>();
71  
72      @Override
73      @Transactional
74      public <T extends PersistableBusinessObject> T save(T bo) {
75      	validateBusinessObjectForSave(bo);
76          return (T) businessObjectDao.save(bo);
77      }
78  
79      @Override
80      @Transactional
81      public List<? extends PersistableBusinessObject> save(List<? extends PersistableBusinessObject> businessObjects) {
82          validateBusinessObjectForSave(businessObjects);
83          return businessObjectDao.save(businessObjects);
84      }
85  
86      @Override
87      @Transactional
88      public PersistableBusinessObject linkAndSave(PersistableBusinessObject bo) {
89      	validateBusinessObjectForSave(bo);
90          persistenceService.linkObjects(bo);
91          return businessObjectDao.save(bo);
92      }
93  
94      @Override
95      @Transactional
96      public List<? extends PersistableBusinessObject> linkAndSave(List<? extends PersistableBusinessObject> businessObjects) {
97          validateBusinessObjectForSave(businessObjects);
98          return businessObjectDao.save(businessObjects);
99      }
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 }