001/**
002 * Copyright 2005-2015 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.document;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.kew.api.KewApiConstants;
021import org.kuali.rice.kew.api.KewApiServiceLocator;
022import org.kuali.rice.kew.api.action.ActionType;
023import org.kuali.rice.kew.api.exception.WorkflowException;
024import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
025import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
026import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
027import org.kuali.rice.kim.api.identity.Person;
028import org.kuali.rice.kim.api.services.KimApiServiceLocator;
029import org.kuali.rice.krad.bo.AdHocRoutePerson;
030import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
031import org.kuali.rice.krad.bo.DocumentHeader;
032import org.kuali.rice.krad.bo.Note;
033import org.kuali.rice.krad.bo.PersistableBusinessObject;
034import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
035import org.kuali.rice.krad.datadictionary.DocumentEntry;
036import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
037import org.kuali.rice.krad.datadictionary.WorkflowProperties;
038import org.kuali.rice.krad.document.authorization.PessimisticLock;
039import org.kuali.rice.krad.exception.PessimisticLockingException;
040import org.kuali.rice.krad.exception.ValidationException;
041import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
042import org.kuali.rice.krad.service.AttachmentService;
043import org.kuali.rice.krad.service.DocumentSerializerService;
044import org.kuali.rice.krad.service.KRADServiceLocator;
045import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
046import org.kuali.rice.krad.service.NoteService;
047import org.kuali.rice.krad.util.ErrorMessage;
048import org.kuali.rice.krad.util.GlobalVariables;
049import org.kuali.rice.krad.util.KRADConstants;
050import org.kuali.rice.krad.util.KRADPropertyConstants;
051import org.kuali.rice.krad.util.NoteType;
052import org.kuali.rice.krad.util.ObjectUtils;
053import org.kuali.rice.krad.util.documentserializer.AlwaysFalsePropertySerializabilityEvaluator;
054import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
055import org.kuali.rice.krad.util.documentserializer.BusinessObjectPropertySerializibilityEvaluator;
056import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
057import org.kuali.rice.krad.workflow.DocumentInitiator;
058import org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer;
059import org.kuali.rice.krad.workflow.KualiTransactionalDocumentInformation;
060import org.springframework.util.AutoPopulatingList;
061import org.springframework.util.CollectionUtils;
062
063import javax.persistence.CascadeType;
064import javax.persistence.Column;
065import javax.persistence.FetchType;
066import javax.persistence.Id;
067import javax.persistence.JoinColumn;
068import javax.persistence.MappedSuperclass;
069import javax.persistence.OneToMany;
070import javax.persistence.OneToOne;
071import javax.persistence.Transient;
072import java.util.ArrayList;
073import java.util.Iterator;
074import java.util.List;
075import java.util.Map;
076
077/**
078 * @see Document
079 */
080@MappedSuperclass
081public abstract class DocumentBase extends PersistableBusinessObjectBase implements Document {
082    private static final Logger LOG = Logger.getLogger(DocumentBase.class);
083
084    @Id @Column(name = "DOC_HDR_ID")
085    protected String documentNumber;
086    @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(
087            name = "DOC_HDR_ID", insertable = false, updatable = false)
088    protected DocumentHeader documentHeader;
089
090    @OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(
091            name = "DOC_HDR_ID", insertable = false, updatable = false)
092    private List<PessimisticLock> pessimisticLocks;
093
094    @Transient
095    private List<AdHocRoutePerson> adHocRoutePersons;
096    @Transient
097    private List<AdHocRouteWorkgroup> adHocRouteWorkgroups;
098    @Transient
099    private List<Note> notes;
100
101    private transient NoteService noteService;
102    private transient AttachmentService attachmentService;
103
104    /**
105     * Constructs a DocumentBase.java.
106     */
107    public DocumentBase() {
108        try {
109            // create a new document header object
110            Class<? extends DocumentHeader> documentHeaderClass =
111                    KRADServiceLocatorWeb.getDocumentHeaderService().getDocumentHeaderBaseClass();
112            setDocumentHeader(documentHeaderClass.newInstance());
113            pessimisticLocks = new ArrayList<PessimisticLock>();
114            adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
115            adHocRouteWorkgroups = new ArrayList<AdHocRouteWorkgroup>();
116            notes = new ArrayList<Note>();
117        } catch (IllegalAccessException e) {
118            throw new RuntimeException("Error instantiating DocumentHeader", e);
119        } catch (InstantiationException e) {
120            throw new RuntimeException("Error instantiating DocumentHeader", e);
121        }
122    }
123
124    /**
125     * @see org.kuali.rice.krad.document.Document#getAllowsCopy()
126     */
127    public boolean getAllowsCopy() {
128        // TODO Auto-generated method stub
129        return false;
130    }
131
132    /**
133     * Retrieves the title of the document
134     *
135     * <p>
136     * This is the default document title implementation. It concatenates the document's data dictionary file label
137     * attribute and
138     * the document's document header description together. This title is used to populate workflow and will show up in
139     * document
140     * search results and user action lists.
141     * </p>
142     *
143     * return String representing the title of the document
144     *
145     * @see org.kuali.rice.krad.document.Document#getDocumentTitle()
146     */
147    public String getDocumentTitle() {
148        String documentTypeLabel = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(
149                this.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()).getLabel();
150        if (null == documentTypeLabel) {
151            documentTypeLabel = "";
152        }
153
154        String description = this.getDocumentHeader().getDocumentDescription();
155        if (null == description) {
156            description = "";
157        }
158
159        return documentTypeLabel + " - " + description;
160    }
161
162    /**
163     * Uses the persistence service's implementation of OJB's retrieveNonKey() fields method.
164     *
165     * @see org.kuali.rice.krad.bo.BusinessObject#refresh()
166     */
167    @Override
168    public void refresh() {
169        KRADServiceLocator.getPersistenceService().retrieveNonKeyFields(this);
170    }
171
172    /**
173     * Checks to see if the objectId value is empty. If so, it will try to refresh the object from the DB.
174     *
175     * @see org.kuali.rice.krad.document.Document#refreshIfEmpty()
176     */
177    public void refreshIfEmpty() {
178        if (null == this.getDocumentHeader()) {
179            this.refresh();
180        } else if (StringUtils.isEmpty(this.getDocumentHeader().getObjectId())) {
181            this.refresh();
182        }
183    }
184
185    /**
186     * Uses the persistence service to retrieve a reference object of a parent.
187     *
188     * @see org.kuali.rice.krad.document.Document#refreshReferenceObject(java.lang.String)
189     */
190    @Override
191    public void refreshReferenceObject(String referenceObjectName) {
192        KRADServiceLocator.getPersistenceService().retrieveReferenceObject(this, referenceObjectName);
193    }
194
195    /**
196     * @see org.kuali.rice.krad.document.Document#prepareForSave()
197     */
198    public void prepareForSave() {
199        // do nothing
200    }
201
202    /**
203     * @see org.kuali.rice.krad.document.Document#processAfterRetrieve()
204     */
205    public void processAfterRetrieve() {
206        // do nothing
207    }
208
209    /**
210     * The the default implementation for RouteLevelChange does nothing, but is meant to provide a hook for documents to
211     * implement
212     * for other needs.
213     *
214     * @see org.kuali.rice.krad.document.Document#doRouteLevelChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange)
215     */
216    public void doRouteLevelChange(DocumentRouteLevelChange levelChangeEvent) {
217        // do nothing
218    }
219
220    /**
221     * @see org.kuali.rice.krad.document.Document#doActionTaken(org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent)
222     */
223    public void doActionTaken(ActionTakenEvent event) {
224        if ((KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(
225                this.getClass().getName()).getUseWorkflowPessimisticLocking()) && (!getNonLockingActionTakenCodes()
226                .contains(event.getActionTaken().getActionTaken().getCode()))) {
227            KRADServiceLocatorWeb.getPessimisticLockService().establishWorkflowPessimisticLocking(this);
228        }
229    }
230
231    /**
232     * @see org.kuali.rice.krad.document.Document#afterActionTaken(ActionType, org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent)
233     */
234    public void afterActionTaken(ActionType performed, ActionTakenEvent event) {
235        // do nothing
236    }
237
238    protected List<String> getNonLockingActionTakenCodes() {
239        List<String> actionTakenStatusCodes = new ArrayList<String>();
240        actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_SAVED_CD);
241        actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD);
242        actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_FYI_CD);
243        actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_DENIED_CD);
244        actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_CANCELED_CD);
245        actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_LOG_DOCUMENT_ACTION_CD);
246        return actionTakenStatusCodes;
247    }
248
249    /**
250     * The the default implementation for afterWorkflowEngineProcess does nothing, but is meant to provide a hook for
251     * documents to implement for other needs.
252     *
253     * @see org.kuali.rice.krad.document.Document#afterWorkflowEngineProcess(boolean)
254     */
255    public void afterWorkflowEngineProcess(boolean successfullyProcessed) {
256        if (KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(
257                this.getClass().getName()).getUseWorkflowPessimisticLocking()) {
258            if (successfullyProcessed) {
259                KRADServiceLocatorWeb.getPessimisticLockService().releaseWorkflowPessimisticLocking(this);
260            }
261        }
262    }
263
264    /**
265     * The the default implementation for beforeWorkflowEngineProcess does nothing, but is meant to provide a hook for
266     * documents to implement for other needs.
267     *
268     * @see org.kuali.rice.krad.document.Document#beforeWorkflowEngineProcess()
269     */
270    public void beforeWorkflowEngineProcess() {
271        // do nothing
272    }
273
274    /**
275     * The default implementation returns no additional ids for the workflow engine to lock prior to processing.
276     *
277     * @see org.kuali.rice.krad.document.Document#getWorkflowEngineDocumentIdsToLock()
278     */
279    public List<String> getWorkflowEngineDocumentIdsToLock() {
280        return null;
281    }
282
283    /**
284     * @see org.kuali.rice.krad.document.Copyable#toCopy()
285     */
286    public void toCopy() throws WorkflowException, IllegalStateException {
287        if (!this.getAllowsCopy()) {
288            throw new IllegalStateException(this.getClass().getName() + " does not support document-level copying");
289        }
290        String sourceDocumentHeaderId = getDocumentNumber();
291        setNewDocumentHeader();
292
293        getDocumentHeader().setDocumentTemplateNumber(sourceDocumentHeaderId);
294
295        //clear out notes from previous bo
296        this.notes.clear();
297        addCopyErrorDocumentNote("copied from document " + sourceDocumentHeaderId);
298    }
299
300    /**
301     * Gets a new document header for this documents type and sets in the document instance.
302     *
303     * @throws WorkflowException
304     */
305    protected void setNewDocumentHeader() throws WorkflowException {
306        TransactionalDocument newDoc =
307                (TransactionalDocument) KRADServiceLocatorWeb.getDocumentService().getNewDocument(
308                        getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
309        newDoc.getDocumentHeader().setDocumentDescription(getDocumentHeader().getDocumentDescription());
310        newDoc.getDocumentHeader().setOrganizationDocumentNumber(getDocumentHeader().getOrganizationDocumentNumber());
311
312        try {
313            ObjectUtils.setObjectPropertyDeep(this, KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber.getClass(),
314                    newDoc.getDocumentNumber());
315        } catch (Exception e) {
316            LOG.error("Unable to set document number property in copied document " + e.getMessage(), e);
317            throw new RuntimeException("Unable to set document number property in copied document " + e.getMessage(),
318                    e);
319        }
320
321        // replace current documentHeader with new documentHeader
322        setDocumentHeader(newDoc.getDocumentHeader());
323    }
324
325    /**
326     * Adds a note to the document indicating it was created by a copy or error correction.
327     *
328     * @param noteText - text for note
329     */
330    protected void addCopyErrorDocumentNote(String noteText) {
331        Note note = null;
332        try {
333            note = KRADServiceLocatorWeb.getDocumentService().createNoteFromDocument(this, noteText);
334        } catch (Exception e) {
335            logErrors();
336            throw new RuntimeException("Couldn't create note on copy or error", e);
337        }
338        addNote(note);
339    }
340
341    /**
342     * @see org.kuali.rice.krad.document.Document#getXmlForRouteReport()
343     */
344    public String getXmlForRouteReport() {
345        prepareForSave();
346        populateDocumentForRouting();
347        return getDocumentHeader().getWorkflowDocument().getApplicationContent();
348    }
349
350    /**
351     * @see org.kuali.rice.krad.document.Document#populateDocumentForRouting()
352     */
353    public void populateDocumentForRouting() {
354        getDocumentHeader().getWorkflowDocument().setApplicationContent(serializeDocumentToXml());
355    }
356
357    /**
358     * @see org.kuali.rice.krad.document.Document#serializeDocumentToXml()
359     */
360    public String serializeDocumentToXml() {
361        DocumentSerializerService documentSerializerService = KRADServiceLocatorWeb.getDocumentSerializerService();
362        String xml = documentSerializerService.serializeDocumentToXmlForRouting(this);
363        return xml;
364    }
365
366    /**
367     * Wraps a document in an instance of KualiDocumentXmlMaterializer, that provides additional metadata for
368     * serialization
369     *
370     * @see org.kuali.rice.krad.document.Document#wrapDocumentWithMetadataForXmlSerialization()
371     */
372    public KualiDocumentXmlMaterializer wrapDocumentWithMetadataForXmlSerialization() {
373        KualiTransactionalDocumentInformation transInfo = new KualiTransactionalDocumentInformation();
374        DocumentInitiator initiator = new DocumentInitiator();
375        String initiatorPrincipalId = getDocumentHeader().getWorkflowDocument().getDocument().getInitiatorPrincipalId();
376        Person initiatorUser = KimApiServiceLocator.getPersonService().getPerson(initiatorPrincipalId);
377        initiator.setPerson(initiatorUser);
378        transInfo.setDocumentInitiator(initiator);
379        KualiDocumentXmlMaterializer xmlWrapper = new KualiDocumentXmlMaterializer();
380        xmlWrapper.setDocument(this);
381        xmlWrapper.setKualiTransactionalDocumentInformation(transInfo);
382        return xmlWrapper;
383    }
384
385    /**
386     * If workflowProperties have been defined within the data dictionary for this document, then it returns an instance
387     * of
388     * {@link BusinessObjectPropertySerializibilityEvaluator} initialized with the properties.  If none have been
389     * defined, then returns
390     * {@link AlwaysTruePropertySerializibilityEvaluator}.
391     *
392     * @see org.kuali.rice.krad.document.Document#getDocumentPropertySerizabilityEvaluator()
393     */
394    public PropertySerializabilityEvaluator getDocumentPropertySerizabilityEvaluator() {
395        String docTypeName = getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
396        DocumentEntry documentEntry =
397                KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(docTypeName);
398        WorkflowProperties workflowProperties = documentEntry.getWorkflowProperties();
399        WorkflowAttributes workflowAttributes = documentEntry.getWorkflowAttributes();
400        return createPropertySerializabilityEvaluator(workflowProperties, workflowAttributes);
401    }
402
403    protected PropertySerializabilityEvaluator createPropertySerializabilityEvaluator(
404            WorkflowProperties workflowProperties, WorkflowAttributes workflowAttributes) {
405        if (workflowAttributes != null) {
406            return new AlwaysFalsePropertySerializabilityEvaluator();
407        }
408        if (workflowProperties == null) {
409            return new AlwaysTruePropertySerializibilityEvaluator();
410        }
411        PropertySerializabilityEvaluator evaluator = new BusinessObjectPropertySerializibilityEvaluator();
412        evaluator.initializeEvaluatorForDocument(this);
413        return evaluator;
414    }
415
416    /**
417     * Returns the POJO property name of "this" document in the object returned by {@link
418     * #wrapDocumentWithMetadataForXmlSerialization()}
419     *
420     * @see org.kuali.rice.krad.document.Document#getBasePathToDocumentDuringSerialization()
421     */
422    public String getBasePathToDocumentDuringSerialization() {
423        return "document";
424    }
425
426    /**
427     * @see org.kuali.rice.krad.document.Document#getDocumentHeader()
428     */
429    public DocumentHeader getDocumentHeader() {
430        return this.documentHeader;
431    }
432
433    /**
434     * @see org.kuali.rice.krad.document.Document#setDocumentHeader(org.kuali.rice.krad.document.DocumentHeader)
435     */
436    public void setDocumentHeader(DocumentHeader documentHeader) {
437        this.documentHeader = documentHeader;
438    }
439
440    /**
441     * @see org.kuali.rice.krad.document.Document#getDocumentNumber()
442     */
443    public String getDocumentNumber() {
444        return documentNumber;
445    }
446
447    /**
448     * @see org.kuali.rice.krad.document.Document#setDocumentNumber(java.lang.String)
449     */
450    public void setDocumentNumber(String documentNumber) {
451        this.documentNumber = documentNumber;
452    }
453
454    /**
455     * @see org.kuali.rice.krad.document.Document#getAdHocRoutePersons()
456     */
457    public List<AdHocRoutePerson> getAdHocRoutePersons() {
458        return adHocRoutePersons;
459    }
460
461    /**
462     * @see org.kuali.rice.krad.document.Document#setAdHocRoutePersons(java.util.List)
463     */
464    public void setAdHocRoutePersons(List<AdHocRoutePerson> adHocRoutePersons) {
465        this.adHocRoutePersons = adHocRoutePersons;
466    }
467
468    /**
469     * @see org.kuali.rice.krad.document.Document#getAdHocRouteWorkgroups()
470     */
471    public List<AdHocRouteWorkgroup> getAdHocRouteWorkgroups() {
472        return adHocRouteWorkgroups;
473    }
474
475    /**
476     * @see org.kuali.rice.krad.document.Document#setAdHocRouteWorkgroups(java.util.List)
477     */
478    public void setAdHocRouteWorkgroups(List<AdHocRouteWorkgroup> adHocRouteWorkgroups) {
479        this.adHocRouteWorkgroups = adHocRouteWorkgroups;
480    }
481
482    public void postProcessSave(KualiDocumentEvent event) {
483        // TODO Auto-generated method stub
484
485    }
486
487    /**
488     * Override this method with implementation specific prepareForSave logic
489     *
490     * @see org.kuali.rice.krad.document.Document#prepareForSave(org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent)
491     */
492    public void prepareForSave(KualiDocumentEvent event) {
493        // do nothing by default
494    }
495
496    public void validateBusinessRules(KualiDocumentEvent event) {
497        if (GlobalVariables.getMessageMap().hasErrors()) {
498            logErrors();
499            throw new ValidationException("errors occured before business rule");
500        }
501
502        // perform validation against rules engine
503        LOG.info("invoking rules engine on document " + getDocumentNumber());
504        boolean isValid = true;
505        isValid = KRADServiceLocatorWeb.getKualiRuleService().applyRules(event);
506
507        // check to see if the br eval passed or failed
508        if (!isValid) {
509            logErrors();
510            // TODO: better error handling at the lower level and a better error message are
511            // needed here
512            throw new ValidationException("business rule evaluation failed");
513        } else if (GlobalVariables.getMessageMap().hasErrors()) {
514            logErrors();
515            throw new ValidationException(
516                    "Unreported errors occured during business rule evaluation (rule developer needs to put meaningful error messages into global ErrorMap)");
517        }
518        LOG.debug("validation completed");
519
520    }
521
522    /**
523     * This method logs errors.
524     */
525    protected void logErrors() {
526        if (LOG.isInfoEnabled()) {
527            if (GlobalVariables.getMessageMap().hasErrors()) {
528
529                for (Iterator<Map.Entry<String, AutoPopulatingList<ErrorMessage>>> i =
530                             GlobalVariables.getMessageMap().getAllPropertiesAndErrors().iterator(); i.hasNext(); ) {
531                    Map.Entry<String, AutoPopulatingList<ErrorMessage>> e = i.next();
532
533                    StringBuffer logMessage = new StringBuffer();
534                    logMessage.append("[" + e.getKey() + "] ");
535                    boolean first = true;
536
537                    AutoPopulatingList<ErrorMessage> errorList = e.getValue();
538                    for (Iterator<ErrorMessage> j = errorList.iterator(); j.hasNext(); ) {
539                        ErrorMessage em = j.next();
540
541                        if (first) {
542                            first = false;
543                        } else {
544                            logMessage.append(";");
545                        }
546                        logMessage.append(em);
547                    }
548
549                    LOG.info(logMessage);
550                }
551            }
552        }
553    }
554
555    /**
556     * Hook for override
557     *
558     * @see org.kuali.rice.krad.document.Document#generateSaveEvents()
559     */
560    public List<KualiDocumentEvent> generateSaveEvents() {
561        return new ArrayList<KualiDocumentEvent>();
562    }
563
564    /**
565     * @see org.kuali.rice.krad.document.Document#doRouteStatusChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange)
566     */
567    public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
568        // do nothing
569    }
570
571    /**
572     * Returns the business object with which notes related to this document should be associated.
573     * By default, the {@link DocumentHeader} of this document will be returned as the note target.
574     *
575     * <p>Sub classes can override this method if they want notes to be associated with something
576     * other than the document header.  If this method is overridden, the {@link #getNoteType()}
577     * method should be overridden to return {@link NoteType#BUSINESS_OBJECT}
578     *
579     * @return Returns the documentBusinessObject.
580     */
581    @Override
582    public PersistableBusinessObject getNoteTarget() {
583        return getDocumentHeader();
584    }
585
586    /**
587     * Returns the {@link NoteType} to use for notes associated with this document.
588     * By default this returns {@link NoteType#DOCUMENT_HEADER} since notes are
589     * associated with the {@link DocumentHeader} record by default.
590     *
591     * <p>The case in which this should be overridden is if {@link #getNoteTarget()} is
592     * overridden to return an object other than the DocumentHeader.
593     *
594     * @return the note type to use for notes associated with this document
595     * @see org.kuali.rice.krad.document.Document#getNoteType()
596     */
597    @Override
598    public NoteType getNoteType() {
599        return NoteType.DOCUMENT_HEADER;
600    }
601
602    /**
603     * @see org.kuali.rice.krad.document.Document#addNote(org.kuali.rice.krad.bo.Note)
604     */
605    @Override
606    public void addNote(Note note) {
607        if (note == null) {
608            throw new IllegalArgumentException("Note cannot be null.");
609        }
610        notes.add(note);
611    }
612
613    /**
614     * @see org.kuali.rice.krad.document.Document#removeNote(org.kuali.rice.krad.bo.Note)
615     */
616    @Override
617    public boolean removeNote(Note note) {
618        if (note == null) {
619            throw new IllegalArgumentException("Note cannot be null.");
620        }
621        return notes.remove(note);
622    }
623
624    /**
625     * @see org.kuali.rice.krad.document.Document#getNote(int)
626     */
627    @Override
628    public Note getNote(int index) {
629        return notes.get(index);
630    }
631
632    /**
633     * @see org.kuali.rice.krad.document.Document#getNotes()
634     */
635    @Override
636    public List<Note> getNotes() {
637        if (CollectionUtils.isEmpty(notes) && getNoteType().equals(NoteType.BUSINESS_OBJECT) && StringUtils.isNotBlank(
638                getNoteTarget().getObjectId())) {
639            notes = getNoteService().getByRemoteObjectId(getNoteTarget().getObjectId());
640        }
641
642        return notes;
643    }
644
645    /**
646     * @see org.kuali.rice.krad.document.Document#setNotes(java.util.List)
647     */
648    @Override
649    public void setNotes(List<Note> notes) {
650        if (notes == null) {
651            throw new IllegalArgumentException("List of notes must be non-null.");
652        }
653        this.notes = notes;
654    }
655
656    @Override
657    protected void postLoad() {
658        super.postLoad();
659        refreshPessimisticLocks();
660    }
661
662    /**
663     * @see org.kuali.rice.krad.document.Document#getPessimisticLocks()
664     */
665    public List<PessimisticLock> getPessimisticLocks() {
666        return this.pessimisticLocks;
667    }
668
669    /**
670     * @see org.kuali.rice.krad.document.Document#refreshPessimisticLocks()
671     * @deprecated This is not needed with the relationship set up with JPA annotations
672     */
673    @Deprecated
674    public void refreshPessimisticLocks() {
675        this.pessimisticLocks.clear();
676        this.pessimisticLocks = KRADServiceLocatorWeb.getPessimisticLockService().getPessimisticLocksForDocument(
677                this.documentNumber);
678    }
679
680    /**
681     * @param pessimisticLocks the PessimisticLock objects to set
682     */
683    public void setPessimisticLocks(List<PessimisticLock> pessimisticLocks) {
684        this.pessimisticLocks = pessimisticLocks;
685    }
686
687    /**
688     * @see org.kuali.rice.krad.document.Document#addPessimisticLock(org.kuali.rice.krad.document.authorization.PessimisticLock)
689     */
690    public void addPessimisticLock(PessimisticLock lock) {
691        this.pessimisticLocks.add(lock);
692    }
693
694    /**
695     * @see org.kuali.rice.krad.document.Document#getLockClearningMethodNames()
696     */
697    public List<String> getLockClearningMethodNames() {
698        List<String> methodToCalls = new ArrayList<String>();
699        methodToCalls.add(KRADConstants.CLOSE_METHOD);
700        methodToCalls.add(KRADConstants.CANCEL_METHOD);
701        //        methodToCalls.add(RiceConstants.BLANKET_APPROVE_METHOD);
702        methodToCalls.add(KRADConstants.ROUTE_METHOD);
703        methodToCalls.add(KRADConstants.APPROVE_METHOD);
704        methodToCalls.add(KRADConstants.DISAPPROVE_METHOD);
705        methodToCalls.add(KRADConstants.ACKNOWLEDGE_METHOD);
706        return methodToCalls;
707    }
708
709    /**
710     * This default implementation simply returns false to indicate that custom lock descriptors are not supported by
711     * DocumentBase. If custom lock
712     * descriptors are needed, the appropriate subclasses should override this method.
713     *
714     * @see org.kuali.rice.krad.document.Document#useCustomLockDescriptors()
715     */
716    public boolean useCustomLockDescriptors() {
717        return false;
718    }
719
720    /**
721     * This default implementation just throws a PessimisticLockingException. Subclasses of DocumentBase that need
722     * support for custom lock descriptors
723     * should override this method.
724     *
725     * @see org.kuali.rice.krad.document.Document#getCustomLockDescriptor(org.kuali.rice.kim.api.identity.Person)
726     */
727    public String getCustomLockDescriptor(Person user) {
728        throw new PessimisticLockingException("Document " + getDocumentNumber() +
729                " is using pessimistic locking with custom lock descriptors, but the document class has not overriden the getCustomLockDescriptor method");
730    }
731
732    protected AttachmentService getAttachmentService() {
733        if (attachmentService == null) {
734            attachmentService = KRADServiceLocator.getAttachmentService();
735        }
736        return attachmentService;
737    }
738
739    protected NoteService getNoteService() {
740        if (noteService == null) {
741            noteService = KRADServiceLocator.getNoteService();
742        }
743        return noteService;
744    }
745}