View Javadoc
1   /**
2    * Copyright 2005-2015 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 java.util.ArrayList;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Map;
22  
23  import javax.persistence.Column;
24  import javax.persistence.Id;
25  import javax.persistence.MappedSuperclass;
26  import javax.persistence.PostLoad;
27  import javax.persistence.PostRemove;
28  import javax.persistence.PrePersist;
29  import javax.persistence.Transient;
30  
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.log4j.Logger;
33  import org.kuali.rice.core.api.mo.common.GloballyUnique;
34  import org.kuali.rice.kew.api.KewApiConstants;
35  import org.kuali.rice.kew.api.KewApiServiceLocator;
36  import org.kuali.rice.kew.api.WorkflowDocument;
37  import org.kuali.rice.kew.api.action.ActionRequest;
38  import org.kuali.rice.kew.api.action.ActionType;
39  import org.kuali.rice.kew.api.exception.WorkflowException;
40  import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
41  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
42  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
43  import org.kuali.rice.kim.api.identity.Person;
44  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
45  import org.kuali.rice.krad.UserSessionUtils;
46  import org.kuali.rice.krad.bo.AdHocRoutePerson;
47  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
48  import org.kuali.rice.krad.bo.DocumentHeader;
49  import org.kuali.rice.krad.bo.Note;
50  import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
51  import org.kuali.rice.krad.datadictionary.DocumentEntry;
52  import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
53  import org.kuali.rice.krad.datadictionary.WorkflowProperties;
54  import org.kuali.rice.krad.document.authorization.PessimisticLock;
55  import org.kuali.rice.krad.exception.PessimisticLockingException;
56  import org.kuali.rice.krad.exception.ValidationException;
57  import org.kuali.rice.krad.rules.rule.event.DocumentEvent;
58  import org.kuali.rice.krad.service.AttachmentService;
59  import org.kuali.rice.krad.service.DocumentSerializerService;
60  import org.kuali.rice.krad.service.KRADServiceLocator;
61  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
62  import org.kuali.rice.krad.service.NoteService;
63  import org.kuali.rice.krad.util.ErrorMessage;
64  import org.kuali.rice.krad.util.GlobalVariables;
65  import org.kuali.rice.krad.util.KRADConstants;
66  import org.kuali.rice.krad.util.KRADPropertyConstants;
67  import org.kuali.rice.krad.util.NoteType;
68  import org.kuali.rice.krad.util.documentserializer.AlwaysFalsePropertySerializabilityEvaluator;
69  import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
70  import org.kuali.rice.krad.util.documentserializer.BusinessObjectPropertySerializibilityEvaluator;
71  import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
72  import org.kuali.rice.krad.workflow.DocumentInitiator;
73  import org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer;
74  import org.kuali.rice.krad.workflow.KualiTransactionalDocumentInformation;
75  import org.springframework.util.CollectionUtils;
76  
77  /**
78   * @see Document
79   */
80  @MappedSuperclass
81  public abstract class DocumentBase extends PersistableBusinessObjectBaseAdapter implements Document {
82      private static final long serialVersionUID = 8530945307802647664L;
83      private static final Logger LOG = Logger.getLogger(DocumentBase.class);
84  
85      @Id
86      @Column(name = "DOC_HDR_ID",length=14)
87      protected String documentNumber;
88  
89      @Transient
90      protected DocumentHeader documentHeader;
91  
92      @Transient
93      protected List<PessimisticLock> pessimisticLocks;
94  
95      @Transient
96      protected List<AdHocRoutePerson> adHocRoutePersons;
97      @Transient
98      protected List<AdHocRouteWorkgroup> adHocRouteWorkgroups;
99      @Transient
100     protected List<Note> notes;
101     @Transient
102     private String superUserAnnotation = "";
103 
104     private transient NoteService noteService;
105     private transient AttachmentService attachmentService;
106 
107     /**
108      * Constructs a DocumentBase.java.
109      */
110     public DocumentBase() {
111         documentHeader = new DocumentHeader();
112         pessimisticLocks = new ArrayList<PessimisticLock>();
113         adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
114         adHocRouteWorkgroups = new ArrayList<AdHocRouteWorkgroup>();
115         notes = new ArrayList<Note>();
116     }
117 
118     /**
119      * @see org.kuali.rice.krad.document.Document#getAllowsCopy()
120      */
121     @Override
122     public boolean getAllowsCopy() {
123         return false;
124     }
125 
126     /**
127      * Retrieves the title of the document
128      *
129      * <p>
130      * This is the default document title implementation. It concatenates the document's data dictionary file label
131      * attribute and
132      * the document's document header description together. This title is used to populate workflow and will show up in
133      * document
134      * search results and user action lists.
135      * </p>
136      *
137      * return String representing the title of the document
138      *
139      * @see org.kuali.rice.krad.document.Document#getDocumentTitle()
140      */
141     @Override
142     public String getDocumentTitle() {
143         String documentTypeLabel = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(
144                 this.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()).getLabel();
145         if (null == documentTypeLabel) {
146             documentTypeLabel = "";
147         }
148 
149         String description = this.getDocumentHeader().getDocumentDescription();
150         if (null == description) {
151             description = "";
152         }
153 
154         return documentTypeLabel + " - " + description;
155     }
156 
157     /**
158      * @see org.kuali.rice.krad.document.Document#prepareForSave()
159      */
160     @Override
161     public void prepareForSave() {
162         // do nothing
163     }
164 
165     /**
166      * @see org.kuali.rice.krad.document.Document#processAfterRetrieve()
167      */
168     @Override
169     public void processAfterRetrieve() {
170         // do nothing
171     }
172 
173     /**
174      * The the default implementation for RouteLevelChange does nothing, but is meant to provide a hook for documents to
175      * implement
176      * for other needs.
177      *
178      * @see org.kuali.rice.krad.document.Document#doRouteLevelChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange)
179      */
180     @Override
181     public void doRouteLevelChange(DocumentRouteLevelChange levelChangeEvent) {
182         // do nothing
183     }
184 
185     /**
186      * @see org.kuali.rice.krad.document.Document#doActionTaken(org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent)
187      */
188     @Override
189     public void doActionTaken(ActionTakenEvent event) {
190         if ((KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(
191                 this.getClass().getName()).getUseWorkflowPessimisticLocking()) && (!getNonLockingActionTakenCodes()
192                 .contains(event.getActionTaken().getActionTaken().getCode()))) {
193             KRADServiceLocatorWeb.getPessimisticLockService().establishWorkflowPessimisticLocking(this);
194         }
195     }
196 
197     /**
198      * @see org.kuali.rice.krad.document.Document#afterActionTaken(ActionType, org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent)
199      */
200     @Override
201     public void afterActionTaken(ActionType performed, ActionTakenEvent event) {
202         // do nothing
203     }
204 
205     /**
206      * Return the list of actions a user could take on a document which should not result
207      * in the recalculation of the {@link PessimisticLock}s.
208      * 
209      * @see #doActionTaken(ActionTakenEvent)
210      */
211     protected List<String> getNonLockingActionTakenCodes() {
212         List<String> actionTakenStatusCodes = new ArrayList<String>();
213         actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_SAVED_CD);
214         actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD);
215         actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_FYI_CD);
216         actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_DENIED_CD);
217         actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_CANCELED_CD);
218         actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_LOG_DOCUMENT_ACTION_CD);
219         return actionTakenStatusCodes;
220     }
221 
222     /**
223      * The the default implementation for afterWorkflowEngineProcess does nothing, but is meant to provide a hook for
224      * documents to implement for other needs.
225      *
226      * @see org.kuali.rice.krad.document.Document#afterWorkflowEngineProcess(boolean)
227      */
228     @Override
229     public void afterWorkflowEngineProcess(boolean successfullyProcessed) {
230         if (KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(
231                 this.getClass().getName()).getUseWorkflowPessimisticLocking()) {
232             if (successfullyProcessed) {
233                 KRADServiceLocatorWeb.getPessimisticLockService().releaseWorkflowPessimisticLocking(this);
234             }
235         }
236     }
237 
238     /**
239      * The the default implementation for beforeWorkflowEngineProcess does nothing, but is meant to provide a hook for
240      * documents to implement for other needs.
241      *
242      * @see org.kuali.rice.krad.document.Document#beforeWorkflowEngineProcess()
243      */
244     @Override
245     public void beforeWorkflowEngineProcess() {
246         // do nothing
247     }
248 
249     /**
250      * The default implementation returns no additional ids for the workflow engine to lock prior to processing.
251      *
252      * @see org.kuali.rice.krad.document.Document#getWorkflowEngineDocumentIdsToLock()
253      */
254     @Override
255     public List<String> getWorkflowEngineDocumentIdsToLock() {
256         return null;
257     }
258 
259     /**
260      * @see org.kuali.rice.krad.document.Copyable#toCopy()
261      */
262     public void toCopy() throws WorkflowException, IllegalStateException {
263         if (!this.getAllowsCopy()) {
264             throw new IllegalStateException(this.getClass().getName() + " does not support document-level copying");
265         }
266         String sourceDocumentHeaderId = getDocumentNumber();
267         setNewDocumentHeader();
268 
269         //clear out notes from previous bo
270         this.notes.clear();
271         addCopyErrorDocumentNote("copied from document " + sourceDocumentHeaderId);
272     }
273 
274     /**
275      * Gets a new document header for this documents type and sets in the document instance.
276      *
277      * @throws WorkflowException
278      */
279     protected void setNewDocumentHeader() throws WorkflowException {
280         // collect the header information from the old document
281         Person user = GlobalVariables.getUserSession().getPerson();
282         WorkflowDocument sourceWorkflowDocument
283                 = KRADServiceLocatorWeb.getWorkflowDocumentService().loadWorkflowDocument(getDocumentNumber(), user);
284         String sourceDocumentTypeName = sourceWorkflowDocument.getDocumentTypeName();
285 
286         // initiate the new workflow entry, get the workflow doc
287         WorkflowDocument workflowDocument
288                 = KRADServiceLocatorWeb.getWorkflowDocumentService().createWorkflowDocument(sourceDocumentTypeName, user);
289         UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDocument);
290 
291         // set new values on the document header, including the document number from which it was copied
292         Document newDocument = KRADServiceLocatorWeb.getDocumentService().getNewDocument(sourceDocumentTypeName);
293         DocumentHeader newDocumentHeader = newDocument.getDocumentHeader();
294         newDocumentHeader.setDocumentTemplateNumber(getDocumentNumber());
295         newDocumentHeader.setDocumentDescription(getDocumentHeader().getDocumentDescription());
296         newDocumentHeader.setOrganizationDocumentNumber(getDocumentHeader().getOrganizationDocumentNumber());
297 
298         // set the new document number on this document
299         try {
300             KRADServiceLocatorWeb.getLegacyDataAdapter().setObjectPropertyDeep(this,
301                     KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber.getClass(), newDocument.getDocumentNumber());
302         } catch (Exception e) {
303             LOG.error("Unable to set document number property in copied document " + this, e);
304             throw new RuntimeException("Unable to set document number property in copied document " + this, e);
305         }
306 
307         // replace the current document header with the new document header
308         setDocumentHeader(newDocument.getDocumentHeader());
309     }
310 
311     /**
312      * Adds a note to the document indicating it was created by a copy or error correction.
313      *
314      * @param noteText - text for note
315      */
316     protected void addCopyErrorDocumentNote(String noteText) {
317         Note note = null;
318         try {
319             note = KRADServiceLocatorWeb.getDocumentService().createNoteFromDocument(this, noteText);
320         } catch (Exception e) {
321             logErrors();
322             throw new RuntimeException("Couldn't create note on copy or error", e);
323         }
324         addNote(note);
325     }
326 
327     /**
328      * @see org.kuali.rice.krad.document.Document#getXmlForRouteReport()
329      */
330     @Override
331     public String getXmlForRouteReport() {
332         prepareForSave();
333         populateDocumentForRouting();
334         return getDocumentHeader().getWorkflowDocument().getApplicationContent();
335     }
336 
337     /**
338      * @see org.kuali.rice.krad.document.Document#populateDocumentForRouting()
339      */
340     @Override
341     public void populateDocumentForRouting() {
342         getDocumentHeader().getWorkflowDocument().setApplicationContent(serializeDocumentToXml());
343     }
344 
345     /**
346      * @see org.kuali.rice.krad.document.Document#serializeDocumentToXml()
347      */
348     @Override
349     public String serializeDocumentToXml() {
350         DocumentSerializerService documentSerializerService = KRADServiceLocatorWeb.getDocumentSerializerService();
351         String xml = documentSerializerService.serializeDocumentToXmlForRouting(this);
352         return xml;
353     }
354 
355     /**
356      * Wraps a document in an instance of KualiDocumentXmlMaterializer, that provides additional metadata for
357      * serialization
358      *
359      * @see org.kuali.rice.krad.document.Document#wrapDocumentWithMetadataForXmlSerialization()
360      */
361     @Override
362     public KualiDocumentXmlMaterializer wrapDocumentWithMetadataForXmlSerialization() {
363         KualiTransactionalDocumentInformation transInfo = new KualiTransactionalDocumentInformation();
364         DocumentInitiator initiator = new DocumentInitiator();
365         String initiatorPrincipalId = getDocumentHeader().getWorkflowDocument().getDocument().getInitiatorPrincipalId();
366         Person initiatorUser = KimApiServiceLocator.getPersonService().getPerson(initiatorPrincipalId);
367         initiator.setPerson(initiatorUser);
368         transInfo.setDocumentInitiator(initiator);
369         KualiDocumentXmlMaterializer xmlWrapper = new KualiDocumentXmlMaterializer();
370         xmlWrapper.setDocument(this);
371         xmlWrapper.setKualiTransactionalDocumentInformation(transInfo);
372         return xmlWrapper;
373     }
374 
375     /**
376      * If workflowProperties have been defined within the data dictionary for this document, then it returns an instance
377      * of
378      * {@link BusinessObjectPropertySerializibilityEvaluator} initialized with the properties.  If none have been
379      * defined, then returns
380      * {@link AlwaysTruePropertySerializibilityEvaluator}.
381      *
382      * @see org.kuali.rice.krad.document.Document#getDocumentPropertySerizabilityEvaluator()
383      */
384     @Override
385     public PropertySerializabilityEvaluator getDocumentPropertySerizabilityEvaluator() {
386         String docTypeName = getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
387         DocumentEntry documentEntry =
388                 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(
395             WorkflowProperties workflowProperties, WorkflowAttributes workflowAttributes) {
396         if (workflowAttributes != null) {
397             return new AlwaysFalsePropertySerializabilityEvaluator();
398         }
399         if (workflowProperties == null) {
400             return new AlwaysTruePropertySerializibilityEvaluator();
401         }
402         PropertySerializabilityEvaluator evaluator = new BusinessObjectPropertySerializibilityEvaluator();
403         evaluator.initializeEvaluatorForDocument(this);
404         return evaluator;
405     }
406 
407     /**
408      * Returns the POJO property name of "this" document in the object returned by {@link
409      * #wrapDocumentWithMetadataForXmlSerialization()}
410      *
411      * @see org.kuali.rice.krad.document.Document#getBasePathToDocumentDuringSerialization()
412      */
413     @Override
414     public String getBasePathToDocumentDuringSerialization() {
415         return "document";
416     }
417 
418     /**
419      * {@inheritDoc}
420      */
421     @Override
422     public DocumentHeader getDocumentHeader() {
423         // during the transition time between OJB and JPA - the OJB hooks are not firing
424         // so, we lazy load the document header.
425         if ((documentHeader == null || documentHeader.getDocumentNumber() == null) && StringUtils.isNotBlank(documentNumber)) {
426             documentHeader = KRADServiceLocatorWeb.getDocumentHeaderService().getDocumentHeaderById(documentNumber);
427         }
428 
429         return this.documentHeader;
430     }
431 
432     /**
433      * {@inheritDoc}
434      */
435     @Override
436     public void setDocumentHeader(DocumentHeader documentHeader) {
437         this.documentHeader = documentHeader;
438     }
439 
440     /**
441      * {@inheritDoc}
442      */
443     @Override
444     public String getDocumentNumber() {
445         return documentNumber;
446     }
447 
448     /**
449      * {@inheritDoc}
450      */
451     @Override
452     public void setDocumentNumber(String documentNumber) {
453         this.documentNumber = documentNumber;
454     }
455 
456     /**
457      * {@inheritDoc}
458      */
459     @Override
460     public List<AdHocRoutePerson> getAdHocRoutePersons() {
461         return adHocRoutePersons;
462     }
463 
464     /**
465      * {@inheritDoc}
466      */
467     @Override
468     public void setAdHocRoutePersons(List<AdHocRoutePerson> adHocRoutePersons) {
469         this.adHocRoutePersons = adHocRoutePersons;
470     }
471 
472     /**
473      * {@inheritDoc}
474      */
475     @Override
476     public List<AdHocRouteWorkgroup> getAdHocRouteWorkgroups() {
477         return adHocRouteWorkgroups;
478     }
479 
480     /**
481      * {@inheritDoc}
482      */
483     @Override
484     public void setAdHocRouteWorkgroups(List<AdHocRouteWorkgroup> adHocRouteWorkgroups) {
485         this.adHocRouteWorkgroups = adHocRouteWorkgroups;
486     }
487 
488     @Override
489     public void postProcessSave(DocumentEvent event) {
490         // TODO Auto-generated method stub
491 
492     }
493 
494     /**
495      * Override this method with implementation specific prepareForSave logic
496      *
497      * @see org.kuali.rice.krad.document.Document#prepareForSave(org.kuali.rice.krad.rules.rule.event.DocumentEvent)
498      */
499     @Override
500     public void prepareForSave(DocumentEvent event) {
501         // do nothing by default
502     }
503 
504     @Override
505     public void validateBusinessRules(DocumentEvent event) {
506         if (GlobalVariables.getMessageMap().hasErrors()) {
507             logErrors();
508             throw new ValidationException("errors occured before business rule");
509         }
510 
511         // perform validation against rules engine
512         LOG.info("invoking rules engine on document " + getDocumentNumber());
513         boolean isValid = true;
514         isValid = KRADServiceLocatorWeb.getKualiRuleService().applyRules(event);
515 
516         // check to see if the br eval passed or failed
517         if (!isValid) {
518             logErrors();
519             // TODO: better error handling at the lower level and a better error message are
520             // needed here
521             throw new ValidationException("business rule evaluation failed");
522         } else if (GlobalVariables.getMessageMap().hasErrors()) {
523             logErrors();
524             throw new ValidationException(
525                     "Unreported errors occured during business rule evaluation (rule developer needs to put meaningful error messages into global ErrorMap)");
526         }
527         LOG.debug("validation completed");
528 
529     }
530 
531     /**
532      * This method logs errors.
533      */
534     protected void logErrors() {
535         if (LOG.isInfoEnabled()) {
536             if (GlobalVariables.getMessageMap().hasErrors()) {
537 
538                 for (Iterator<Map.Entry<String, List<ErrorMessage>>> i =
539                              GlobalVariables.getMessageMap().getAllPropertiesAndErrors().iterator(); i.hasNext(); ) {
540                     Map.Entry<String, List<ErrorMessage>> e = i.next();
541 
542                     StringBuffer logMessage = new StringBuffer();
543                     logMessage.append("[" + e.getKey() + "] ");
544                     boolean first = true;
545 
546                     List<ErrorMessage> errorList = e.getValue();
547                     for (Iterator<ErrorMessage> j = errorList.iterator(); j.hasNext(); ) {
548                         ErrorMessage em = j.next();
549 
550                         if (first) {
551                             first = false;
552                         } else {
553                             logMessage.append(";");
554                         }
555                         logMessage.append(em);
556                     }
557 
558                     LOG.info(logMessage);
559                 }
560             }
561         }
562     }
563 
564     /**
565      * Hook for override
566      *
567      * @see org.kuali.rice.krad.document.Document#generateSaveEvents()
568      */
569     @Override
570     public List<DocumentEvent> generateSaveEvents() {
571         return new ArrayList<DocumentEvent>();
572     }
573 
574     /**
575      * @see org.kuali.rice.krad.document.Document#doRouteStatusChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange)
576      */
577     @Override
578     public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
579         // do nothing
580     }
581 
582     /**
583      * Returns the business object with which notes related to this document should be associated.
584      * By default, the {@link DocumentHeader} of this document will be returned as the note target.
585      *
586      * <p>Sub classes can override this method if they want notes to be associated with something
587      * other than the document header.  If this method is overridden, the {@link #getNoteType()}
588      * method should be overridden to return {@link NoteType#BUSINESS_OBJECT}
589      *
590      * @return Returns the documentBusinessObject.
591      */
592     @Override
593     public GloballyUnique getNoteTarget() {
594         return getDocumentHeader();
595     }
596 
597     /**
598      * Returns the {@link NoteType} to use for notes associated with this document.
599      * By default this returns {@link NoteType#DOCUMENT_HEADER} since notes are
600      * associated with the {@link DocumentHeader} record by default.
601      *
602      * <p>The case in which this should be overridden is if {@link #getNoteTarget()} is
603      * overridden to return an object other than the DocumentHeader.
604      *
605      * @return the note type to use for notes associated with this document
606      * @see org.kuali.rice.krad.document.Document#getNoteType()
607      */
608     @Override
609     public NoteType getNoteType() {
610         return NoteType.DOCUMENT_HEADER;
611     }
612 
613     /**
614      * @see org.kuali.rice.krad.document.Document#addNote(org.kuali.rice.krad.bo.Note)
615      */
616     @Override
617     public void addNote(Note note) {
618         if (note == null) {
619             throw new IllegalArgumentException("Note cannot be null.");
620         }
621         notes.add(note);
622     }
623 
624     /**
625      * @see org.kuali.rice.krad.document.Document#removeNote(org.kuali.rice.krad.bo.Note)
626      */
627     @Override
628     public boolean removeNote(Note note) {
629         if (note == null) {
630             throw new IllegalArgumentException("Note cannot be null.");
631         }
632         return notes.remove(note);
633     }
634 
635     /**
636      * @see org.kuali.rice.krad.document.Document#getNote(int)
637      */
638     @Override
639     public Note getNote(int index) {
640         return notes.get(index);
641     }
642 
643     /**
644      * @see org.kuali.rice.krad.document.Document#getNotes()
645      */
646     @Override
647     public List<Note> getNotes() {
648         if (CollectionUtils.isEmpty(notes) && getNoteType().equals(NoteType.BUSINESS_OBJECT) && StringUtils.isNotBlank(
649                 getNoteTarget().getObjectId())) {
650             notes = getNoteService().getByRemoteObjectId(getNoteTarget().getObjectId());
651         }
652 
653         return notes;
654     }
655 
656     /**
657      * @see org.kuali.rice.krad.document.Document#setNotes(java.util.List)
658      */
659     @Override
660     public void setNotes(List<Note> notes) {
661         if (notes == null) {
662             throw new IllegalArgumentException("List of notes must be non-null.");
663         }
664         this.notes = notes;
665     }
666 
667     /**
668      * {@inheritDoc}
669      */
670     @Override
671     public List<ActionRequest> getActionRequests() {
672         return KewApiServiceLocator.getWorkflowDocumentService().getPendingActionRequests(getDocumentNumber());
673     }
674 
675     /**
676      * {@inheritDoc}
677      */
678     @Override
679     public String getSuperUserAnnotation() {
680         return superUserAnnotation;
681     }
682 
683     /**
684      * {@inheritDoc}
685      */
686     @Override
687     public void setSuperUserAnnotation(String superUserAnnotation) {
688         this.superUserAnnotation = superUserAnnotation;
689     }
690 
691     /**
692      * Loads the KRAD document header via the document header service.
693      *
694      * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#postLoad()
695      */
696     @PostLoad
697     protected void postLoad() {
698         documentHeader = KRADServiceLocatorWeb.getDocumentHeaderService().getDocumentHeaderById(documentNumber);
699         refreshPessimisticLocks();
700     }
701 
702     /**
703      * Save the KRAD document header via the document header service.
704      */
705     @PrePersist
706     protected void prePersist() {
707         super.prePersist();
708         // KRAD/JPA - have to change the handle to object to that just saved
709         documentHeader = KRADServiceLocatorWeb.getDocumentHeaderService().saveDocumentHeader(documentHeader);
710     }
711 
712     /**
713      * This overridden method is used to delete the {@link DocumentHeader} object due to the system not being able to
714      * manage the {@link DocumentHeader} object via mapping files
715      *
716      * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#postRemove()
717      */
718     @PostRemove
719     protected void postRemove() {
720         KRADServiceLocatorWeb.getDocumentHeaderService().deleteDocumentHeader(getDocumentHeader());
721     }
722 
723     /**
724      * @see org.kuali.rice.krad.document.Document#getPessimisticLocks()
725      */
726     @Override
727     public List<PessimisticLock> getPessimisticLocks() {
728         return pessimisticLocks;
729     }
730 
731     /**
732      * @see org.kuali.rice.krad.document.Document#refreshPessimisticLocks()
733      */
734     @Override
735     public void refreshPessimisticLocks() {
736         pessimisticLocks = KRADServiceLocatorWeb.getPessimisticLockService().getPessimisticLocksForDocument(documentNumber);
737     }
738 
739     /**
740      * @param pessimisticLocks the PessimisticLock objects to set
741      */
742     public void setPessimisticLocks(List<PessimisticLock> pessimisticLocks) {
743         this.pessimisticLocks = pessimisticLocks;
744     }
745 
746     /**
747      * @see org.kuali.rice.krad.document.Document#addPessimisticLock(org.kuali.rice.krad.document.authorization.PessimisticLock)
748      */
749     @Override
750     public void addPessimisticLock(PessimisticLock lock) {
751         pessimisticLocks.add(lock);
752     }
753 
754     /**
755      * @see org.kuali.rice.krad.document.Document#getLockClearingMethodNames()
756      */
757     @Override
758     @Deprecated
759     public List<String> getLockClearingMethodNames() {
760         return getLockClearningMethodNames();
761     }
762 
763     /**
764      * @see org.kuali.rice.krad.document.Document#getLockClearingMethodNames()
765      */
766     @Override
767     @Deprecated
768     public List<String> getLockClearningMethodNames() {
769         List<String> methodToCalls = new ArrayList<String>();
770         methodToCalls.add(KRADConstants.CLOSE_METHOD);
771         methodToCalls.add(KRADConstants.CANCEL_METHOD);
772         //        methodToCalls.add(RiceConstants.BLANKET_APPROVE_METHOD);
773         methodToCalls.add(KRADConstants.ROUTE_METHOD);
774         methodToCalls.add(KRADConstants.APPROVE_METHOD);
775         methodToCalls.add(KRADConstants.DISAPPROVE_METHOD);
776         methodToCalls.add(KRADConstants.ACKNOWLEDGE_METHOD);
777         return methodToCalls;
778     }
779 
780     /**
781      * This default implementation simply returns false to indicate that custom lock descriptors are not supported by
782      * DocumentBase. If custom lock
783      * descriptors are needed, the appropriate subclasses should override this method.
784      *
785      * @see org.kuali.rice.krad.document.Document#useCustomLockDescriptors()
786      */
787     @Override
788     public boolean useCustomLockDescriptors() {
789         return false;
790     }
791 
792     /**
793      * This default implementation just throws a PessimisticLockingException. Subclasses of DocumentBase that need
794      * support for custom lock descriptors
795      * should override this method.
796      *
797      * @see org.kuali.rice.krad.document.Document#getCustomLockDescriptor(org.kuali.rice.kim.api.identity.Person)
798      */
799     @Override
800     public String getCustomLockDescriptor(Person user) {
801         throw new PessimisticLockingException("Document " + getDocumentNumber() +
802                 " is using pessimistic locking with custom lock descriptors, but the document class has not overriden the getCustomLockDescriptor method");
803     }
804 
805     protected AttachmentService getAttachmentService() {
806         if (attachmentService == null) {
807             attachmentService = KRADServiceLocator.getAttachmentService();
808         }
809         return attachmentService;
810     }
811 
812     protected NoteService getNoteService() {
813         if (noteService == null) {
814             noteService = KRADServiceLocator.getNoteService();
815         }
816         return noteService;
817     }
818 
819     /**
820      * Overrides this OJB method to accept the no-longer-bound documentHeader reference
821      * and perform the refresh via services instead of via OJB.
822      *
823      * For any other property, it works as before.
824      *
825      * @deprecated This is a KNS/OJB-related method.  It should not be used on KRAD/JPA-based documents.
826      * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#refreshReferenceObject(java.lang.String)
827      */
828     @Deprecated
829     public void refreshReferenceObject(String referenceObjectName) {
830         if ( StringUtils.equals( referenceObjectName, "documentHeader" ) ) {
831             documentHeader = KRADServiceLocatorWeb.getDocumentHeaderService().getDocumentHeaderById(documentNumber);
832         } else {
833             super.refreshReferenceObject(referenceObjectName);
834         }
835     }
836 }