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.lang.StringUtils; 019import org.apache.commons.lang.time.StopWatch; 020import org.kuali.rice.core.api.CoreApiServiceLocator; 021import org.kuali.rice.core.api.config.ConfigurationException; 022import org.kuali.rice.core.api.config.property.ConfigurationService; 023import org.kuali.rice.core.api.datetime.DateTimeService; 024import org.kuali.rice.core.api.util.RiceKeyConstants; 025import org.kuali.rice.core.framework.persistence.jta.TransactionalNoValidationExceptionRollback; 026import org.kuali.rice.kew.api.WorkflowDocument; 027import org.kuali.rice.kew.api.exception.WorkflowException; 028import org.kuali.rice.kim.api.identity.Person; 029import org.kuali.rice.kim.api.identity.PersonService; 030import org.kuali.rice.kim.api.services.KimApiServiceLocator; 031import org.kuali.rice.krad.UserSessionUtils; 032import org.kuali.rice.krad.UserSession; 033import org.kuali.rice.krad.bo.AdHocRoutePerson; 034import org.kuali.rice.krad.bo.AdHocRouteRecipient; 035import org.kuali.rice.krad.bo.AdHocRouteWorkgroup; 036import org.kuali.rice.krad.bo.BusinessObject; 037import org.kuali.rice.krad.bo.DocumentHeader; 038import org.kuali.rice.krad.bo.Note; 039import org.kuali.rice.krad.bo.PersistableBusinessObject; 040import org.kuali.rice.krad.dao.DocumentDao; 041import org.kuali.rice.krad.datadictionary.exception.UnknownDocumentTypeException; 042import org.kuali.rice.krad.document.Document; 043import org.kuali.rice.krad.document.DocumentAuthorizer; 044import org.kuali.rice.krad.document.DocumentPresentationController; 045import org.kuali.rice.krad.maintenance.MaintenanceDocument; 046import org.kuali.rice.krad.maintenance.MaintenanceDocumentBase; 047import org.kuali.rice.krad.exception.DocumentAuthorizationException; 048import org.kuali.rice.krad.exception.ValidationException; 049import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent; 050import org.kuali.rice.krad.rules.rule.event.BlanketApproveDocumentEvent; 051import org.kuali.rice.krad.rules.rule.event.CompleteDocumentEvent; 052import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent; 053import org.kuali.rice.krad.rules.rule.event.RouteDocumentEvent; 054import org.kuali.rice.krad.rules.rule.event.SaveDocumentEvent; 055import org.kuali.rice.krad.rules.rule.event.SaveEvent; 056import org.kuali.rice.krad.service.BusinessObjectService; 057import org.kuali.rice.krad.service.DataDictionaryService; 058import org.kuali.rice.krad.service.DocumentDictionaryService; 059import org.kuali.rice.krad.service.DocumentHeaderService; 060import org.kuali.rice.krad.service.DocumentService; 061import org.kuali.rice.krad.service.KRADServiceLocator; 062import org.kuali.rice.krad.service.KRADServiceLocatorInternal; 063import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 064import org.kuali.rice.krad.service.NoteService; 065import org.kuali.rice.krad.util.GlobalVariables; 066import org.kuali.rice.krad.util.KRADConstants; 067import org.kuali.rice.krad.util.NoteType; 068import org.kuali.rice.krad.util.ObjectUtils; 069import org.kuali.rice.krad.workflow.service.WorkflowDocumentService; 070import org.springframework.dao.OptimisticLockingFailureException; 071 072import java.lang.reflect.Constructor; 073import java.lang.reflect.InvocationTargetException; 074import java.text.MessageFormat; 075import java.util.ArrayList; 076import java.util.HashMap; 077import java.util.List; 078import java.util.Map; 079 080 081 082/** 083 * Service implementation for the Document structure. It contains all of the document level type of 084 * processing and calling back into documents for various centralization of functionality. This is the default, 085 * Kuali delivered implementation which utilizes Workflow. 086 * 087 * @author Kuali Rice Team (rice.collab@kuali.org) 088 */ 089@TransactionalNoValidationExceptionRollback 090public class DocumentServiceImpl implements DocumentService { 091 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentServiceImpl.class); 092 093 private DocumentDao documentDao; 094 095 private DateTimeService dateTimeService; 096 private NoteService noteService; 097 private WorkflowDocumentService workflowDocumentService; 098 private BusinessObjectService businessObjectService; 099 private DataDictionaryService dataDictionaryService; 100 private DocumentHeaderService documentHeaderService; 101 private DocumentDictionaryService documentDictionaryService; 102 private PersonService personService; 103 private ConfigurationService kualiConfigurationService; 104 105 /** 106 * @see org.kuali.rice.krad.service.DocumentService#saveDocument(org.kuali.rice.krad.document.Document) 107 */ 108 @Override 109 public Document saveDocument(Document document) throws WorkflowException, ValidationException { 110 return saveDocument(document, SaveDocumentEvent.class); 111 } 112 113 @Override 114 public Document saveDocument(Document document, 115 Class<? extends KualiDocumentEvent> kualiDocumentEventClass) throws WorkflowException, ValidationException { 116 checkForNulls(document); 117 if (kualiDocumentEventClass == null) { 118 throw new IllegalArgumentException("invalid (null) kualiDocumentEventClass"); 119 } 120 // if event is not an instance of a SaveDocumentEvent or a SaveOnlyDocumentEvent 121 if (!SaveEvent.class.isAssignableFrom(kualiDocumentEventClass)) { 122 throw new ConfigurationException("The KualiDocumentEvent class '" + kualiDocumentEventClass.getName() + 123 "' does not implement the class '" + SaveEvent.class.getName() + "'"); 124 } 125// if (!getDocumentActionFlags(document).getCanSave()) { 126// throw buildAuthorizationException("save", document); 127// } 128 document.prepareForSave(); 129 Document savedDocument = validateAndPersistDocumentAndSaveAdHocRoutingRecipients(document, 130 generateKualiDocumentEvent(document, kualiDocumentEventClass)); 131 prepareWorkflowDocument(savedDocument); 132 getWorkflowDocumentService().save(savedDocument.getDocumentHeader().getWorkflowDocument(), null); 133 134 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 135 savedDocument.getDocumentHeader().getWorkflowDocument()); 136 137 return savedDocument; 138 } 139 140 private KualiDocumentEvent generateKualiDocumentEvent(Document document, 141 Class<? extends KualiDocumentEvent> eventClass) throws ConfigurationException { 142 String potentialErrorMessage = 143 "Found error trying to generate Kuali Document Event using event class '" + eventClass.getName() + 144 "' for document " + document.getDocumentNumber(); 145 146 try { 147 Constructor<?> usableConstructor = null; 148 List<Object> paramList = new ArrayList<Object>(); 149 for (Constructor<?> currentConstructor : eventClass.getConstructors()) { 150 for (Class<?> parameterClass : currentConstructor.getParameterTypes()) { 151 if (Document.class.isAssignableFrom(parameterClass)) { 152 usableConstructor = currentConstructor; 153 paramList.add(document); 154 } else { 155 paramList.add(null); 156 } 157 } 158 if (ObjectUtils.isNotNull(usableConstructor)) { 159 break; 160 } 161 } 162 if (usableConstructor == null) { 163 throw new RuntimeException("Cannot find a constructor for class '" + eventClass.getName() + 164 "' that takes in a document parameter"); 165 } 166 return (KualiDocumentEvent) usableConstructor.newInstance(paramList.toArray()); 167 } catch (SecurityException e) { 168 throw new ConfigurationException(potentialErrorMessage, e); 169 } catch (IllegalArgumentException e) { 170 throw new ConfigurationException(potentialErrorMessage, e); 171 } catch (InstantiationException e) { 172 throw new ConfigurationException(potentialErrorMessage, e); 173 } catch (IllegalAccessException e) { 174 throw new ConfigurationException(potentialErrorMessage, e); 175 } catch (InvocationTargetException e) { 176 throw new ConfigurationException(potentialErrorMessage, e); 177 } 178 } 179 180 /** 181 * @see org.kuali.rice.krad.service.DocumentService#routeDocument(org.kuali.rice.krad.document.Document, 182 * java.lang.String, java.util.List) 183 */ 184 @Override 185 public Document routeDocument(Document document, String annotation, 186 List<AdHocRouteRecipient> adHocRecipients) throws ValidationException, WorkflowException { 187 checkForNulls(document); 188 //if (!getDocumentActionFlags(document).getCanRoute()) { 189 // throw buildAuthorizationException("route", document); 190 //} 191 document.prepareForSave(); 192 Document savedDocument = validateAndPersistDocument(document, new RouteDocumentEvent(document)); 193 prepareWorkflowDocument(savedDocument); 194 getWorkflowDocumentService() 195 .route(savedDocument.getDocumentHeader().getWorkflowDocument(), annotation, adHocRecipients); 196 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 197 savedDocument.getDocumentHeader().getWorkflowDocument()); 198 removeAdHocPersonsAndWorkgroups(savedDocument); 199 return savedDocument; 200 } 201 202 /** 203 * @see org.kuali.rice.krad.service.DocumentService#approveDocument(org.kuali.rice.krad.document.Document, 204 * java.lang.String, 205 * java.util.List) 206 */ 207 @Override 208 public Document approveDocument(Document document, String annotation, 209 List<AdHocRouteRecipient> adHocRecipients) throws ValidationException, WorkflowException { 210 checkForNulls(document); 211 //if (!getDocumentActionFlags(document).getCanApprove()) { 212 // throw buildAuthorizationException("approve", document); 213 //} 214 document.prepareForSave(); 215 Document savedDocument = validateAndPersistDocument(document, new ApproveDocumentEvent(document)); 216 prepareWorkflowDocument(savedDocument); 217 getWorkflowDocumentService() 218 .approve(savedDocument.getDocumentHeader().getWorkflowDocument(), annotation, adHocRecipients); 219 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 220 savedDocument.getDocumentHeader().getWorkflowDocument()); 221 removeAdHocPersonsAndWorkgroups(savedDocument); 222 return savedDocument; 223 } 224 225 /** 226 * @see org.kuali.rice.krad.service.DocumentService#superUserApproveDocument(org.kuali.rice.krad.document.Document, 227 * java.lang.String) 228 */ 229 @Override 230 public Document superUserApproveDocument(Document document, String annotation) throws WorkflowException { 231 getDocumentDao().save(document); 232 prepareWorkflowDocument(document); 233 getWorkflowDocumentService().superUserApprove(document.getDocumentHeader().getWorkflowDocument(), annotation); 234 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 235 document.getDocumentHeader().getWorkflowDocument()); 236 removeAdHocPersonsAndWorkgroups(document); 237 return document; 238 } 239 240 /** 241 * @see org.kuali.rice.krad.service.DocumentService#superUserCancelDocument(org.kuali.rice.krad.document.Document, 242 * java.lang.String) 243 */ 244 @Override 245 public Document superUserCancelDocument(Document document, String annotation) throws WorkflowException { 246 getDocumentDao().save(document); 247 prepareWorkflowDocument(document); 248 getWorkflowDocumentService().superUserCancel(document.getDocumentHeader().getWorkflowDocument(), annotation); 249 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 250 document.getDocumentHeader().getWorkflowDocument()); 251 removeAdHocPersonsAndWorkgroups(document); 252 return document; 253 } 254 255 /** 256 * @see org.kuali.rice.krad.service.DocumentService#superUserCancelDocument(org.kuali.rice.krad.document.Document, 257 * java.lang.String) 258 */ 259 @Override 260 public Document superUserDisapproveDocument(Document document, String annotation) throws WorkflowException { 261 getDocumentDao().save(document); 262 return superUserDisapproveDocumentWithoutSaving(document, annotation); 263 } 264 265 /** 266 * @see org.kuali.rice.krad.service.DocumentService#superUserCancelDocument(org.kuali.rice.krad.document.Document, 267 * java.lang.String) 268 */ 269 @Override 270 public Document superUserDisapproveDocumentWithoutSaving(Document document, String annotation) throws WorkflowException { 271 prepareWorkflowDocument(document); 272 getWorkflowDocumentService() 273 .superUserDisapprove(document.getDocumentHeader().getWorkflowDocument(), annotation); 274 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 275 document.getDocumentHeader().getWorkflowDocument()); 276 removeAdHocPersonsAndWorkgroups(document); 277 return document; 278 } 279 280 281 /** 282 * @see org.kuali.rice.krad.service.DocumentService#disapproveDocument(org.kuali.rice.krad.document.Document, 283 * java.lang.String) 284 */ 285 @Override 286 public Document disapproveDocument(Document document, String annotation) throws Exception { 287 checkForNulls(document); 288 289 Note note = createNoteFromDocument(document, annotation); 290 //if note type is BO, override and link disapprove notes to Doc Header 291 if (document.getNoteType().equals(NoteType.BUSINESS_OBJECT)) { 292 note.setNoteTypeCode(NoteType.DOCUMENT_HEADER.getCode()); 293 note.setRemoteObjectIdentifier(document.getDocumentHeader().getObjectId()); 294 } 295 document.addNote(note); 296 297 //SAVE THE NOTE 298 //Note: This save logic is replicated here and in KualiDocumentAction, when to save (based on doc state) should be moved 299 // into a doc service method 300 getNoteService().save(note); 301 302 prepareWorkflowDocument(document); 303 getWorkflowDocumentService().disapprove(document.getDocumentHeader().getWorkflowDocument(), annotation); 304 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 305 document.getDocumentHeader().getWorkflowDocument()); 306 removeAdHocPersonsAndWorkgroups(document); 307 return document; 308 } 309 310 /** 311 * @see org.kuali.rice.krad.service.DocumentService#cancelDocument(org.kuali.rice.krad.document.Document, 312 * java.lang.String) 313 */ 314 @Override 315 public Document cancelDocument(Document document, String annotation) throws WorkflowException { 316 checkForNulls(document); 317 //if (!getDocumentActionFlags(document).getCanCancel()) { 318 // throw buildAuthorizationException("cancel", document); 319 //} 320 if (document instanceof MaintenanceDocument) { 321 MaintenanceDocument maintDoc = ((MaintenanceDocument) document); 322 if (maintDoc.getOldMaintainableObject() != null && 323 (maintDoc.getOldMaintainableObject().getDataObject() instanceof BusinessObject)) { 324 ((BusinessObject) maintDoc.getOldMaintainableObject().getDataObject()).refresh(); 325 } 326 327 if (maintDoc.getNewMaintainableObject().getDataObject() instanceof BusinessObject) { 328 ((BusinessObject) maintDoc.getNewMaintainableObject().getDataObject()).refresh(); 329 } 330 } 331 prepareWorkflowDocument(document); 332 getWorkflowDocumentService().cancel(document.getDocumentHeader().getWorkflowDocument(), annotation); 333 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 334 document.getDocumentHeader().getWorkflowDocument()); 335 //getBusinessObjectService().delete(document.getAdHocRoutePersons()); 336 //getBusinessObjectService().delete(document.getAdHocRouteWorkgroups()); 337 removeAdHocPersonsAndWorkgroups(document); 338 return document; 339 } 340 341 @Override 342 public Document recallDocument(Document document, String annotation, boolean cancel) throws WorkflowException { 343 checkForNulls(document); 344 345 Note note = createNoteFromDocument(document, annotation); 346 document.addNote(note); 347 getNoteService().save(note); 348 349 prepareWorkflowDocument(document); 350 getWorkflowDocumentService().recall(document.getDocumentHeader().getWorkflowDocument(), annotation, cancel); 351 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 352 document.getDocumentHeader().getWorkflowDocument()); 353 removeAdHocPersonsAndWorkgroups(document); 354 return document; 355 } 356 357 /** 358 * @see org.kuali.rice.krad.service.DocumentService#acknowledgeDocument(org.kuali.rice.krad.document.Document, 359 * java.lang.String, 360 * java.util.List) 361 */ 362 @Override 363 public Document acknowledgeDocument(Document document, String annotation, 364 List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException { 365 checkForNulls(document); 366 //if (!getDocumentActionFlags(document).getCanAcknowledge()) { 367 // throw buildAuthorizationException("acknowledge", document); 368 //} 369 prepareWorkflowDocument(document); 370 getWorkflowDocumentService() 371 .acknowledge(document.getDocumentHeader().getWorkflowDocument(), annotation, adHocRecipients); 372 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 373 document.getDocumentHeader().getWorkflowDocument()); 374 removeAdHocPersonsAndWorkgroups(document); 375 return document; 376 } 377 378 /** 379 * @see org.kuali.rice.krad.service.DocumentService#blanketApproveDocument(org.kuali.rice.krad.document.Document, 380 * java.lang.String, 381 * java.util.List) 382 */ 383 @Override 384 public Document blanketApproveDocument(Document document, String annotation, 385 List<AdHocRouteRecipient> adHocRecipients) throws ValidationException, WorkflowException { 386 checkForNulls(document); 387 //if (!getDocumentActionFlags(document).getCanBlanketApprove()) { 388 // throw buildAuthorizationException("blanket approve", document); 389 //} 390 document.prepareForSave(); 391 Document savedDocument = validateAndPersistDocument(document, new BlanketApproveDocumentEvent(document)); 392 prepareWorkflowDocument(savedDocument); 393 getWorkflowDocumentService() 394 .blanketApprove(savedDocument.getDocumentHeader().getWorkflowDocument(), annotation, adHocRecipients); 395 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 396 savedDocument.getDocumentHeader().getWorkflowDocument()); 397 removeAdHocPersonsAndWorkgroups(savedDocument); 398 return savedDocument; 399 } 400 401 /** 402 * @see org.kuali.rice.krad.service.DocumentService#clearDocumentFyi(org.kuali.rice.krad.document.Document, 403 * java.util.List) 404 */ 405 @Override 406 public Document clearDocumentFyi(Document document, 407 List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException { 408 checkForNulls(document); 409 // populate document content so searchable attributes will be indexed properly 410 document.populateDocumentForRouting(); 411 getWorkflowDocumentService().clearFyi(document.getDocumentHeader().getWorkflowDocument(), adHocRecipients); 412 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 413 document.getDocumentHeader().getWorkflowDocument()); 414 removeAdHocPersonsAndWorkgroups(document); 415 return document; 416 } 417 418 /** 419 * @see org.kuali.rice.krad.service.DocumentService#completeDocument(org.kuali.rice.krad.document.Document, 420 * java.lang.String, 421 * java.util.List) 422 */ 423 @Override 424 public Document completeDocument(Document document, String annotation, 425 List adHocRecipients) throws WorkflowException { 426 checkForNulls(document); 427 428 document.prepareForSave(); 429 validateAndPersistDocument(document, new CompleteDocumentEvent(document)); 430 431 prepareWorkflowDocument(document); 432 getWorkflowDocumentService().complete(document.getDocumentHeader().getWorkflowDocument(), annotation, 433 adHocRecipients); 434 435 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 436 document.getDocumentHeader().getWorkflowDocument()); 437 438 removeAdHocPersonsAndWorkgroups(document); 439 440 return document; 441 } 442 443 protected void checkForNulls(Document document) { 444 if (document == null) { 445 throw new IllegalArgumentException("invalid (null) document"); 446 } 447 if (document.getDocumentNumber() == null) { 448 throw new IllegalStateException("invalid (null) documentHeaderId"); 449 } 450 } 451 452 private Document validateAndPersistDocumentAndSaveAdHocRoutingRecipients(Document document, 453 KualiDocumentEvent event) { 454 /* 455 * Using this method to wrap validateAndPersistDocument to keep everything in one transaction. This avoids modifying the 456 * signature on validateAndPersistDocument method 457 */ 458 List<AdHocRouteRecipient> adHocRoutingRecipients = new ArrayList<AdHocRouteRecipient>(); 459 adHocRoutingRecipients.addAll(document.getAdHocRoutePersons()); 460 adHocRoutingRecipients.addAll(document.getAdHocRouteWorkgroups()); 461 462 for (AdHocRouteRecipient recipient : adHocRoutingRecipients) { 463 recipient.setdocumentNumber(document.getDocumentNumber()); 464 } 465 Map<String, String> criteria = new HashMap<String, String>(); 466 criteria.put("documentNumber", document.getDocumentNumber()); 467 getBusinessObjectService().deleteMatching(AdHocRouteRecipient.class, criteria); 468 469 getBusinessObjectService().save(adHocRoutingRecipients); 470 return validateAndPersistDocument(document, event); 471 } 472 473 /** 474 * @see org.kuali.rice.krad.service.DocumentService#documentExists(java.lang.String) 475 */ 476 @Override 477 public boolean documentExists(String documentHeaderId) { 478 // validate parameters 479 if (StringUtils.isBlank(documentHeaderId)) { 480 throw new IllegalArgumentException("invalid (blank) documentHeaderId"); 481 } 482 483 boolean internalUserSession = false; 484 try { 485 // KFSMI-2543 - allowed method to run without a user session so it can be used 486 // by workflow processes 487 if (GlobalVariables.getUserSession() == null) { 488 internalUserSession = true; 489 GlobalVariables.setUserSession(new UserSession(KRADConstants.SYSTEM_USER)); 490 GlobalVariables.clear(); 491 } 492 493 // look for workflowDocumentHeader, since that supposedly won't break the transaction 494 if (getWorkflowDocumentService().workflowDocumentExists(documentHeaderId)) { 495 // look for docHeaderId, since that fails without breaking the transaction 496 return getDocumentHeaderService().getDocumentHeaderById(documentHeaderId) != null; 497 } 498 499 return false; 500 } finally { 501 // if a user session was established for this call, clear it our 502 if (internalUserSession) { 503 GlobalVariables.clear(); 504 GlobalVariables.setUserSession(null); 505 } 506 } 507 } 508 509 /** 510 * Creates a new document by class. 511 * 512 * @see org.kuali.rice.krad.service.DocumentService#getNewDocument(java.lang.Class) 513 */ 514 @Override 515 public Document getNewDocument(Class<? extends Document> documentClass) throws WorkflowException { 516 if (documentClass == null) { 517 throw new IllegalArgumentException("invalid (null) documentClass"); 518 } 519 if (!Document.class.isAssignableFrom(documentClass)) { 520 throw new IllegalArgumentException("invalid (non-Document) documentClass"); 521 } 522 523 String documentTypeName = getDataDictionaryService().getDocumentTypeNameByClass(documentClass); 524 if (StringUtils.isBlank(documentTypeName)) { 525 throw new UnknownDocumentTypeException( 526 "unable to get documentTypeName for unknown documentClass '" + documentClass.getName() + "'"); 527 } 528 return getNewDocument(documentTypeName); 529 } 530 531 /** 532 * Creates a new document by document type name. The principal name 533 * passed in will be used as the document initiator. If the initiatorPrincipalNm 534 * is null or blank, the current user will be used. 535 * 536 * @see org.kuali.rice.krad.service.DocumentService#getNewDocument(String, String) 537 */ 538 @Override 539 public Document getNewDocument(String documentTypeName, String initiatorPrincipalNm) throws WorkflowException { 540 541 // argument validation 542 String watchName = "DocumentServiceImpl.getNewDocument"; 543 StopWatch watch = new StopWatch(); 544 watch.start(); 545 if (LOG.isDebugEnabled()) { 546 LOG.debug(watchName + ": started"); 547 } 548 if (StringUtils.isBlank(documentTypeName)) { 549 throw new IllegalArgumentException("invalid (blank) documentTypeName"); 550 } 551 if (GlobalVariables.getUserSession() == null) { 552 throw new IllegalStateException( 553 "GlobalVariables must be populated with a valid UserSession before a new document can be created"); 554 } 555 556 // get the class for this docTypeName 557 Class<? extends Document> documentClass = getDocumentClassByTypeName(documentTypeName); 558 559 // get the initiator 560 Person initiator = null; 561 if (StringUtils.isBlank(initiatorPrincipalNm)) { 562 initiator = GlobalVariables.getUserSession().getPerson(); 563 } else { 564 initiator = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(initiatorPrincipalNm); 565 if (ObjectUtils.isNull(initiator)) { 566 initiator = GlobalVariables.getUserSession().getPerson(); 567 } 568 } 569 570 // get the authorization 571 DocumentAuthorizer documentAuthorizer = getDocumentDictionaryService().getDocumentAuthorizer(documentTypeName); 572 DocumentPresentationController documentPresentationController = 573 getDocumentDictionaryService().getDocumentPresentationController(documentTypeName); 574 // make sure this person is authorized to initiate 575 LOG.debug("calling canInitiate from getNewDocument()"); 576 if (!documentPresentationController.canInitiate(documentTypeName) || 577 !documentAuthorizer.canInitiate(documentTypeName, initiator)) { 578 throw new DocumentAuthorizationException(initiator.getPrincipalName(), "initiate", documentTypeName); 579 } 580 581 // initiate new workflow entry, get the workflow doc 582 WorkflowDocument workflowDocument = getWorkflowDocumentService().createWorkflowDocument(documentTypeName, initiator); 583 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDocument); 584 585 // create a new document header object 586 DocumentHeader documentHeader = null; 587 try { 588 // create a new document header object 589 Class<? extends DocumentHeader> documentHeaderClass = 590 getDocumentHeaderService().getDocumentHeaderBaseClass(); 591 documentHeader = documentHeaderClass.newInstance(); 592 documentHeader.setWorkflowDocument(workflowDocument); 593 documentHeader.setDocumentNumber(workflowDocument.getDocumentId()); 594 // status and notes are initialized correctly in the constructor 595 } catch (IllegalAccessException e) { 596 throw new RuntimeException("Error instantiating DocumentHeader", e); 597 } catch (InstantiationException e) { 598 throw new RuntimeException("Error instantiating DocumentHeader", e); 599 } 600 601 // build Document of specified type 602 Document document = null; 603 try { 604 // all maintenance documents have same class 605 if (MaintenanceDocumentBase.class.isAssignableFrom(documentClass)) { 606 Class<?>[] defaultConstructor = new Class[]{String.class}; 607 Constructor<? extends Document> cons = documentClass.getConstructor(defaultConstructor); 608 if (ObjectUtils.isNull(cons)) { 609 throw new ConfigurationException( 610 "Could not find constructor with document type name parameter needed for Maintenance Document Base class"); 611 } 612 document = cons.newInstance(documentTypeName); 613 } else { 614 // non-maintenance document 615 document = documentClass.newInstance(); 616 } 617 } catch (IllegalAccessException e) { 618 throw new RuntimeException("Error instantiating Document", e); 619 } catch (InstantiationException e) { 620 throw new RuntimeException("Error instantiating Document", e); 621 } catch (SecurityException e) { 622 throw new RuntimeException("Error instantiating Maintenance Document", e); 623 } catch (NoSuchMethodException e) { 624 throw new RuntimeException( 625 "Error instantiating Maintenance Document: No constructor with String parameter found", e); 626 } catch (IllegalArgumentException e) { 627 throw new RuntimeException("Error instantiating Maintenance Document", e); 628 } catch (InvocationTargetException e) { 629 throw new RuntimeException("Error instantiating Maintenance Document", e); 630 } 631 632 document.setDocumentHeader(documentHeader); 633 document.setDocumentNumber(documentHeader.getDocumentNumber()); 634 635 watch.stop(); 636 if (LOG.isDebugEnabled()) { 637 LOG.debug(watchName + ": " + watch.toString()); 638 } 639 640 return document; 641 } 642 643 /** 644 * Creates a new document by document type name. 645 * 646 * @see org.kuali.rice.krad.service.DocumentService#getNewDocument(java.lang.String) 647 */ 648 @Override 649 public Document getNewDocument(String documentTypeName) throws WorkflowException { 650 return getNewDocument(documentTypeName, null); 651 } 652 653 654 /** 655 * This is temporary until workflow 2.0 and reads from a table to get documents whose status has changed to A 656 * (approved - no 657 * outstanding approval actions requested) 658 * 659 * @param documentHeaderId 660 * @return Document 661 * @throws WorkflowException 662 */ 663 @Override 664 public Document getByDocumentHeaderId(String documentHeaderId) throws WorkflowException { 665 if (documentHeaderId == null) { 666 throw new IllegalArgumentException("invalid (null) documentHeaderId"); 667 } 668 boolean internalUserSession = false; 669 try { 670 // KFSMI-2543 - allowed method to run without a user session so it can be used 671 // by workflow processes 672 if (GlobalVariables.getUserSession() == null) { 673 internalUserSession = true; 674 GlobalVariables.setUserSession(new UserSession(KRADConstants.SYSTEM_USER)); 675 GlobalVariables.clear(); 676 } 677 678 WorkflowDocument workflowDocument = null; 679 680 if (LOG.isDebugEnabled()) { 681 LOG.debug("Retrieving doc id: " + documentHeaderId + " from workflow service."); 682 } 683 workflowDocument = getWorkflowDocumentService() 684 .loadWorkflowDocument(documentHeaderId, GlobalVariables.getUserSession().getPerson()); 685 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDocument); 686 687 Class<? extends Document> documentClass = getDocumentClassByTypeName(workflowDocument.getDocumentTypeName()); 688 689 // retrieve the Document 690 Document document = getDocumentDao().findByDocumentHeaderId(documentClass, documentHeaderId); 691 692 return postProcessDocument(documentHeaderId, workflowDocument, document); 693 } finally { 694 // if a user session was established for this call, clear it out 695 if (internalUserSession) { 696 GlobalVariables.clear(); 697 GlobalVariables.setUserSession(null); 698 } 699 } 700 } 701 702 /** 703 * @see org.kuali.rice.krad.service.DocumentService#getByDocumentHeaderIdSessionless(java.lang.String) 704 */ 705 @Override 706 public Document getByDocumentHeaderIdSessionless(String documentHeaderId) throws WorkflowException { 707 if (documentHeaderId == null) { 708 throw new IllegalArgumentException("invalid (null) documentHeaderId"); 709 } 710 711 WorkflowDocument workflowDocument = null; 712 713 if (LOG.isDebugEnabled()) { 714 LOG.debug("Retrieving doc id: " + documentHeaderId + " from workflow service."); 715 } 716 717 Person person = getPersonService().getPersonByPrincipalName(KRADConstants.SYSTEM_USER); 718 workflowDocument = workflowDocumentService.loadWorkflowDocument(documentHeaderId, person); 719 720 Class<? extends Document> documentClass = getDocumentClassByTypeName(workflowDocument.getDocumentTypeName()); 721 722 // retrieve the Document 723 Document document = getDocumentDao().findByDocumentHeaderId(documentClass, documentHeaderId); 724 725 return postProcessDocument(documentHeaderId, workflowDocument, document); 726 } 727 728 private Class<? extends Document> getDocumentClassByTypeName(String documentTypeName) { 729 if (StringUtils.isBlank(documentTypeName)) { 730 throw new IllegalArgumentException("invalid (blank) documentTypeName"); 731 } 732 733 Class<? extends Document> clazz = getDataDictionaryService().getDocumentClassByTypeName(documentTypeName); 734 if (clazz == null) { 735 throw new UnknownDocumentTypeException( 736 "unable to get class for unknown documentTypeName '" + documentTypeName + "'"); 737 } 738 return clazz; 739 } 740 741 /** 742 * Loads the Notes for the note target on this Document. 743 * 744 * @param document the document for which to load the notes 745 */ 746 protected void loadNotes(Document document) { 747 if (isNoteTargetReady(document)) { 748 List<Note> notes = new ArrayList<Note>(); 749 if (StringUtils.isNotBlank(document.getNoteTarget().getObjectId())) { 750 notes.addAll(getNoteService().getByRemoteObjectId(document.getNoteTarget().getObjectId())); 751 } 752 //notes created on 'disapprove' are linked to Doc Header, so this checks that even if notetype = BO 753 if (document.getNoteType().equals(NoteType.BUSINESS_OBJECT) 754 && document.getDocumentHeader().getWorkflowDocument().isDisapproved()) { 755 notes.addAll(getNoteService().getByRemoteObjectId(document.getDocumentHeader().getObjectId())); 756 } 757 758 // KULRNE-5692 - force a refresh of the attachments 759 // they are not (non-updateable) references and don't seem to update properly upon load 760 for (Note note : notes) { 761 note.refreshReferenceObject("attachment"); 762 } 763 document.setNotes(notes); 764 } 765 } 766 767 /** 768 * Performs required post-processing for every document from the documentDao 769 * 770 * @param documentHeaderId 771 * @param workflowDocument 772 * @param document 773 */ 774 private Document postProcessDocument(String documentHeaderId, WorkflowDocument workflowDocument, Document document) { 775 if (document != null) { 776 document.getDocumentHeader().setWorkflowDocument(workflowDocument); 777 document.processAfterRetrieve(); 778 loadNotes(document); 779 } 780 return document; 781 } 782 783 /** 784 * The default implementation - this retrieves all documents by a list of documentHeader for a given class. 785 * 786 * @see org.kuali.rice.krad.service.DocumentService#getDocumentsByListOfDocumentHeaderIds(java.lang.Class, 787 * java.util.List) 788 */ 789 @Override 790 public List<Document> getDocumentsByListOfDocumentHeaderIds(Class<? extends Document> documentClass, 791 List<String> documentHeaderIds) throws WorkflowException { 792 // validate documentHeaderIdList and contents 793 if (documentHeaderIds == null) { 794 throw new IllegalArgumentException("invalid (null) documentHeaderId list"); 795 } 796 int index = 0; 797 for (String documentHeaderId : documentHeaderIds) { 798 if (StringUtils.isBlank(documentHeaderId)) { 799 throw new IllegalArgumentException("invalid (blank) documentHeaderId at list index " + index); 800 } 801 index++; 802 } 803 804 boolean internalUserSession = false; 805 try { 806 // KFSMI-2543 - allowed method to run without a user session so it can be used 807 // by workflow processes 808 if (GlobalVariables.getUserSession() == null) { 809 internalUserSession = true; 810 GlobalVariables.setUserSession(new UserSession(KRADConstants.SYSTEM_USER)); 811 GlobalVariables.clear(); 812 } 813 814 // retrieve all documents that match the document header ids 815 List<? extends Document> rawDocuments = 816 getDocumentDao().findByDocumentHeaderIds(documentClass, documentHeaderIds); 817 818 // post-process them 819 List<Document> documents = new ArrayList<Document>(); 820 for (Document document : rawDocuments) { 821 WorkflowDocument workflowDocument = getWorkflowDocumentService().loadWorkflowDocument(document.getDocumentNumber(), GlobalVariables.getUserSession().getPerson()); 822 823 document = postProcessDocument(document.getDocumentNumber(), workflowDocument, document); 824 documents.add(document); 825 } 826 return documents; 827 } finally { 828 // if a user session was established for this call, clear it our 829 if (internalUserSession) { 830 GlobalVariables.clear(); 831 GlobalVariables.setUserSession(null); 832 } 833 } 834 } 835 836 /* Helper Methods */ 837 838 /** 839 * Validates and persists a document. 840 */ 841 @Override 842 public Document validateAndPersistDocument(Document document, KualiDocumentEvent event) throws ValidationException { 843 if (document == null) { 844 LOG.error("document passed to validateAndPersist was null"); 845 throw new IllegalArgumentException("invalid (null) document"); 846 } 847 if (LOG.isDebugEnabled()) { 848 LOG.debug("validating and preparing to persist document " + document.getDocumentNumber()); 849 } 850 851 document.validateBusinessRules(event); 852 document.prepareForSave(event); 853 854 // save the document 855 Document savedDocument = null; 856 try { 857 if (LOG.isInfoEnabled()) { 858 LOG.info("storing document " + document.getDocumentNumber()); 859 } 860 savedDocument = getDocumentDao().save(document); 861 } catch (OptimisticLockingFailureException e) { 862 LOG.error("exception encountered on store of document " + e.getMessage()); 863 throw e; 864 } 865 866 boolean notesSaved = saveDocumentNotes(document); 867 if (!notesSaved) { 868 if (LOG.isInfoEnabled()) { 869 LOG.info( 870 "Notes not saved during validateAndPersistDocument, likely means that note save needs to be deferred because note target is not ready."); 871 } 872 } 873 874 savedDocument.postProcessSave(event); 875 876 return savedDocument; 877 } 878 879 /** 880 * Sets the title and app document id in the flex document 881 * 882 * @param document 883 * @throws org.kuali.rice.kew.api.exception.WorkflowException 884 */ 885 @Override 886 public void prepareWorkflowDocument(Document document) throws WorkflowException { 887 // populate document content so searchable attributes will be indexed properly 888 document.populateDocumentForRouting(); 889 890 // make sure we push the document title into the workflowDocument 891 populateDocumentTitle(document); 892 893 // make sure we push the application document id into the workflowDocument 894 populateApplicationDocumentId(document); 895 } 896 897 /** 898 * This method will grab the generated document title from the document and add it to the workflowDocument so that 899 * it gets pushed into 900 * workflow when routed. 901 * 902 * @param document 903 * @throws org.kuali.rice.kew.api.exception.WorkflowException 904 */ 905 private void populateDocumentTitle(Document document) throws WorkflowException { 906 String documentTitle = document.getDocumentTitle(); 907 if (StringUtils.isNotBlank(documentTitle)) { 908 document.getDocumentHeader().getWorkflowDocument().setTitle(documentTitle); 909 } 910 } 911 912 /** 913 * This method will grab the organization document number from the document and add it to the workflowDocument so 914 * that it gets pushed 915 * into workflow when routed. 916 * 917 * @param document 918 */ 919 private void populateApplicationDocumentId(Document document) { 920 String organizationDocumentNumber = document.getDocumentHeader().getOrganizationDocumentNumber(); 921 if (StringUtils.isNotBlank(organizationDocumentNumber)) { 922 document.getDocumentHeader().getWorkflowDocument().setApplicationDocumentId(organizationDocumentNumber); 923 } 924 } 925 926 /** 927 * This is to allow for updates of document statuses and other related requirements for updates outside of the 928 * initial save and 929 * route 930 */ 931 @Override 932 public Document updateDocument(Document document) { 933 checkForNulls(document); 934 return getDocumentDao().save(document); 935 } 936 937 /** 938 * @see org.kuali.rice.krad.service.DocumentService#createNoteFromDocument(org.kuali.rice.krad.document.Document, 939 * java.lang.String) 940 */ 941 @Override 942 public Note createNoteFromDocument(Document document, String text) { 943 Note note = new Note(); 944 945 note.setNotePostedTimestamp(getDateTimeService().getCurrentTimestamp()); 946 note.setVersionNumber(Long.valueOf(1)); 947 note.setNoteText(text); 948 note.setNoteTypeCode(document.getNoteType().getCode()); 949 950 PersistableBusinessObject bo = document.getNoteTarget(); 951 // TODO gah! this is awful 952 Person kualiUser = GlobalVariables.getUserSession().getPerson(); 953 if (kualiUser == null) { 954 throw new IllegalStateException("Current UserSession has a null Person."); 955 } 956 return bo == null ? null : getNoteService().createNote(note, bo, kualiUser.getPrincipalId()); 957 } 958 959 /** 960 * @see org.kuali.rice.krad.service.DocumentService#saveDocumentNotes(org.kuali.rice.krad.document.Document) 961 */ 962 @Override 963 public boolean saveDocumentNotes(Document document) { 964 if (isNoteTargetReady(document)) { 965 List<Note> notes = document.getNotes(); 966 for (Note note : document.getNotes()) { 967 linkNoteRemoteObjectId(note, document.getNoteTarget()); 968 } 969 getNoteService().saveNoteList(notes); 970 return true; 971 } 972 return false; 973 } 974 975 /** 976 * @see org.kuali.rice.krad.service.DocumentService 977 */ 978 @Override 979 public void sendNoteRouteNotification(Document document, Note note, Person sender) throws WorkflowException { 980 AdHocRouteRecipient routeRecipient = note.getAdHocRouteRecipient(); 981 982 // build notification request 983 Person requestedUser = this.getPersonService().getPersonByPrincipalName(routeRecipient.getId()); 984 String senderName = sender.getFirstName() + " " + sender.getLastName(); 985 String requestedName = requestedUser.getFirstName() + " " + requestedUser.getLastName(); 986 987 String notificationText = 988 kualiConfigurationService.getPropertyValueAsString( 989 RiceKeyConstants.MESSAGE_NOTE_NOTIFICATION_ANNOTATION); 990 if (StringUtils.isBlank(notificationText)) { 991 throw new RuntimeException( 992 "No annotation message found for note notification. Message needs added to application resources with key:" + 993 RiceKeyConstants.MESSAGE_NOTE_NOTIFICATION_ANNOTATION); 994 } 995 notificationText = 996 MessageFormat.format(notificationText, new Object[]{senderName, requestedName, note.getNoteText()}); 997 998 List<AdHocRouteRecipient> routeRecipients = new ArrayList<AdHocRouteRecipient>(); 999 routeRecipients.add(routeRecipient); 1000 1001 workflowDocumentService 1002 .sendWorkflowNotification(document.getDocumentHeader().getWorkflowDocument(), notificationText, 1003 routeRecipients, KRADConstants.NOTE_WORKFLOW_NOTIFICATION_REQUEST_LABEL); 1004 1005 // clear recipient allowing an notification to be sent to another person 1006 note.setAdHocRouteRecipient(new AdHocRoutePerson()); 1007 } 1008 1009 /** 1010 * Determines if the given document's note target is ready for notes to be 1011 * attached and persisted against it. This method verifies that the document's 1012 * note target is non-null as well as checking that it has a non-empty object id. 1013 * 1014 * @param document the document on which to check for note target readiness 1015 * @return true if the note target is ready, false otherwise 1016 */ 1017 protected boolean isNoteTargetReady(Document document) { 1018 1019 //special case for disappoved documents 1020 if (document.getDocumentHeader().getWorkflowDocument().isDisapproved()) { 1021 return true; 1022 } 1023 PersistableBusinessObject noteTarget = document.getNoteTarget(); 1024 if (noteTarget == null || StringUtils.isBlank(noteTarget.getObjectId())) { 1025 return false; 1026 } 1027 return true; 1028 } 1029 1030 private void linkNoteRemoteObjectId(Note note, PersistableBusinessObject noteTarget) { 1031 String objectId = noteTarget.getObjectId(); 1032 if (StringUtils.isBlank(objectId)) { 1033 throw new IllegalStateException( 1034 "Attempted to link a Note with a PersistableBusinessObject with no object id"); 1035 } 1036 note.setRemoteObjectIdentifier(noteTarget.getObjectId()); 1037 } 1038 1039 /** 1040 * @see org.kuali.rice.krad.service.DocumentService#sendAdHocRequests(org.kuali.rice.krad.document.Document, String, java.util.List) 1041 */ 1042 @Override 1043 public void sendAdHocRequests(Document document, String annotation, 1044 List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException { 1045 prepareWorkflowDocument(document); 1046 getWorkflowDocumentService() 1047 .sendWorkflowNotification(document.getDocumentHeader().getWorkflowDocument(), annotation, 1048 adHocRecipients); 1049 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), 1050 document.getDocumentHeader().getWorkflowDocument()); 1051 //getBusinessObjectService().delete(document.getAdHocRoutePersons()); 1052 //getBusinessObjectService().delete(document.getAdHocRouteWorkgroups()); 1053 removeAdHocPersonsAndWorkgroups(document); 1054 } 1055 1056 private void removeAdHocPersonsAndWorkgroups(Document document) { 1057 List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>(); 1058 List<AdHocRouteWorkgroup> adHocRouteWorkgroups = new ArrayList<AdHocRouteWorkgroup>(); 1059 getBusinessObjectService().delete(document.getAdHocRoutePersons()); 1060 getBusinessObjectService().delete(document.getAdHocRouteWorkgroups()); 1061 document.setAdHocRoutePersons(adHocRoutePersons); 1062 document.setAdHocRouteWorkgroups(adHocRouteWorkgroups); 1063 } 1064 1065 public void setDateTimeService(DateTimeService dateTimeService) { 1066 this.dateTimeService = dateTimeService; 1067 } 1068 1069 protected DateTimeService getDateTimeService() { 1070 if (this.dateTimeService == null) { 1071 this.dateTimeService = CoreApiServiceLocator.getDateTimeService(); 1072 } 1073 return this.dateTimeService; 1074 } 1075 1076 public void setNoteService(NoteService noteService) { 1077 this.noteService = noteService; 1078 } 1079 1080 protected NoteService getNoteService() { 1081 if (this.noteService == null) { 1082 this.noteService = KRADServiceLocator.getNoteService(); 1083 } 1084 return this.noteService; 1085 } 1086 1087 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 1088 this.businessObjectService = businessObjectService; 1089 } 1090 1091 protected BusinessObjectService getBusinessObjectService() { 1092 if (this.businessObjectService == null) { 1093 this.businessObjectService = KRADServiceLocator.getBusinessObjectService(); 1094 } 1095 return this.businessObjectService; 1096 } 1097 1098 public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) { 1099 this.workflowDocumentService = workflowDocumentService; 1100 } 1101 1102 protected WorkflowDocumentService getWorkflowDocumentService() { 1103 if (this.workflowDocumentService == null) { 1104 this.workflowDocumentService = KRADServiceLocatorWeb.getWorkflowDocumentService(); 1105 } 1106 return this.workflowDocumentService; 1107 } 1108 1109 public void setDocumentDao(DocumentDao documentDao) { 1110 this.documentDao = documentDao; 1111 } 1112 1113 protected DocumentDao getDocumentDao() { 1114 if (this.documentDao == null) { 1115 this.documentDao = KRADServiceLocatorInternal.getDocumentDao(); 1116 } 1117 return documentDao; 1118 } 1119 1120 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 1121 this.dataDictionaryService = dataDictionaryService; 1122 } 1123 1124 protected DataDictionaryService getDataDictionaryService() { 1125 if (this.dataDictionaryService == null) { 1126 this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 1127 } 1128 return this.dataDictionaryService; 1129 } 1130 1131 public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) { 1132 this.documentHeaderService = documentHeaderService; 1133 } 1134 1135 protected DocumentHeaderService getDocumentHeaderService() { 1136 if (this.documentHeaderService == null) { 1137 this.documentHeaderService = KRADServiceLocatorWeb.getDocumentHeaderService(); 1138 } 1139 return this.documentHeaderService; 1140 } 1141 1142 protected DocumentDictionaryService getDocumentDictionaryService() { 1143 if (documentDictionaryService == null) { 1144 documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 1145 } 1146 return documentDictionaryService; 1147 } 1148 1149 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 1150 this.documentDictionaryService = documentDictionaryService; 1151 } 1152 1153 public PersonService getPersonService() { 1154 if (personService == null) { 1155 personService = KimApiServiceLocator.getPersonService(); 1156 } 1157 return personService; 1158 } 1159 1160 public void setKualiConfigurationService(ConfigurationService kualiConfigurationService) { 1161 this.kualiConfigurationService = kualiConfigurationService; 1162 } 1163 1164}