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