View Javadoc

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