001/**
002 * Copyright 2005-2014 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 org.apache.commons.lang.StringUtils;
032import org.apache.log4j.Logger;
033import org.kuali.rice.core.api.mo.common.GloballyUnique;
034import org.kuali.rice.kew.api.KewApiConstants;
035import org.kuali.rice.kew.api.KewApiServiceLocator;
036import org.kuali.rice.kew.api.WorkflowDocument;
037import org.kuali.rice.kew.api.action.ActionRequest;
038import org.kuali.rice.kew.api.action.ActionType;
039import org.kuali.rice.kew.api.exception.WorkflowException;
040import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
041import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
042import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
043import org.kuali.rice.kim.api.identity.Person;
044import org.kuali.rice.kim.api.services.KimApiServiceLocator;
045import org.kuali.rice.krad.UserSessionUtils;
046import org.kuali.rice.krad.bo.AdHocRoutePerson;
047import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
048import org.kuali.rice.krad.bo.DocumentHeader;
049import org.kuali.rice.krad.bo.Note;
050import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
051import org.kuali.rice.krad.datadictionary.DocumentEntry;
052import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
053import org.kuali.rice.krad.datadictionary.WorkflowProperties;
054import org.kuali.rice.krad.document.authorization.PessimisticLock;
055import org.kuali.rice.krad.exception.PessimisticLockingException;
056import org.kuali.rice.krad.exception.ValidationException;
057import org.kuali.rice.krad.rules.rule.event.DocumentEvent;
058import org.kuali.rice.krad.service.AttachmentService;
059import org.kuali.rice.krad.service.DocumentSerializerService;
060import org.kuali.rice.krad.service.KRADServiceLocator;
061import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
062import org.kuali.rice.krad.service.NoteService;
063import org.kuali.rice.krad.util.ErrorMessage;
064import org.kuali.rice.krad.util.GlobalVariables;
065import org.kuali.rice.krad.util.KRADConstants;
066import org.kuali.rice.krad.util.KRADPropertyConstants;
067import org.kuali.rice.krad.util.NoteType;
068import org.kuali.rice.krad.util.documentserializer.AlwaysFalsePropertySerializabilityEvaluator;
069import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
070import org.kuali.rice.krad.util.documentserializer.BusinessObjectPropertySerializibilityEvaluator;
071import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
072import org.kuali.rice.krad.workflow.DocumentInitiator;
073import org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer;
074import org.kuali.rice.krad.workflow.KualiTransactionalDocumentInformation;
075import org.springframework.util.CollectionUtils;
076
077/**
078 * @see Document
079 */
080@MappedSuperclass
081public abstract class DocumentBase extends PersistableBusinessObjectBaseAdapter implements Document {
082    private static final long serialVersionUID = 8530945307802647664L;
083    private static final Logger LOG = Logger.getLogger(DocumentBase.class);
084
085    @Id
086    @Column(name = "DOC_HDR_ID",length=14)
087    protected String documentNumber;
088
089    @Transient
090    protected DocumentHeader documentHeader;
091
092    @Transient
093    protected List<PessimisticLock> pessimisticLocks;
094
095    @Transient
096    protected List<AdHocRoutePerson> adHocRoutePersons;
097    @Transient
098    protected List<AdHocRouteWorkgroup> adHocRouteWorkgroups;
099    @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}