View Javadoc

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