001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.document;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.kew.api.KewApiConstants;
021import org.kuali.rice.kew.api.KewApiServiceLocator;
022import org.kuali.rice.kew.api.action.ActionType;
023import org.kuali.rice.kew.api.exception.WorkflowException;
024import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
025import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
026import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
027import org.kuali.rice.kim.api.identity.Person;
028import org.kuali.rice.kim.api.services.KimApiServiceLocator;
029import org.kuali.rice.krad.bo.AdHocRoutePerson;
030import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
031import org.kuali.rice.krad.bo.DocumentHeader;
032import org.kuali.rice.krad.bo.Note;
033import org.kuali.rice.krad.bo.PersistableBusinessObject;
034import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
035import org.kuali.rice.krad.datadictionary.DocumentEntry;
036import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
037import org.kuali.rice.krad.datadictionary.WorkflowProperties;
038import org.kuali.rice.krad.document.authorization.PessimisticLock;
039import org.kuali.rice.krad.exception.PessimisticLockingException;
040import org.kuali.rice.krad.exception.ValidationException;
041import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
042import org.kuali.rice.krad.service.AttachmentService;
043import org.kuali.rice.krad.service.DocumentSerializerService;
044import org.kuali.rice.krad.service.KRADServiceLocator;
045import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
046import org.kuali.rice.krad.service.NoteService;
047import org.kuali.rice.krad.util.ErrorMessage;
048import org.kuali.rice.krad.util.GlobalVariables;
049import org.kuali.rice.krad.util.KRADConstants;
050import org.kuali.rice.krad.util.KRADPropertyConstants;
051import org.kuali.rice.krad.util.NoteType;
052import org.kuali.rice.krad.util.ObjectUtils;
053import org.kuali.rice.krad.util.documentserializer.AlwaysFalsePropertySerializabilityEvaluator;
054import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
055import org.kuali.rice.krad.util.documentserializer.BusinessObjectPropertySerializibilityEvaluator;
056import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
057import org.kuali.rice.krad.workflow.DocumentInitiator;
058import org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer;
059import org.kuali.rice.krad.workflow.KualiTransactionalDocumentInformation;
060import org.springframework.util.AutoPopulatingList;
061import org.springframework.util.CollectionUtils;
062
063import javax.persistence.CascadeType;
064import javax.persistence.Column;
065import javax.persistence.FetchType;
066import javax.persistence.Id;
067import javax.persistence.JoinColumn;
068import javax.persistence.MappedSuperclass;
069import javax.persistence.OneToMany;
070import javax.persistence.OneToOne;
071import javax.persistence.Transient;
072import java.util.ArrayList;
073import java.util.Iterator;
074import java.util.List;
075import java.util.Map;
076
077
078/**
079 * @see Document
080 */
081@MappedSuperclass
082public abstract class DocumentBase extends PersistableBusinessObjectBase implements Document {
083    private static final Logger LOG = Logger.getLogger(DocumentBase.class);
084    
085    @Id
086    @Column(name="DOC_HDR_ID")
087    protected String documentNumber;
088    @OneToOne(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE})
089        @JoinColumn(name="DOC_HDR_ID", insertable=false, updatable=false)
090    protected DocumentHeader documentHeader;    
091
092    @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE})
093        @JoinColumn(name="DOC_HDR_ID", insertable=false, updatable=false)
094    private List<PessimisticLock> pessimisticLocks;
095
096    @Transient
097    private List<AdHocRoutePerson> adHocRoutePersons;
098    @Transient
099    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}