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