View Javadoc

1   /**
2    * Copyright 2005-2013 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 = null;
230         if(bo instanceof PersistableBusinessObject) {
231             referenceClass = persistenceStructureService.getBusinessObjectAttributeClass(((PersistableBusinessObject)bo).getClass(), referenceName);
232         }
233         if(referenceClass == null) {
234             referenceClass = ObjectUtils.getPropertyType( bo, referenceName, persistenceStructureService );
235         }
236         if ( referenceClass == null ) {
237         	referenceClass = propertyDescriptor.getPropertyType();
238         }
239 
240         /*
241          * check for Person or EBO references in which case we can just get the reference through propertyutils
242          */
243         if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
244             try {
245             	BusinessObject referenceBoExternalizable = (BusinessObject) PropertyUtils.getProperty(bo, referenceName);
246             	if (referenceBoExternalizable!=null) {
247             		return referenceBoExternalizable;
248                 }
249             } catch (Exception ex) {
250                 //throw new RuntimeException("Unable to get property " + referenceName + " from a BO of class: " + bo.getClass().getName(),ex);
251             	//Proceed further - get the BO relationship using responsible module service and proceed further
252             }
253         }
254 
255         // make sure the class of the attribute descends from BusinessObject,
256         // otherwise throw an exception
257         if (!ExternalizableBusinessObject.class.isAssignableFrom(referenceClass) && !PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
258             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.");
259         }
260 
261         // get the list of foreign-keys for this reference. if the reference
262         // does not exist, or is not a reference-descriptor, an exception will
263         // be thrown here.
264         //DataObjectRelationship boRel = dataObjectMetaDataService.getBusinessObjectRelationship( bo, referenceName );
265         DataObjectRelationship boRel = dataObjectMetaDataService.getDataObjectRelationship(bo, bo.getClass(),
266                 referenceName, "", true, false, false);
267         final Map<String,String> fkMap = boRel != null ? boRel.getParentToChildReferences() : Collections.<String, String>emptyMap();
268 
269         boolean allFkeysHaveValues = true;
270         // walk through the foreign keys, testing each one to see if it has a value
271         Map<String,Object> pkMap = new HashMap<String,Object>();
272         for (Map.Entry<String, String> entry : fkMap.entrySet()) {
273             String fkFieldName = entry.getKey();
274             String pkFieldName = entry.getValue();
275 
276             // attempt to retrieve the value for the given field
277             Object fkFieldValue;
278             try {
279                 fkFieldValue = PropertyUtils.getProperty(bo, fkFieldName);
280             }
281             catch (Exception e) {
282                 throw new RuntimeException(e);
283             }
284 
285             // determine if there is a value for the field
286             if (ObjectUtils.isNull(fkFieldValue)) {
287                 allFkeysHaveValues = false;
288                 break; // no reason to continue processing the fkeys
289             }
290             else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
291                 if (StringUtils.isEmpty((String) fkFieldValue)) {
292                     allFkeysHaveValues = false;
293                     break;
294                 }
295                 else {
296                     pkMap.put(pkFieldName, fkFieldValue);
297                 }
298             }
299 
300             // if there is a value, grab it
301             else {
302                 pkMap.put(pkFieldName, fkFieldValue);
303             }
304         }
305 
306         BusinessObject referenceBo = null;
307         // only do the retrieval if all Foreign Keys have values
308         if (allFkeysHaveValues) {
309         	if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
310         		ModuleService responsibleModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(referenceClass);
311 				if(responsibleModuleService!=null) {
312 					return responsibleModuleService.<ExternalizableBusinessObject>getExternalizableBusinessObject(referenceClass, pkMap);
313 				}
314         	} else
315         		referenceBo = this.<BusinessObject>findByPrimaryKey(referenceClass, pkMap);
316         }
317 
318         // return what we have, it'll be null if it was never retrieved
319         return referenceBo;
320     }
321     @Override
322     public void linkUserFields(PersistableBusinessObject bo) {
323         if (bo == null) {
324             throw new IllegalArgumentException("bo passed in was null");
325         }
326 
327         bo.linkEditableUserFields();
328        
329         linkUserFields( Collections.singletonList( bo ) );
330     }
331 
332     @Override
333     public void linkUserFields(List<PersistableBusinessObject> bos) {
334 
335         // do nothing if there's nothing to process
336         if (bos == null) {
337             throw new IllegalArgumentException("List of bos passed in was null");
338         }
339         else if (bos.isEmpty()) {
340             return;
341         }
342 
343 
344         Person person;
345         for (PersistableBusinessObject bo : bos) {
346             // get a list of the reference objects on the BO
347             List<DataObjectRelationship> relationships = dataObjectMetaDataService.getDataObjectRelationships(
348                     bo.getClass());
349             for ( DataObjectRelationship rel : relationships ) {
350                 if ( Person.class.isAssignableFrom( rel.getRelatedClass() ) ) {
351                     person = (Person) ObjectUtils.getPropertyValue(bo, rel.getParentAttributeName() );
352                     if (person != null) {
353                         // find the universal user ID relationship and link the field
354                         for ( Map.Entry<String,String> entry : rel.getParentToChildReferences().entrySet() ) {
355                             if ( "principalId".equals(entry.getValue())) {
356                                 linkUserReference(bo, person, rel.getParentAttributeName(), entry.getKey() );
357                                 break;
358                             }
359                         }
360                     }                    
361                 }
362             }
363             if ( persistenceStructureService.isPersistable(bo.getClass())) {
364 	            Map<String, Class> references = persistenceStructureService.listReferenceObjectFields(bo);
365 
366 	            // walk through the ref objects, only doing work if they are Person objects
367 	            for ( Map.Entry<String, Class> entry : references.entrySet() ) {
368 	                if (Person.class.isAssignableFrom(entry.getValue())) {
369 	                    person = (Person) ObjectUtils.getPropertyValue(bo, entry.getKey());
370 	                    if (person != null) {
371 	                        String fkFieldName = persistenceStructureService.getForeignKeyFieldName(bo.getClass(), entry.getKey(), "principalId");
372 	                        linkUserReference(bo, person, entry.getKey(), fkFieldName);
373 	                    }
374 	                }
375 	            }
376             }
377         }
378     }
379 
380     /**
381      * 
382      * This method links a single UniveralUser back to the parent BO based on the authoritative principalName.
383      * 
384      * @param bo
385      * @param refFieldName
386      */
387     private void linkUserReference(PersistableBusinessObject bo, Person user, String refFieldName, String fkFieldName) {
388 
389         // if the UserId field is blank, there's nothing we can do, so quit
390         if (StringUtils.isBlank(user.getPrincipalName())) {
391             return;
392         }
393 
394         // attempt to load the user from the user-name, exit quietly if the user isnt found
395         Person userFromService = getPersonService().getPersonByPrincipalName(user.getPrincipalName());
396         if (userFromService == null) {
397             return;
398         }
399 
400         // attempt to set the universalId on the parent BO
401         setBoField(bo, fkFieldName, userFromService.getPrincipalId());
402     }
403 
404     private void setBoField(PersistableBusinessObject bo, String fieldName, Object fieldValue) {
405         try {
406             ObjectUtils.setObjectProperty(bo, fieldName, fieldValue.getClass(), fieldValue);
407         }
408         catch (Exception e) {
409             throw new RuntimeException("Could not set field [" + fieldName + "] on BO to value: " + fieldValue.toString() + " (see nested exception for details).", e);
410         }
411     }
412 
413     @Override
414 	public PersistableBusinessObject manageReadOnly(PersistableBusinessObject bo) {
415 		return getBusinessObjectDao().manageReadOnly(bo);
416 	}
417 
418 	/**
419      * Gets the businessObjectDao attribute.
420      * 
421      * @return Returns the businessObjectDao.
422      */
423     protected BusinessObjectDao getBusinessObjectDao() {
424         return businessObjectDao;
425     }
426 
427     /**
428      * Sets the businessObjectDao attribute value.
429      * 
430      * @param businessObjectDao The businessObjectDao to set.
431      */
432     public void setBusinessObjectDao(BusinessObjectDao businessObjectDao) {
433         this.businessObjectDao = businessObjectDao;
434     }
435 
436     /**
437      * Sets the persistenceStructureService attribute value.
438      * 
439      * @param persistenceStructureService The persistenceStructureService to set.
440      */
441     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
442         this.persistenceStructureService = persistenceStructureService;
443     }
444 
445     /**
446      * Sets the kualiUserService attribute value.
447      */
448 	public final void setPersonService(PersonService personService) {
449         this.personService = personService;
450     }
451 
452 	protected PersonService getPersonService() {
453         return personService != null ? personService : (personService = KimApiServiceLocator.getPersonService());
454     }
455 
456     /**
457      * Sets the persistenceService attribute value.
458      * 
459      * @param persistenceService The persistenceService to set.
460      */
461     public final void setPersistenceService(PersistenceService persistenceService) {
462         this.persistenceService = persistenceService;
463     }
464 
465     protected DataObjectMetaDataService getDataObjectMetaDataService() {
466         return dataObjectMetaDataService;
467     }
468 
469     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetadataService) {
470         this.dataObjectMetaDataService = dataObjectMetadataService;
471     }
472 
473 }