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