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