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