1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.document;
17
18 import org.apache.commons.lang.StringUtils;
19 import org.apache.log4j.Logger;
20 import org.kuali.rice.kew.api.KewApiConstants;
21 import org.kuali.rice.kew.api.KewApiServiceLocator;
22 import org.kuali.rice.kew.api.action.ActionType;
23 import org.kuali.rice.kew.api.exception.WorkflowException;
24 import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
25 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
26 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
27 import org.kuali.rice.kim.api.identity.Person;
28 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
29 import org.kuali.rice.krad.bo.AdHocRoutePerson;
30 import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
31 import org.kuali.rice.krad.bo.DocumentHeader;
32 import org.kuali.rice.krad.bo.Note;
33 import org.kuali.rice.krad.bo.PersistableBusinessObject;
34 import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
35 import org.kuali.rice.krad.datadictionary.DocumentEntry;
36 import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
37 import org.kuali.rice.krad.datadictionary.WorkflowProperties;
38 import org.kuali.rice.krad.document.authorization.PessimisticLock;
39 import org.kuali.rice.krad.exception.PessimisticLockingException;
40 import org.kuali.rice.krad.exception.ValidationException;
41 import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
42 import org.kuali.rice.krad.service.AttachmentService;
43 import org.kuali.rice.krad.service.DocumentSerializerService;
44 import org.kuali.rice.krad.service.KRADServiceLocator;
45 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
46 import org.kuali.rice.krad.service.NoteService;
47 import org.kuali.rice.krad.util.ErrorMessage;
48 import org.kuali.rice.krad.util.GlobalVariables;
49 import org.kuali.rice.krad.util.KRADConstants;
50 import org.kuali.rice.krad.util.KRADPropertyConstants;
51 import org.kuali.rice.krad.util.NoteType;
52 import org.kuali.rice.krad.util.ObjectUtils;
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.AutoPopulatingList;
61 import org.springframework.util.CollectionUtils;
62
63 import javax.persistence.CascadeType;
64 import javax.persistence.Column;
65 import javax.persistence.FetchType;
66 import javax.persistence.Id;
67 import javax.persistence.JoinColumn;
68 import javax.persistence.MappedSuperclass;
69 import javax.persistence.OneToMany;
70 import javax.persistence.OneToOne;
71 import javax.persistence.Transient;
72 import java.util.ArrayList;
73 import java.util.Iterator;
74 import java.util.List;
75 import java.util.Map;
76
77
78
79
80 @MappedSuperclass
81 public abstract class DocumentBase extends PersistableBusinessObjectBase implements Document {
82 private static final Logger LOG = Logger.getLogger(DocumentBase.class);
83
84 @Id @Column(name = "DOC_HDR_ID")
85 protected String documentNumber;
86 @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(
87 name = "DOC_HDR_ID", insertable = false, updatable = false)
88 protected DocumentHeader documentHeader;
89
90 @OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(
91 name = "DOC_HDR_ID", insertable = false, updatable = false)
92 private List<PessimisticLock> pessimisticLocks;
93
94 @Transient
95 private List<AdHocRoutePerson> adHocRoutePersons;
96 @Transient
97 private List<AdHocRouteWorkgroup> adHocRouteWorkgroups;
98 @Transient
99 private List<Note> notes;
100
101 private transient NoteService noteService;
102 private transient AttachmentService attachmentService;
103
104
105
106
107 public DocumentBase() {
108 try {
109
110 Class<? extends DocumentHeader> documentHeaderClass =
111 KRADServiceLocatorWeb.getDocumentHeaderService().getDocumentHeaderBaseClass();
112 setDocumentHeader(documentHeaderClass.newInstance());
113 pessimisticLocks = new ArrayList<PessimisticLock>();
114 adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
115 adHocRouteWorkgroups = new ArrayList<AdHocRouteWorkgroup>();
116 notes = new ArrayList<Note>();
117 } catch (IllegalAccessException e) {
118 throw new RuntimeException("Error instantiating DocumentHeader", e);
119 } catch (InstantiationException e) {
120 throw new RuntimeException("Error instantiating DocumentHeader", e);
121 }
122 }
123
124
125
126
127 public boolean getAllowsCopy() {
128
129 return false;
130 }
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 public String getDocumentTitle() {
148 String documentTypeLabel = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(
149 this.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()).getLabel();
150 if (null == documentTypeLabel) {
151 documentTypeLabel = "";
152 }
153
154 String description = this.getDocumentHeader().getDocumentDescription();
155 if (null == description) {
156 description = "";
157 }
158
159 return documentTypeLabel + " - " + description;
160 }
161
162
163
164
165
166
167 @Override
168 public void refresh() {
169 KRADServiceLocator.getPersistenceService().retrieveNonKeyFields(this);
170 }
171
172
173
174
175
176
177 public void refreshIfEmpty() {
178 if (null == this.getDocumentHeader()) {
179 this.refresh();
180 } else if (StringUtils.isEmpty(this.getDocumentHeader().getObjectId())) {
181 this.refresh();
182 }
183 }
184
185
186
187
188
189
190 @Override
191 public void refreshReferenceObject(String referenceObjectName) {
192 KRADServiceLocator.getPersistenceService().retrieveReferenceObject(this, referenceObjectName);
193 }
194
195
196
197
198 public void prepareForSave() {
199
200 }
201
202
203
204
205 public void processAfterRetrieve() {
206
207 }
208
209
210
211
212
213
214
215
216 public void doRouteLevelChange(DocumentRouteLevelChange levelChangeEvent) {
217
218 }
219
220
221
222
223 public void doActionTaken(ActionTakenEvent event) {
224 if ((KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(
225 this.getClass().getName()).getUseWorkflowPessimisticLocking()) && (!getNonLockingActionTakenCodes()
226 .contains(event.getActionTaken().getActionTaken().getCode()))) {
227 KRADServiceLocatorWeb.getPessimisticLockService().establishWorkflowPessimisticLocking(this);
228 }
229 }
230
231
232
233
234 public void afterActionTaken(ActionType performed, ActionTakenEvent event) {
235
236 }
237
238 protected List<String> getNonLockingActionTakenCodes() {
239 List<String> actionTakenStatusCodes = new ArrayList<String>();
240 actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_SAVED_CD);
241 actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_ACKNOWLEDGED_CD);
242 actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_FYI_CD);
243 actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_DENIED_CD);
244 actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_CANCELED_CD);
245 actionTakenStatusCodes.add(KewApiConstants.ACTION_TAKEN_LOG_DOCUMENT_ACTION_CD);
246 return actionTakenStatusCodes;
247 }
248
249
250
251
252
253
254
255 public void afterWorkflowEngineProcess(boolean successfullyProcessed) {
256 if (KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(
257 this.getClass().getName()).getUseWorkflowPessimisticLocking()) {
258 if (successfullyProcessed) {
259 KRADServiceLocatorWeb.getPessimisticLockService().releaseWorkflowPessimisticLocking(this);
260 }
261 }
262 }
263
264
265
266
267
268
269
270 public void beforeWorkflowEngineProcess() {
271
272 }
273
274
275
276
277
278
279 public List<String> getWorkflowEngineDocumentIdsToLock() {
280 return null;
281 }
282
283
284
285
286 public void toCopy() throws WorkflowException, IllegalStateException {
287 if (!this.getAllowsCopy()) {
288 throw new IllegalStateException(this.getClass().getName() + " does not support document-level copying");
289 }
290 String sourceDocumentHeaderId = getDocumentNumber();
291 setNewDocumentHeader();
292
293 getDocumentHeader().setDocumentTemplateNumber(sourceDocumentHeaderId);
294
295
296 this.notes.clear();
297 addCopyErrorDocumentNote("copied from document " + sourceDocumentHeaderId);
298 }
299
300
301
302
303
304
305 protected void setNewDocumentHeader() throws WorkflowException {
306 TransactionalDocument newDoc =
307 (TransactionalDocument) KRADServiceLocatorWeb.getDocumentService().getNewDocument(
308 getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
309 newDoc.getDocumentHeader().setDocumentDescription(getDocumentHeader().getDocumentDescription());
310 newDoc.getDocumentHeader().setOrganizationDocumentNumber(getDocumentHeader().getOrganizationDocumentNumber());
311
312 try {
313 ObjectUtils.setObjectPropertyDeep(this, KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber.getClass(),
314 newDoc.getDocumentNumber());
315 } catch (Exception e) {
316 LOG.error("Unable to set document number property in copied document " + e.getMessage(), e);
317 throw new RuntimeException("Unable to set document number property in copied document " + e.getMessage(),
318 e);
319 }
320
321
322 setDocumentHeader(newDoc.getDocumentHeader());
323 }
324
325
326
327
328
329
330 protected void addCopyErrorDocumentNote(String noteText) {
331 Note note = null;
332 try {
333 note = KRADServiceLocatorWeb.getDocumentService().createNoteFromDocument(this, noteText);
334 } catch (Exception e) {
335 logErrors();
336 throw new RuntimeException("Couldn't create note on copy or error", e);
337 }
338 addNote(note);
339 }
340
341
342
343
344 public String getXmlForRouteReport() {
345 prepareForSave();
346 populateDocumentForRouting();
347 return getDocumentHeader().getWorkflowDocument().getApplicationContent();
348 }
349
350
351
352
353 public void populateDocumentForRouting() {
354 getDocumentHeader().getWorkflowDocument().setApplicationContent(serializeDocumentToXml());
355 }
356
357
358
359
360 public String serializeDocumentToXml() {
361 DocumentSerializerService documentSerializerService = KRADServiceLocatorWeb.getDocumentSerializerService();
362 String xml = documentSerializerService.serializeDocumentToXmlForRouting(this);
363 return xml;
364 }
365
366
367
368
369
370
371
372 public KualiDocumentXmlMaterializer wrapDocumentWithMetadataForXmlSerialization() {
373 KualiTransactionalDocumentInformation transInfo = new KualiTransactionalDocumentInformation();
374 DocumentInitiator initiator = new DocumentInitiator();
375 String initiatorPrincipalId = getDocumentHeader().getWorkflowDocument().getDocument().getInitiatorPrincipalId();
376 Person initiatorUser = KimApiServiceLocator.getPersonService().getPerson(initiatorPrincipalId);
377 initiator.setPerson(initiatorUser);
378 transInfo.setDocumentInitiator(initiator);
379 KualiDocumentXmlMaterializer xmlWrapper = new KualiDocumentXmlMaterializer();
380 xmlWrapper.setDocument(this);
381 xmlWrapper.setKualiTransactionalDocumentInformation(transInfo);
382 return xmlWrapper;
383 }
384
385
386
387
388
389
390
391
392
393
394 public PropertySerializabilityEvaluator getDocumentPropertySerizabilityEvaluator() {
395 String docTypeName = getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
396 DocumentEntry documentEntry =
397 KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(docTypeName);
398 WorkflowProperties workflowProperties = documentEntry.getWorkflowProperties();
399 WorkflowAttributes workflowAttributes = documentEntry.getWorkflowAttributes();
400 return createPropertySerializabilityEvaluator(workflowProperties, workflowAttributes);
401 }
402
403 protected PropertySerializabilityEvaluator createPropertySerializabilityEvaluator(
404 WorkflowProperties workflowProperties, WorkflowAttributes workflowAttributes) {
405 if (workflowAttributes != null) {
406 return new AlwaysFalsePropertySerializabilityEvaluator();
407 }
408 if (workflowProperties == null) {
409 return new AlwaysTruePropertySerializibilityEvaluator();
410 }
411 PropertySerializabilityEvaluator evaluator = new BusinessObjectPropertySerializibilityEvaluator();
412 evaluator.initializeEvaluatorForDocument(this);
413 return evaluator;
414 }
415
416
417
418
419
420
421
422 public String getBasePathToDocumentDuringSerialization() {
423 return "document";
424 }
425
426
427
428
429 public DocumentHeader getDocumentHeader() {
430 return this.documentHeader;
431 }
432
433
434
435
436 public void setDocumentHeader(DocumentHeader documentHeader) {
437 this.documentHeader = documentHeader;
438 }
439
440
441
442
443 public String getDocumentNumber() {
444 return documentNumber;
445 }
446
447
448
449
450 public void setDocumentNumber(String documentNumber) {
451 this.documentNumber = documentNumber;
452 }
453
454
455
456
457 public List<AdHocRoutePerson> getAdHocRoutePersons() {
458 return adHocRoutePersons;
459 }
460
461
462
463
464 public void setAdHocRoutePersons(List<AdHocRoutePerson> adHocRoutePersons) {
465 this.adHocRoutePersons = adHocRoutePersons;
466 }
467
468
469
470
471 public List<AdHocRouteWorkgroup> getAdHocRouteWorkgroups() {
472 return adHocRouteWorkgroups;
473 }
474
475
476
477
478 public void setAdHocRouteWorkgroups(List<AdHocRouteWorkgroup> adHocRouteWorkgroups) {
479 this.adHocRouteWorkgroups = adHocRouteWorkgroups;
480 }
481
482 public void postProcessSave(KualiDocumentEvent event) {
483
484
485 }
486
487
488
489
490
491
492 public void prepareForSave(KualiDocumentEvent event) {
493
494 }
495
496 public void validateBusinessRules(KualiDocumentEvent event) {
497 if (GlobalVariables.getMessageMap().hasErrors()) {
498 logErrors();
499 throw new ValidationException("errors occured before business rule");
500 }
501
502
503 LOG.info("invoking rules engine on document " + getDocumentNumber());
504 boolean isValid = true;
505 isValid = KRADServiceLocatorWeb.getKualiRuleService().applyRules(event);
506
507
508 if (!isValid) {
509 logErrors();
510
511
512 throw new ValidationException("business rule evaluation failed");
513 } else if (GlobalVariables.getMessageMap().hasErrors()) {
514 logErrors();
515 throw new ValidationException(
516 "Unreported errors occured during business rule evaluation (rule developer needs to put meaningful error messages into global ErrorMap)");
517 }
518 LOG.debug("validation completed");
519
520 }
521
522
523
524
525 protected void logErrors() {
526 if (LOG.isInfoEnabled()) {
527 if (GlobalVariables.getMessageMap().hasErrors()) {
528
529 for (Iterator<Map.Entry<String, AutoPopulatingList<ErrorMessage>>> i =
530 GlobalVariables.getMessageMap().getAllPropertiesAndErrors().iterator(); i.hasNext(); ) {
531 Map.Entry<String, AutoPopulatingList<ErrorMessage>> e = i.next();
532
533 StringBuffer logMessage = new StringBuffer();
534 logMessage.append("[" + e.getKey() + "] ");
535 boolean first = true;
536
537 AutoPopulatingList<ErrorMessage> errorList = e.getValue();
538 for (Iterator<ErrorMessage> j = errorList.iterator(); j.hasNext(); ) {
539 ErrorMessage em = j.next();
540
541 if (first) {
542 first = false;
543 } else {
544 logMessage.append(";");
545 }
546 logMessage.append(em);
547 }
548
549 LOG.info(logMessage);
550 }
551 }
552 }
553 }
554
555
556
557
558
559
560 public List<KualiDocumentEvent> generateSaveEvents() {
561 return new ArrayList<KualiDocumentEvent>();
562 }
563
564
565
566
567 public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
568
569 }
570
571
572
573
574
575
576
577
578
579
580
581 @Override
582 public PersistableBusinessObject 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 @Override
657 protected void postLoad() {
658 super.postLoad();
659 refreshPessimisticLocks();
660 }
661
662
663
664
665 public List<PessimisticLock> getPessimisticLocks() {
666 return this.pessimisticLocks;
667 }
668
669
670
671
672
673 @Deprecated
674 public void refreshPessimisticLocks() {
675 this.pessimisticLocks.clear();
676 this.pessimisticLocks = KRADServiceLocatorWeb.getPessimisticLockService().getPessimisticLocksForDocument(
677 this.documentNumber);
678 }
679
680
681
682
683 public void setPessimisticLocks(List<PessimisticLock> pessimisticLocks) {
684 this.pessimisticLocks = pessimisticLocks;
685 }
686
687
688
689
690 public void addPessimisticLock(PessimisticLock lock) {
691 this.pessimisticLocks.add(lock);
692 }
693
694
695
696
697 public List<String> getLockClearningMethodNames() {
698 List<String> methodToCalls = new ArrayList<String>();
699 methodToCalls.add(KRADConstants.CLOSE_METHOD);
700 methodToCalls.add(KRADConstants.CANCEL_METHOD);
701
702 methodToCalls.add(KRADConstants.ROUTE_METHOD);
703 methodToCalls.add(KRADConstants.APPROVE_METHOD);
704 methodToCalls.add(KRADConstants.DISAPPROVE_METHOD);
705 methodToCalls.add(KRADConstants.ACKNOWLEDGE_METHOD);
706 return methodToCalls;
707 }
708
709
710
711
712
713
714
715
716 public boolean useCustomLockDescriptors() {
717 return false;
718 }
719
720
721
722
723
724
725
726
727 public String getCustomLockDescriptor(Person user) {
728 throw new PessimisticLockingException("Document " + getDocumentNumber() +
729 " is using pessimistic locking with custom lock descriptors, but the document class has not overriden the getCustomLockDescriptor method");
730 }
731
732 protected AttachmentService getAttachmentService() {
733 if (attachmentService == null) {
734 attachmentService = KRADServiceLocator.getAttachmentService();
735 }
736 return attachmentService;
737 }
738
739 protected NoteService getNoteService() {
740 if (noteService == null) {
741 noteService = KRADServiceLocator.getNoteService();
742 }
743 return noteService;
744 }
745 }