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