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