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 }