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