001 /** 002 * Copyright 2005-2012 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 */ 016 package org.kuali.rice.krad.service.impl; 017 018 import org.apache.commons.beanutils.PropertyUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.kuali.rice.core.api.config.property.ConfigContext; 021 import org.kuali.rice.kim.api.identity.Person; 022 import org.kuali.rice.kim.api.identity.PersonService; 023 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 024 import org.kuali.rice.krad.bo.BusinessObject; 025 import org.kuali.rice.krad.bo.DataObjectRelationship; 026 import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 027 import org.kuali.rice.krad.bo.PersistableBusinessObject; 028 import org.kuali.rice.krad.dao.BusinessObjectDao; 029 import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException; 030 import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException; 031 import org.kuali.rice.krad.service.BusinessObjectService; 032 import org.kuali.rice.krad.service.DataObjectMetaDataService; 033 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 034 import org.kuali.rice.krad.service.ModuleService; 035 import org.kuali.rice.krad.service.PersistenceService; 036 import org.kuali.rice.krad.service.PersistenceStructureService; 037 import org.kuali.rice.krad.util.KRADConstants; 038 import org.kuali.rice.krad.util.ObjectUtils; 039 import org.springframework.transaction.annotation.Transactional; 040 041 import java.beans.PropertyDescriptor; 042 import java.util.Collection; 043 import java.util.Collections; 044 import java.util.HashMap; 045 import java.util.HashSet; 046 import java.util.List; 047 import java.util.Map; 048 import 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 055 public 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 = 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 }