1 package org.kuali.rice.krad.document;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5 import java.util.List;
6 import java.util.Map;
7
8 import javax.persistence.Column;
9 import javax.persistence.Id;
10 import javax.persistence.MappedSuperclass;
11 import javax.persistence.PostLoad;
12 import javax.persistence.PostRemove;
13 import javax.persistence.PrePersist;
14 import javax.persistence.Transient;
15
16 import org.apache.commons.lang.StringUtils;
17 import org.apache.log4j.Logger;
18 import org.kuali.rice.core.api.mo.common.GloballyUnique;
19 import org.kuali.rice.kew.api.KewApiConstants;
20 import org.kuali.rice.kew.api.KewApiServiceLocator;
21 import org.kuali.rice.kew.api.WorkflowDocument;
22 import org.kuali.rice.kew.api.action.ActionRequest;
23 import org.kuali.rice.kew.api.action.ActionType;
24 import org.kuali.rice.kew.api.exception.WorkflowException;
25 import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
26 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
27 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
28 import org.kuali.rice.kim.api.identity.Person;
29 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
30 import org.kuali.rice.krad.UserSessionUtils;
31 import org.kuali.rice.krad.bo.AdHocRoutePerson;
32 import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
33 import org.kuali.rice.krad.bo.DocumentHeader;
34 import org.kuali.rice.krad.bo.Note;
35 import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
36 import org.kuali.rice.krad.datadictionary.DocumentEntry;
37 import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
38 import org.kuali.rice.krad.datadictionary.WorkflowProperties;
39 import org.kuali.rice.krad.document.authorization.PessimisticLock;
40 import org.kuali.rice.krad.exception.PessimisticLockingException;
41 import org.kuali.rice.krad.exception.ValidationException;
42 import org.kuali.rice.krad.rules.rule.event.DocumentEvent;
43 import org.kuali.rice.krad.service.AttachmentService;
44 import org.kuali.rice.krad.service.DocumentSerializerService;
45 import org.kuali.rice.krad.service.KRADServiceLocator;
46 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
47 import org.kuali.rice.krad.service.NoteService;
48 import org.kuali.rice.krad.util.ErrorMessage;
49 import org.kuali.rice.krad.util.GlobalVariables;
50 import org.kuali.rice.krad.util.KRADConstants;
51 import org.kuali.rice.krad.util.KRADPropertyConstants;
52 import org.kuali.rice.krad.util.NoteType;
53 import org.kuali.rice.krad.util.documentserializer.AlwaysFalsePropertySerializabilityEvaluator;
54 import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
55 import org.kuali.rice.krad.util.documentserializer.BusinessObjectPropertySerializibilityEvaluator;
56 import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
57 import org.kuali.rice.krad.workflow.DocumentInitiator;
58 import org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer;
59 import org.kuali.rice.krad.workflow.KualiTransactionalDocumentInformation;
60 import org.springframework.util.CollectionUtils;
61
62
63
64
65
66
67
68
69 @MappedSuperclass
70 public abstract class DocumentBase extends PersistableBusinessObjectBaseAdapter implements Document {
71 private static final long serialVersionUID = 8530945307802647664L;
72 private static final Logger LOG = Logger.getLogger(DocumentBase.class);
73
74 @Id
75 @Column(name = "DOC_HDR_ID",length=14)
76 protected String documentNumber;
77
78 @Transient
79 protected DocumentHeader documentHeader;
80
81 @Transient
82 protected List<PessimisticLock> pessimisticLocks;
83
84 @Transient
85 protected List<AdHocRoutePerson> adHocRoutePersons;
86 @Transient
87 protected List<AdHocRouteWorkgroup> adHocRouteWorkgroups;
88 @Transient
89 protected List<Note> notes;
90 @Transient
91 private String superUserAnnotation = "";
92
93 private transient NoteService noteService;
94 private transient AttachmentService attachmentService;
95
96
97
98
99 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
109
110 @Override
111 public boolean getAllowsCopy() {
112 return false;
113 }
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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
148
149 @Override
150 public void prepareForSave() {
151
152 }
153
154
155
156
157 @Override
158 public void processAfterRetrieve() {
159
160 }
161
162
163
164
165
166
167
168
169 @Override
170 public void doRouteLevelChange(DocumentRouteLevelChange levelChangeEvent) {
171
172 }
173
174
175
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
188
189 @Override
190 public void afterActionTaken(ActionType performed, ActionTakenEvent event) {
191
192 }
193
194
195
196
197
198
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
213
214
215
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
229
230
231
232
233 @Override
234 public void beforeWorkflowEngineProcess() {
235
236 }
237
238
239
240
241
242
243 @Override
244 public List<String> getWorkflowEngineDocumentIdsToLock() {
245 return null;
246 }
247
248
249
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
259 this.notes.clear();
260 addCopyErrorDocumentNote("copied from document " + sourceDocumentHeaderId);
261 }
262
263
264
265
266
267
268 protected void setNewDocumentHeader() throws WorkflowException {
269
270 Person user = GlobalVariables.getUserSession().getPerson();
271 WorkflowDocument sourceWorkflowDocument
272 = KRADServiceLocatorWeb.getWorkflowDocumentService().loadWorkflowDocument(getDocumentNumber(), user);
273 String sourceDocumentTypeName = sourceWorkflowDocument.getDocumentTypeName();
274
275
276 WorkflowDocument workflowDocument
277 = KRADServiceLocatorWeb.getWorkflowDocumentService().createWorkflowDocument(sourceDocumentTypeName, user);
278 UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDocument);
279
280
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
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
297 setDocumentHeader(newDocument.getDocumentHeader());
298 }
299
300
301
302
303
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
318
319 @Override
320 public String getXmlForRouteReport() {
321 prepareForSave();
322 populateDocumentForRouting();
323 return getDocumentHeader().getWorkflowDocument().getApplicationContent();
324 }
325
326
327
328
329 @Override
330 public void populateDocumentForRouting() {
331 getDocumentHeader().getWorkflowDocument().setApplicationContent(serializeDocumentToXml());
332 }
333
334
335
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
346
347
348
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
366
367
368
369
370
371
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
398
399
400
401
402 @Override
403 public String getBasePathToDocumentDuringSerialization() {
404 return "document";
405 }
406
407
408
409
410 @Override
411 public DocumentHeader getDocumentHeader() {
412
413
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
423
424 @Override
425 public void setDocumentHeader(DocumentHeader documentHeader) {
426 this.documentHeader = documentHeader;
427 }
428
429
430
431
432 @Override
433 public String getDocumentNumber() {
434 return documentNumber;
435 }
436
437
438
439
440 @Override
441 public void setDocumentNumber(String documentNumber) {
442 this.documentNumber = documentNumber;
443 }
444
445
446
447
448 @Override
449 public List<AdHocRoutePerson> getAdHocRoutePersons() {
450 return adHocRoutePersons;
451 }
452
453
454
455
456 @Override
457 public void setAdHocRoutePersons(List<AdHocRoutePerson> adHocRoutePersons) {
458 this.adHocRoutePersons = adHocRoutePersons;
459 }
460
461
462
463
464 @Override
465 public List<AdHocRouteWorkgroup> getAdHocRouteWorkgroups() {
466 return adHocRouteWorkgroups;
467 }
468
469
470
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
480
481 }
482
483
484
485
486
487
488 @Override
489 public void prepareForSave(DocumentEvent event) {
490
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
501 LOG.info("invoking rules engine on document " + getDocumentNumber());
502 boolean isValid = true;
503 isValid = KRADServiceLocatorWeb.getKualiRuleService().applyRules(event);
504
505
506 if (!isValid) {
507 logErrors();
508
509
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
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
555
556
557
558 @Override
559 public List<DocumentEvent> generateSaveEvents() {
560 return new ArrayList<DocumentEvent>();
561 }
562
563
564
565
566 @Override
567 public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
568
569 }
570
571
572
573
574
575
576
577
578
579
580
581 @Override
582 public GloballyUnique getNoteTarget() {
583 return getDocumentHeader();
584 }
585
586
587
588
589
590
591
592
593
594
595
596
597 @Override
598 public NoteType getNoteType() {
599 return NoteType.DOCUMENT_HEADER;
600 }
601
602
603
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
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
626
627 @Override
628 public Note getNote(int index) {
629 return notes.get(index);
630 }
631
632
633
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
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
658
659 @Override
660 public List<ActionRequest> getActionRequests() {
661 return KewApiServiceLocator.getWorkflowDocumentService().getPendingActionRequests(getDocumentNumber());
662 }
663
664
665
666
667 @Override
668 public String getSuperUserAnnotation() {
669 return superUserAnnotation;
670 }
671
672
673
674
675 @Override
676 public void setSuperUserAnnotation(String superUserAnnotation) {
677 this.superUserAnnotation = superUserAnnotation;
678 }
679
680
681
682
683
684
685 @PostLoad
686 protected void postLoad() {
687 documentHeader = KRADServiceLocatorWeb.getDocumentHeaderService().getDocumentHeaderById(documentNumber);
688 refreshPessimisticLocks();
689 }
690
691
692
693
694 @PrePersist
695 protected void prePersist() {
696 super.prePersist();
697
698
699
700
701
702
703 }
704
705
706
707
708
709
710
711 @PostRemove
712 protected void postRemove() {
713 KRADServiceLocatorWeb.getDocumentHeaderService().deleteDocumentHeader(getDocumentHeader());
714 }
715
716
717
718
719 @Override
720 public List<PessimisticLock> getPessimisticLocks() {
721 return pessimisticLocks;
722 }
723
724
725
726
727 @Override
728 public void refreshPessimisticLocks() {
729 pessimisticLocks = KRADServiceLocatorWeb.getPessimisticLockService().getPessimisticLocksForDocument(documentNumber);
730 }
731
732
733
734
735 public void setPessimisticLocks(List<PessimisticLock> pessimisticLocks) {
736 this.pessimisticLocks = pessimisticLocks;
737 }
738
739
740
741
742 @Override
743 public void addPessimisticLock(PessimisticLock lock) {
744 pessimisticLocks.add(lock);
745 }
746
747
748
749
750 @Override
751 @Deprecated
752 public List<String> getLockClearingMethodNames() {
753 return getLockClearningMethodNames();
754 }
755
756
757
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
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
775
776
777
778
779
780 @Override
781 public boolean useCustomLockDescriptors() {
782 return false;
783 }
784
785
786
787
788
789
790
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
814
815
816
817
818
819
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 }