001: /*
002: * Copyright 2005-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.core.document;
017:
018: import java.util.ArrayList;
019: import java.util.Iterator;
020: import java.util.LinkedHashMap;
021: import java.util.List;
022: import java.util.Map;
023:
024: import org.apache.commons.lang.StringUtils;
025: import org.apache.log4j.Logger;
026: import org.kuali.RiceConstants;
027: import org.kuali.RicePropertyConstants;
028: import org.kuali.core.bo.DocumentHeader;
029: import org.kuali.core.bo.Note;
030: import org.kuali.core.bo.PersistableBusinessObject;
031: import org.kuali.core.bo.PersistableBusinessObjectBase;
032: import org.kuali.core.bo.user.AuthenticationUserId;
033: import org.kuali.core.bo.user.UniversalUser;
034: import org.kuali.core.exceptions.UserNotFoundException;
035: import org.kuali.core.exceptions.ValidationException;
036: import org.kuali.core.rule.event.KualiDocumentEvent;
037: import org.kuali.core.util.ErrorMessage;
038: import org.kuali.core.util.GlobalVariables;
039: import org.kuali.core.util.ObjectUtils;
040: import org.kuali.core.util.TypeUtils;
041: import org.kuali.core.util.TypedArrayList;
042: import org.kuali.core.workflow.DocumentInitiator;
043: import org.kuali.core.workflow.KualiDocumentXmlMaterializer;
044: import org.kuali.core.workflow.KualiTransactionalDocumentInformation;
045: import org.kuali.rice.KNSServiceLocator;
046:
047: import edu.iu.uis.eden.clientapp.vo.ActionTakenEventVO;
048: import edu.iu.uis.eden.clientapp.vo.DocumentRouteLevelChangeVO;
049: import edu.iu.uis.eden.clientapp.vo.DocumentRouteStatusChangeVO;
050: import edu.iu.uis.eden.exception.WorkflowException;
051:
052: /**
053: * @see Document
054: */
055: public abstract class DocumentBase extends
056: PersistableBusinessObjectBase implements Document {
057: private static final Logger LOG = Logger
058: .getLogger(DocumentBase.class);
059:
060: protected String documentNumber;
061: protected DocumentHeader documentHeader;
062: protected transient PersistableBusinessObject documentBusinessObject; //here for reflection
063:
064: private List adHocRoutePersons;
065: private List adHocRouteWorkgroups;
066:
067: /**
068: * Constructs a DocumentBase.java.
069: */
070: public DocumentBase() {
071: setDocumentHeader(new DocumentHeader());
072: adHocRoutePersons = new ArrayList();
073: adHocRouteWorkgroups = new ArrayList();
074: }
075:
076: /**
077: * @see org.kuali.core.document.Document#getAllowsCopy()
078: */
079: public boolean getAllowsCopy() {
080: // TODO Auto-generated method stub
081: return false;
082: }
083:
084: /**
085: * This is the default document title implementation. It concatenates the document's data dictionary file label attribute and
086: * the document's document header description together. This title is used to populate workflow and will show up in document
087: * search results and user action lists.
088: *
089: * @see org.kuali.core.document.Document#getDocumentTitle()
090: */
091: public String getDocumentTitle() {
092: String documentTypeLabel = KNSServiceLocator
093: .getDataDictionaryService().getDataDictionary()
094: .getDocumentEntry(
095: this .getDocumentHeader().getWorkflowDocument()
096: .getDocumentType()).getLabel();
097: if (null == documentTypeLabel) {
098: documentTypeLabel = "";
099: }
100:
101: String description = this .getDocumentHeader()
102: .getFinancialDocumentDescription();
103: if (null == description) {
104: description = "";
105: }
106:
107: return documentTypeLabel + " - " + description;
108: }
109:
110: /**
111: * Uses the persistence service's implementation of OJB's retrieveNonKey() fields method.
112: *
113: * @see org.kuali.core.bo.BusinessObject#refresh()
114: */
115: public void refresh() {
116: KNSServiceLocator.getPersistenceService().retrieveNonKeyFields(
117: this );
118: }
119:
120: /**
121: * Checks to see if the objectId value is empty. If so, it will try to refresh the object from the DB.
122: *
123: * @see org.kuali.core.document.Document#refreshIfEmpty()
124: */
125: public void refreshIfEmpty() {
126: if (null == this .getDocumentHeader()) {
127: this .refresh();
128: } else if (StringUtils.isEmpty(this .getDocumentHeader()
129: .getObjectId())) {
130: this .refresh();
131: }
132: }
133:
134: /**
135: * Uses the persistence service to retrieve a reference object of a parent.
136: *
137: * @see org.kuali.core.document.Document#refreshReferenceObject(java.lang.String)
138: */
139: public void refreshReferenceObject(String referenceObjectName) {
140: KNSServiceLocator.getPersistenceService()
141: .retrieveReferenceObject(this , referenceObjectName);
142: }
143:
144: /**
145: * @param fieldValues
146: * @return consistently-formatted String containing the given fieldnames and their values
147: */
148: protected String toStringBuilder(LinkedHashMap fieldValues) {
149: String built = null;
150: String className = StringUtils.uncapitalize(StringUtils
151: .substringAfterLast(this .getClass().getName(), "."));
152:
153: if ((fieldValues == null) || fieldValues.isEmpty()) {
154: built = super .toString();
155: } else {
156:
157: StringBuffer prefix = new StringBuffer(className);
158: StringBuffer suffix = new StringBuffer("=");
159:
160: prefix.append("documentHeaderId");
161: suffix.append(this .getDocumentNumber());
162:
163: prefix.append("(");
164: suffix.append("(");
165: for (Iterator i = fieldValues.entrySet().iterator(); i
166: .hasNext();) {
167: Map.Entry e = (Map.Entry) i.next();
168:
169: String fieldName = e.getKey().toString();
170: Object fieldValue = e.getValue();
171:
172: String fieldValueString = String.valueOf(e.getValue()); // prevent NullPointerException;
173:
174: if ((fieldValue == null)
175: || TypeUtils
176: .isSimpleType(fieldValue.getClass())) {
177: prefix.append(fieldName);
178: suffix.append(fieldValueString);
179: } else {
180: prefix.append("{");
181: prefix.append(fieldName);
182: prefix.append("}");
183:
184: suffix.append("{");
185: suffix.append(fieldValueString);
186: suffix.append("}");
187: }
188:
189: if (i.hasNext()) {
190: prefix.append(",");
191: suffix.append(",");
192: }
193: }
194: prefix.append(")");
195: suffix.append(")");
196:
197: built = prefix.toString() + suffix.toString();
198: }
199:
200: return built;
201: }
202:
203: /**
204: * @return Map containing the fieldValues of the key fields for this class, indexed by fieldName
205: */
206: protected LinkedHashMap toStringMapper() {
207: LinkedHashMap m = new LinkedHashMap();
208:
209: m.put("documentNumber", getDocumentNumber());
210: m.put("versionNumber", getVersionNumber());
211:
212: return m;
213: }
214:
215: /**
216: * @see java.lang.Object#toString()
217: */
218: final public String toString() {
219: return toStringBuilder(toStringMapper());
220: }
221:
222: /**
223: * If the document has a total amount, call method on document to get the total and set in doc header.
224: *
225: * @see org.kuali.core.document.Document#prepareForSave()
226: */
227: public void prepareForSave() {
228: if (this instanceof AmountTotaling) {
229: getDocumentHeader().setFinancialDocumentTotalAmount(
230: ((AmountTotaling) this ).getTotalDollarAmount());
231: }
232: }
233:
234: /**
235: * This is the default implementation which ensures that document note attachment references are loaded.
236: *
237: * @see org.kuali.core.document.Document#processAfterRetrieve()
238: */
239: public void processAfterRetrieve() {
240: // KULRNE-5692 - force a refresh of the attachments
241: // they are not (non-updateable) references and don't seem to update properly upon load
242: DocumentHeader dh = getDocumentHeader();
243: if (dh != null) {
244: List<Note> notes = dh.getBoNotes();
245: if (notes != null) {
246: for (Note note : notes) {
247: note.refreshReferenceObject("attachment");
248: }
249: }
250: }
251: }
252:
253: /**
254: * This is the default implementation which checks for a different workflow statuses, and updates the Kuali status accordingly.
255: *
256: * @see org.kuali.core.document.Document#handleRouteStatusChange()
257: */
258: public void handleRouteStatusChange() {
259: if (getDocumentHeader().getWorkflowDocument().stateIsCanceled()) {
260: getDocumentHeader().setFinancialDocumentStatusCode(
261: RiceConstants.DocumentStatusCodes.CANCELLED);
262: } else if (getDocumentHeader().getWorkflowDocument()
263: .stateIsEnroute()) {
264: getDocumentHeader().setFinancialDocumentStatusCode(
265: RiceConstants.DocumentStatusCodes.ENROUTE);
266: }
267: if (getDocumentHeader().getWorkflowDocument()
268: .stateIsDisapproved()) {
269: getDocumentHeader().setFinancialDocumentStatusCode(
270: RiceConstants.DocumentStatusCodes.DISAPPROVED);
271: }
272: if (getDocumentHeader().getWorkflowDocument()
273: .stateIsProcessed()) {
274: getDocumentHeader().setFinancialDocumentStatusCode(
275: RiceConstants.DocumentStatusCodes.APPROVED);
276: }
277: LOG.info("Status is: "
278: + getDocumentHeader().getFinancialDocumentStatusCode());
279: }
280:
281: /**
282: * The the default implementation for RouteLevelChange does nothing, but is meant to provide a hook for documents to implement
283: * for other needs.
284: *
285: * @see org.kuali.core.document.Document#handleRouteLevelChange(edu.iu.uis.eden.clientapp.vo.DocumentRouteLevelChangeVO)
286: */
287: public void handleRouteLevelChange(
288: DocumentRouteLevelChangeVO levelChangeEvent) {
289: // do nothing
290: }
291:
292: /**
293: * @see org.kuali.core.document.Document#doActionTaken(edu.iu.uis.eden.clientapp.vo.ActionTakenEventVO)
294: */
295: public void doActionTaken(ActionTakenEventVO event) {
296: // do nothing
297: }
298:
299: /**
300: * @see org.kuali.core.document.Copyable#toCopy()
301: */
302: public void toCopy() throws WorkflowException,
303: IllegalStateException {
304: if (!this .getAllowsCopy()) {
305: throw new IllegalStateException(this .getClass().getName()
306: + " does not support document-level copying");
307: }
308: String sourceDocumentHeaderId = getDocumentNumber();
309: setNewDocumentHeader();
310:
311: getDocumentBusinessObject().getBoNotes();
312:
313: getDocumentHeader().setFinancialDocumentTemplateNumber(
314: sourceDocumentHeaderId);
315:
316: addCopyErrorDocumentNote("copied from document "
317: + sourceDocumentHeaderId);
318: }
319:
320: /**
321: * Gets a new document header for this documents type and sets in the document instance.
322: *
323: * @throws WorkflowException
324: */
325: protected void setNewDocumentHeader() throws WorkflowException {
326: TransactionalDocument newDoc = (TransactionalDocument) KNSServiceLocator
327: .getDocumentService().getNewDocument(
328: getDocumentHeader().getWorkflowDocument()
329: .getDocumentType());
330: newDoc.getDocumentHeader().setFinancialDocumentDescription(
331: getDocumentHeader().getFinancialDocumentDescription());
332: newDoc.getDocumentHeader().setOrganizationDocumentNumber(
333: getDocumentHeader().getOrganizationDocumentNumber());
334:
335: try {
336: ObjectUtils.setObjectPropertyDeep(this ,
337: RicePropertyConstants.DOCUMENT_NUMBER,
338: documentNumber.getClass(), newDoc
339: .getDocumentNumber());
340: } catch (Exception e) {
341: LOG
342: .error("Unable to set document number property in copied document "
343: + e.getMessage());
344: throw new RuntimeException(
345: "Unable to set document number property in copied document "
346: + e.getMessage());
347: }
348:
349: // replace current documentHeader with new documentHeader
350: setDocumentHeader(newDoc.getDocumentHeader());
351: }
352:
353: /**
354: * Adds a note to the document indicating it was created by a copy or error correction.
355: *
356: * @param noteText - text for note
357: */
358: protected void addCopyErrorDocumentNote(String noteText) {
359: Note note = null;
360: try {
361: note = KNSServiceLocator.getDocumentService()
362: .createNoteFromDocument(this , noteText);
363: } catch (Exception e) {
364: logErrors();
365: throw new RuntimeException(
366: "Couldn't create note on copy or error");
367: }
368: KNSServiceLocator.getDocumentService().addNoteToDocument(this ,
369: note);
370: }
371:
372: /**
373: * @see org.kuali.core.document.Document#getXmlForRouteReport()
374: */
375: public String getXmlForRouteReport() {
376: prepareForSave();
377: populateDocumentForRouting();
378: return getDocumentHeader().getWorkflowDocument()
379: .getApplicationContent();
380: }
381:
382: /**
383: * @see org.kuali.core.document.Document#populateDocumentForRouting()
384: */
385: public void populateDocumentForRouting() {
386: documentHeader.getWorkflowDocument().setApplicationContent(
387: serializeDocumentToXml());
388: }
389:
390: public String serializeDocumentToXml() {
391: KualiTransactionalDocumentInformation transInfo = new KualiTransactionalDocumentInformation();
392: DocumentInitiator initiatior = new DocumentInitiator();
393: String initiatorNetworkId = documentHeader
394: .getWorkflowDocument().getInitiatorNetworkId();
395: try {
396: UniversalUser initiatorUser = KNSServiceLocator
397: .getUniversalUserService()
398: .getUniversalUser(
399: new AuthenticationUserId(initiatorNetworkId));
400: initiatorUser.getModuleUsers(); // init the module users map for serialization
401: initiatior.setUniversalUser(initiatorUser);
402: } catch (UserNotFoundException e) {
403: throw new RuntimeException(e);
404: }
405: transInfo.setDocumentInitiator(initiatior);
406: KualiDocumentXmlMaterializer xmlWrapper = new KualiDocumentXmlMaterializer();
407: xmlWrapper
408: .setDocument(getDocumentRepresentationForSerialization());
409: xmlWrapper.setKualiTransactionalDocumentInformation(transInfo);
410: String xml = KNSServiceLocator.getXmlObjectSerializerService()
411: .toXml(xmlWrapper);
412: return xml;
413: }
414:
415: /**
416: * This method was added because of performance problems with the default workflow xml serialization strategy.
417: * This allows individual "big" document implementations to defer to a service that can be overriden for translation
418: * of the real document into a much smaller object structure for serialization.
419: *
420: * @return the Document instance that should be used to generate the xml for workfow
421: */
422: protected Document getDocumentRepresentationForSerialization() {
423: return this ;
424: }
425:
426: /**
427: * @see org.kuali.core.document.Document#getDocumentHeader()
428: */
429: public DocumentHeader getDocumentHeader() {
430: return this .documentHeader;
431: }
432:
433: /**
434: * @see org.kuali.core.document.Document#setDocumentHeader(org.kuali.core.document.DocumentHeader)
435: */
436: public void setDocumentHeader(DocumentHeader documentHeader) {
437: this .documentHeader = documentHeader;
438: }
439:
440: /**
441: * @see org.kuali.core.document.Document#getDocumentNumber()
442: */
443: public String getDocumentNumber() {
444: return documentNumber;
445: }
446:
447: /**
448: * @see org.kuali.core.document.Document#setDocumentNumber(java.lang.String)
449: */
450: public void setDocumentNumber(String documentNumber) {
451: this .documentNumber = documentNumber;
452: }
453:
454: /**
455: * @see org.kuali.core.document.Document#getAdHocRoutePersons()
456: */
457: public List getAdHocRoutePersons() {
458: return adHocRoutePersons;
459: }
460:
461: /**
462: * @see org.kuali.core.document.Document#setAdHocRoutePersons(java.util.List)
463: */
464: public void setAdHocRoutePersons(List adHocRoutePersons) {
465: this .adHocRoutePersons = adHocRoutePersons;
466: }
467:
468: /**
469: * @see org.kuali.core.document.Document#getAdHocRouteWorkgroups()
470: */
471: public List getAdHocRouteWorkgroups() {
472: return adHocRouteWorkgroups;
473: }
474:
475: /**
476: * @see org.kuali.core.document.Document#setAdHocRouteWorkgroups(java.util.List)
477: */
478: public void setAdHocRouteWorkgroups(List adHocRouteWorkgroups) {
479: this .adHocRouteWorkgroups = adHocRouteWorkgroups;
480: }
481:
482: public void postProcessSave(KualiDocumentEvent event) {
483: // TODO Auto-generated method stub
484:
485: }
486:
487: /**
488: * Override this method with implementation specific prepareForSave logic
489: *
490: * @see org.kuali.core.document.Document#prepareForSave(org.kuali.core.rule.event.KualiDocumentEvent)
491: */
492: public void prepareForSave(KualiDocumentEvent event) {
493:
494: }
495:
496: public void validateBusinessRules(KualiDocumentEvent event) {
497: if (!GlobalVariables.getErrorMap().isEmpty()) {
498: logErrors();
499: throw new ValidationException(
500: "errors occured before business rule");
501: }
502:
503: // perform validation against rules engine
504: LOG.info("invoking rules engine on document "
505: + getDocumentNumber());
506: boolean isValid = true;
507: isValid = KNSServiceLocator.getKualiRuleService().applyRules(
508: event);
509:
510: // check to see if the br eval passed or failed
511: if (!isValid) {
512: logErrors();
513: // TODO: better error handling at the lower level and a better error message are
514: // needed here
515: throw new ValidationException(
516: "business rule evaluation failed");
517: } else if (!GlobalVariables.getErrorMap().isEmpty()) {
518: logErrors();
519: throw new ValidationException(
520: "Unreported errors occured during business rule evaluation (rule developer needs to put meaningful error messages into global ErrorMap)");
521: }
522: LOG.debug("validation completed");
523:
524: }
525:
526: /**
527: * This method logs errors.
528: */
529: protected void logErrors() {
530: if (!GlobalVariables.getErrorMap().isEmpty()) {
531:
532: for (Iterator i = GlobalVariables.getErrorMap().entrySet()
533: .iterator(); i.hasNext();) {
534: Map.Entry e = (Map.Entry) i.next();
535:
536: StringBuffer logMessage = new StringBuffer();
537: logMessage.append("[" + e.getKey() + "] ");
538: boolean first = true;
539:
540: TypedArrayList errorList = (TypedArrayList) e
541: .getValue();
542: for (Iterator j = errorList.iterator(); j.hasNext();) {
543: ErrorMessage em = (ErrorMessage) j.next();
544:
545: if (first) {
546: first = false;
547: } else {
548: logMessage.append(";");
549: }
550: logMessage.append(em);
551: }
552:
553: LOG.error(logMessage);
554: }
555: }
556: }
557:
558: /**
559: * Hook for override
560: *
561: * @see org.kuali.core.document.Document#generateSaveEvents()
562: */
563: public List generateSaveEvents() {
564: return new ArrayList();
565: }
566:
567: public void doRouteStatusChange(
568: DocumentRouteStatusChangeVO statusChangeEvent)
569: throws Exception {
570: }
571:
572: /**
573: * Gets the documentBusinessObject attribute.
574: *
575: * @return Returns the documentBusinessObject.
576: */
577: public PersistableBusinessObject getDocumentBusinessObject() {
578: if (documentBusinessObject == null) {
579: documentBusinessObject = this;
580: }
581: return documentBusinessObject;
582:
583: }
584: }
|