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