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