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 org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.config.property.ConfigContext; 021import org.kuali.rice.kim.api.identity.Person; 022import org.kuali.rice.kim.api.identity.PersonService; 023import org.kuali.rice.kim.api.services.KimApiServiceLocator; 024import org.kuali.rice.krad.bo.BusinessObject; 025import org.kuali.rice.krad.bo.DataObjectRelationship; 026import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 027import org.kuali.rice.krad.bo.PersistableBusinessObject; 028import org.kuali.rice.krad.dao.BusinessObjectDao; 029import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException; 030import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException; 031import org.kuali.rice.krad.service.BusinessObjectService; 032import org.kuali.rice.krad.service.DataObjectMetaDataService; 033import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 034import org.kuali.rice.krad.service.ModuleService; 035import org.kuali.rice.krad.service.PersistenceService; 036import org.kuali.rice.krad.service.PersistenceStructureService; 037import org.kuali.rice.krad.util.KRADConstants; 038import org.kuali.rice.krad.util.ObjectUtils; 039import org.springframework.transaction.annotation.Transactional; 040 041import java.beans.PropertyDescriptor; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.HashMap; 045import java.util.HashSet; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049 050/** 051 * This class is the service implementation for the BusinessObjectService structure. This is the default implementation, that is 052 * delivered with Kuali. 053 */ 054 055public class BusinessObjectServiceImpl implements BusinessObjectService { 056 057 private PersistenceService persistenceService; 058 private PersistenceStructureService persistenceStructureService; 059 private BusinessObjectDao businessObjectDao; 060 private PersonService personService; 061 private DataObjectMetaDataService dataObjectMetaDataService; 062 063 private boolean illegalBusinessObjectsForSaveInitialized; 064 private final Set<String> illegalBusinessObjectsForSave = new HashSet<String>(); 065 066 @Override 067 @Transactional 068 public <T extends PersistableBusinessObject> T save(T bo) { 069 validateBusinessObjectForSave(bo); 070 return (T) businessObjectDao.save(bo); 071 } 072 073 @Override 074 @Transactional 075 public List<? extends PersistableBusinessObject> save(List<? extends PersistableBusinessObject> businessObjects) { 076 validateBusinessObjectForSave(businessObjects); 077 return businessObjectDao.save(businessObjects); 078 } 079 080 @Override 081 @Transactional 082 public PersistableBusinessObject linkAndSave(PersistableBusinessObject bo) { 083 validateBusinessObjectForSave(bo); 084 persistenceService.linkObjects(bo); 085 return businessObjectDao.save(bo); 086 } 087 088 @Override 089 @Transactional 090 public List<? extends PersistableBusinessObject> linkAndSave(List<? extends PersistableBusinessObject> businessObjects) { 091 validateBusinessObjectForSave(businessObjects); 092 return businessObjectDao.save(businessObjects); 093 } 094 095 protected void validateBusinessObjectForSave(PersistableBusinessObject bo) { 096 if (bo == null) { 097 throw new IllegalArgumentException("Object passed in is null"); 098 } 099 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}