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