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