View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.document;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.kew.api.KewApiConstants;
21  import org.kuali.rice.kew.api.KewApiServiceLocator;
22  import org.kuali.rice.kew.api.action.ActionType;
23  import org.kuali.rice.kew.api.exception.WorkflowException;
24  import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
25  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
26  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
27  import org.kuali.rice.kim.api.identity.Person;
28  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
29  import org.kuali.rice.krad.bo.AdHocRoutePerson;
30  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
31  import org.kuali.rice.krad.bo.DocumentHeader;
32  import org.kuali.rice.krad.bo.Note;
33  import org.kuali.rice.krad.bo.PersistableBusinessObject;
34  import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
35  import org.kuali.rice.krad.datadictionary.DocumentEntry;
36  import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
37  import org.kuali.rice.krad.datadictionary.WorkflowProperties;
38  import org.kuali.rice.krad.document.authorization.PessimisticLock;
39  import org.kuali.rice.krad.exception.PessimisticLockingException;
40  import org.kuali.rice.krad.exception.ValidationException;
41  import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
42  import org.kuali.rice.krad.service.AttachmentService;
43  import org.kuali.rice.krad.service.DocumentSerializerService;
44  import org.kuali.rice.krad.service.KRADServiceLocator;
45  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
46  import org.kuali.rice.krad.service.NoteService;
47  import org.kuali.rice.krad.util.ErrorMessage;
48  import org.kuali.rice.krad.util.GlobalVariables;
49  import org.kuali.rice.krad.util.KRADConstants;
50  import org.kuali.rice.krad.util.KRADPropertyConstants;
51  import org.kuali.rice.krad.util.NoteType;
52  import org.kuali.rice.krad.util.ObjectUtils;
53  import org.kuali.rice.krad.util.documentserializer.AlwaysFalsePropertySerializabilityEvaluator;
54  import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
55  import org.kuali.rice.krad.util.documentserializer.BusinessObjectPropertySerializibilityEvaluator;
56  import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
57  import org.kuali.rice.krad.workflow.DocumentInitiator;
58  import org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer;
59  import org.kuali.rice.krad.workflow.KualiTransactionalDocumentInformation;
60  import org.springframework.util.AutoPopulatingList;
61  import org.springframework.util.CollectionUtils;
62  
63  import javax.persistence.CascadeType;
64  import javax.persistence.Column;
65  import javax.persistence.FetchType;
66  import javax.persistence.Id;
67  import javax.persistence.JoinColumn;
68  import javax.persistence.MappedSuperclass;
69  import javax.persistence.OneToMany;
70  import javax.persistence.OneToOne;
71  import javax.persistence.Transient;
72  import java.util.ArrayList;
73  import java.util.Iterator;
74  import java.util.List;
75  import java.util.Map;
76  
77  /**
78   * @see Document
79   */
80  @MappedSuperclass
81  public abstract class DocumentBase extends PersistableBusinessObjectBase implements Document {
82      private static final Logger LOG = Logger.getLogger(DocumentBase.class);
83  
84      @Id @Column(name = "DOC_HDR_ID")
85      protected String documentNumber;
86      @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(
87              name = "DOC_HDR_ID", insertable = false, updatable = false)
88      protected DocumentHeader documentHeader;
89  
90      @OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(
91              name = "DOC_HDR_ID", insertable = false, updatable = false)
92      private List<PessimisticLock> pessimisticLocks;
93  
94      @Transient
95      private List<AdHocRoutePerson> adHocRoutePersons;
96      @Transient
97      private List<AdHocRouteWorkgroup> adHocRouteWorkgroups;
98      @Transient
99      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 }