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