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