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