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.io.IOException;
019: import java.io.StringReader;
020: import java.util.LinkedHashMap;
021: import java.util.Properties;
022:
023: import javax.xml.parsers.DocumentBuilder;
024: import javax.xml.parsers.DocumentBuilderFactory;
025: import javax.xml.parsers.ParserConfigurationException;
026:
027: import org.apache.commons.lang.StringUtils;
028: import org.kuali.RiceConstants;
029: import org.kuali.RiceKeyConstants;
030: import org.kuali.core.bo.DocumentHeader;
031: import org.kuali.core.bo.GlobalBusinessObject;
032: import org.kuali.core.bo.PersistableBusinessObject;
033: import org.kuali.core.exceptions.ValidationException;
034: import org.kuali.core.maintenance.Maintainable;
035: import org.kuali.core.rule.event.KualiDocumentEvent;
036: import org.kuali.core.rule.event.SaveDocumentEvent;
037: import org.kuali.core.util.GlobalVariables;
038: import org.kuali.core.util.ObjectUtils;
039: import org.kuali.core.util.UrlFactory;
040: import org.kuali.core.workflow.service.KualiWorkflowDocument;
041: import org.kuali.rice.KNSServiceLocator;
042: import org.w3c.dom.Document;
043: import org.w3c.dom.Node;
044: import org.w3c.dom.NodeList;
045: import org.xml.sax.InputSource;
046: import org.xml.sax.SAXException;
047:
048: import edu.iu.uis.eden.exception.WorkflowException;
049:
050: /**
051: * The maintenance xml structure will be: <maintainableDocumentContents maintainableImplClass="className">
052: * <oldMaintainableObject>... </oldMaintainableObject> <newMaintainableObject>... </newMaintainableObject>
053: * </maintainableDocumentContents> Maintenance Document
054: */
055: public final class MaintenanceDocumentBase extends DocumentBase
056: implements MaintenanceDocument {
057: private static final long serialVersionUID = -505085142412593305L;
058: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
059: .getLogger(MaintenanceDocumentBase.class);
060: public static final String MAINTAINABLE_IMPL_CLASS = "maintainableImplClass";
061: public static final String OLD_MAINTAINABLE_TAG_NAME = "oldMaintainableObject";
062: public static final String NEW_MAINTAINABLE_TAG_NAME = "newMaintainableObject";
063: public static final String MAINTENANCE_ACTION_TAG_NAME = "maintenanceAction";
064:
065: protected Maintainable oldMaintainableObject;
066: protected Maintainable newMaintainableObject;
067: protected String xmlDocumentContents;
068: protected boolean fieldsClearedOnCopy;
069: protected boolean displayTopicFieldInNotes = false;
070:
071: public MaintenanceDocumentBase() {
072: super ();
073: fieldsClearedOnCopy = false;
074: }
075:
076: /**
077: * Initializies the maintainables.
078: */
079: public MaintenanceDocumentBase(String documentTypeName) {
080: this ();
081: Class clazz = KNSServiceLocator
082: .getMaintenanceDocumentDictionaryService()
083: .getMaintainableClass(documentTypeName);
084: try {
085: oldMaintainableObject = (Maintainable) clazz.newInstance();
086: newMaintainableObject = (Maintainable) clazz.newInstance();
087:
088: // initialize maintainable with a business object
089: Class boClazz = KNSServiceLocator
090: .getMaintenanceDocumentDictionaryService()
091: .getBusinessObjectClass(documentTypeName);
092: oldMaintainableObject
093: .setBusinessObject((PersistableBusinessObject) boClazz
094: .newInstance());
095: oldMaintainableObject.setBoClass(boClazz);
096: newMaintainableObject
097: .setBusinessObject((PersistableBusinessObject) boClazz
098: .newInstance());
099: newMaintainableObject.setBoClass(boClazz);
100: } catch (InstantiationException e) {
101: LOG.error("Unable to initialize maintainables of type "
102: + clazz.getName());
103: throw new RuntimeException(
104: "Unable to initialize maintainables of type "
105: + clazz.getName());
106: } catch (IllegalAccessException e) {
107: LOG.error("Unable to initialize maintainables of type "
108: + clazz.getName());
109: throw new RuntimeException(
110: "Unable to initialize maintainables of type "
111: + clazz.getName());
112: }
113: }
114:
115: /**
116: * Builds out the document title for maintenance documents - this will get loaded into the flex doc and passed into workflow. It
117: * will be searchable.
118: */
119: @Override
120: public String getDocumentTitle() {
121: String documentTitle = "";
122:
123: documentTitle = newMaintainableObject.getDocumentTitle(this );
124: if (StringUtils.isNotBlank(documentTitle)) {
125: // if doc title has been overridden by maintainable, use it
126: return documentTitle;
127: }
128:
129: // TODO - build out with bo label once we get the data dictionary stuff in place
130: // build out the right classname
131: String className = newMaintainableObject.getBusinessObject()
132: .getClass().getName();
133: String truncatedClassName = className.substring(className
134: .lastIndexOf('.') + 1);
135: if (isOldBusinessObjectInDocument()) {
136: documentTitle = "Edit ";
137: } else {
138: documentTitle = "New ";
139: }
140: documentTitle += truncatedClassName + " - ";
141: documentTitle += this .getDocumentHeader()
142: .getFinancialDocumentDescription()
143: + " ";
144: // TODO: talk with Aaron about the getKeysName replacement
145: // HashMap keyVals = (HashMap) newMaintainableObject.getKeysNameAndValuePairs();
146: // Set list = keyVals.keySet();
147: // Iterator i = list.iterator();
148: // int idx = 0;
149: // while(i.hasNext()) {
150: // String key = (String) i.next();
151: // String value = (String) keyVals.get(key);
152: // if(idx != 0) {
153: // documentTitle += ", ";
154: // }
155: // documentTitle += key;
156: // documentTitle += " = ";
157: // documentTitle += value;
158: // idx++;
159: // }
160: // documentTitle += " - ";
161: // documentTitle += this.getDocumentHeader().getDocumentDescription();
162: return documentTitle;
163: }
164:
165: /**
166: * @param xmlDocument
167: * @return
168: */
169: private boolean isOldMaintainableInDocument(Document xmlDocument) {
170: boolean isOldMaintainableInExistence = false;
171: if (xmlDocument.getElementsByTagName(OLD_MAINTAINABLE_TAG_NAME)
172: .getLength() > 0) {
173: isOldMaintainableInExistence = true;
174: }
175: return isOldMaintainableInExistence;
176: }
177:
178: /**
179: * Checks old maintainable bo has key values
180: */
181: public boolean isOldBusinessObjectInDocument() {
182: boolean isOldBusinessObjectInExistence = false;
183: if (oldMaintainableObject == null
184: || oldMaintainableObject.getBusinessObject() == null) {
185: isOldBusinessObjectInExistence = false;
186: } else {
187: isOldBusinessObjectInExistence = KNSServiceLocator
188: .getPersistenceStructureService()
189: .hasPrimaryKeyFieldValues(
190: oldMaintainableObject.getBusinessObject());
191: }
192: return isOldBusinessObjectInExistence;
193: }
194:
195: /**
196: * This method is a simplified-naming wrapper around isOldBusinessObjectInDocument(), so that the method name matches the
197: * functionality.
198: */
199: public boolean isNew() {
200: if (RiceConstants.MAINTENANCE_EDIT_ACTION
201: .equalsIgnoreCase(newMaintainableObject
202: .getMaintenanceAction())) {
203: return false;
204: } else if (RiceConstants.MAINTENANCE_NEWWITHEXISTING_ACTION
205: .equalsIgnoreCase(newMaintainableObject
206: .getMaintenanceAction())) {
207: return false;
208: } else if (RiceConstants.MAINTENANCE_NEW_ACTION
209: .equalsIgnoreCase(newMaintainableObject
210: .getMaintenanceAction())) {
211: return true;
212: } else if (RiceConstants.MAINTENANCE_COPY_ACTION
213: .equalsIgnoreCase(newMaintainableObject
214: .getMaintenanceAction())) {
215: return true;
216: } else {
217: return true;
218: }
219: // return !isOldBusinessObjectInDocument();
220: }
221:
222: /**
223: * This method is a simplified-naming wrapper around isOldBusinessObjectInDocument(), so that the method name matches the
224: * functionality.
225: */
226: public boolean isEdit() {
227: if (RiceConstants.MAINTENANCE_EDIT_ACTION
228: .equalsIgnoreCase(newMaintainableObject
229: .getMaintenanceAction())) {
230: return true;
231: } else {
232: return false;
233: }
234: // return isOldBusinessObjectInDocument();
235: }
236:
237: public boolean isNewWithExisting() {
238: if (RiceConstants.MAINTENANCE_NEWWITHEXISTING_ACTION
239: .equalsIgnoreCase(newMaintainableObject
240: .getMaintenanceAction())) {
241: return true;
242: } else {
243: return false;
244: }
245: }
246:
247: public void populateMaintainablesFromXmlDocumentContents() {
248: // get a hold of the parsed xml document, then read the classname,
249: // then instantiate one to two instances depending on content
250: // then populate those instances
251: if (!StringUtils.isEmpty(xmlDocumentContents)) {
252: DocumentBuilderFactory factory = DocumentBuilderFactory
253: .newInstance();
254:
255: try {
256: DocumentBuilder builder = factory.newDocumentBuilder();
257: Document xmlDocument = builder.parse(new InputSource(
258: new StringReader(xmlDocumentContents)));
259: String clazz = xmlDocument.getDocumentElement()
260: .getAttribute(MAINTAINABLE_IMPL_CLASS);
261: if (isOldMaintainableInDocument(xmlDocument)) {
262: oldMaintainableObject = (Maintainable) Class
263: .forName(clazz).newInstance();
264: PersistableBusinessObject bo = getBusinessObjectFromXML(OLD_MAINTAINABLE_TAG_NAME);
265:
266: String oldMaintenanceAction = getMaintenanceAction(
267: xmlDocument, OLD_MAINTAINABLE_TAG_NAME);
268: oldMaintainableObject
269: .setMaintenanceAction(oldMaintenanceAction);
270:
271: oldMaintainableObject.setBusinessObject(bo);
272: oldMaintainableObject.setBoClass(bo.getClass());
273: }
274: newMaintainableObject = (Maintainable) Class.forName(
275: clazz).newInstance();
276: PersistableBusinessObject bo = getBusinessObjectFromXML(NEW_MAINTAINABLE_TAG_NAME);
277: newMaintainableObject.setBusinessObject(bo);
278: newMaintainableObject.setBoClass(bo.getClass());
279:
280: String newMaintenanceAction = getMaintenanceAction(
281: xmlDocument, NEW_MAINTAINABLE_TAG_NAME);
282: newMaintainableObject
283: .setMaintenanceAction(newMaintenanceAction);
284:
285: } catch (ParserConfigurationException e) {
286: LOG.error("Error while parsing document contents", e);
287: throw new RuntimeException(
288: "Could not load document contents from xml", e);
289: } catch (SAXException e) {
290: LOG.error("Error while parsing document contents", e);
291: throw new RuntimeException(
292: "Could not load document contents from xml", e);
293: } catch (IOException e) {
294: LOG.error("Error while parsing document contents", e);
295: throw new RuntimeException(
296: "Could not load document contents from xml", e);
297: } catch (InstantiationException e) {
298: LOG.error("Error while parsing document contents", e);
299: throw new RuntimeException(
300: "Could not load document contents from xml", e);
301: } catch (IllegalAccessException e) {
302: LOG.error("Error while parsing document contents", e);
303: throw new RuntimeException(
304: "Could not load document contents from xml", e);
305: } catch (ClassNotFoundException e) {
306: LOG.error("Error while parsing document contents", e);
307: throw new RuntimeException(
308: "Could not load document contents from xml", e);
309: }
310:
311: }
312: }
313:
314: /**
315: * This method is a lame containment of ugly DOM walking code. This is ONLY necessary because of the version conflicts between
316: * Xalan.jar in 2.6.x and 2.7. As soon as we can upgrade to 2.7, this will be switched to using XPath, which is faster and much
317: * easier on the eyes.
318: *
319: * @param xmlDocument
320: * @param oldOrNewElementName - String oldMaintainableObject or newMaintainableObject
321: * @return the value of the element, or null if none was there
322: */
323: private String getMaintenanceAction(Document xmlDocument,
324: String oldOrNewElementName) {
325:
326: if (StringUtils.isBlank(oldOrNewElementName)) {
327: throw new IllegalArgumentException(
328: "oldOrNewElementName may not be blank, null, or empty-string.");
329: }
330:
331: String maintenanceAction = null;
332: NodeList rootChildren = xmlDocument.getDocumentElement()
333: .getChildNodes();
334: for (int i = 0; i < rootChildren.getLength(); i++) {
335: Node rootChild = rootChildren.item(i);
336: if (oldOrNewElementName.equalsIgnoreCase(rootChild
337: .getNodeName())) {
338: NodeList maintChildren = rootChild.getChildNodes();
339: for (int j = 0; j < maintChildren.getLength(); j++) {
340: Node maintChild = maintChildren.item(j);
341: if (MAINTENANCE_ACTION_TAG_NAME
342: .equalsIgnoreCase(maintChild.getNodeName())) {
343: maintenanceAction = maintChild.getChildNodes()
344: .item(0).getNodeValue();
345: }
346: }
347: }
348: }
349: return maintenanceAction;
350: }
351:
352: /**
353: * Retrieves substring of document contents from maintainable tag name. Then use xml service to translate xml into a business
354: * object.
355: */
356: private PersistableBusinessObject getBusinessObjectFromXML(
357: String maintainableTagName) {
358: String maintXml = StringUtils.substringBetween(
359: xmlDocumentContents, "<" + maintainableTagName + ">",
360: "</" + maintainableTagName + ">");
361: PersistableBusinessObject businessObject = (PersistableBusinessObject) KNSServiceLocator
362: .getXmlObjectSerializerService().fromXml(maintXml);
363: return businessObject;
364: }
365:
366: /**
367: * Populates the xml document contents from the maintainables.
368: *
369: * @see org.kuali.core.document.MaintenanceDocument#populateXmlDocumentContentsFromMaintainables()
370: */
371: public void populateXmlDocumentContentsFromMaintainables() {
372: StringBuffer docContentBuffer = new StringBuffer();
373: docContentBuffer
374: .append(
375: "<maintainableDocumentContents maintainableImplClass=\"")
376: .append(newMaintainableObject.getClass().getName())
377: .append("\">");
378: if (oldMaintainableObject != null
379: && oldMaintainableObject.getBusinessObject() != null) {
380: // TODO: refactor this out into a method
381: docContentBuffer.append("<" + OLD_MAINTAINABLE_TAG_NAME
382: + ">");
383:
384: PersistableBusinessObject oldBo = oldMaintainableObject
385: .getBusinessObject();
386: ObjectUtils.materializeAllSubObjects(oldBo); // hack to resolve XStream not dealing well with Proxies
387: docContentBuffer.append(KNSServiceLocator
388: .getXmlObjectSerializerService().toXml(oldBo));
389:
390: // add the maintainable's maintenanceAction
391: docContentBuffer.append("<" + MAINTENANCE_ACTION_TAG_NAME
392: + ">");
393: docContentBuffer.append(oldMaintainableObject
394: .getMaintenanceAction());
395: docContentBuffer.append("</" + MAINTENANCE_ACTION_TAG_NAME
396: + ">\n");
397:
398: docContentBuffer.append("</" + OLD_MAINTAINABLE_TAG_NAME
399: + ">");
400: }
401: docContentBuffer.append("<" + NEW_MAINTAINABLE_TAG_NAME + ">");
402:
403: PersistableBusinessObject newBo = newMaintainableObject
404: .getBusinessObject();
405: ObjectUtils.materializeAllSubObjects(newBo); // hack to resolve XStream not dealing well with Proxies
406: docContentBuffer.append(KNSServiceLocator
407: .getXmlObjectSerializerService().toXml(newBo));
408:
409: // add the maintainable's maintenanceAction
410: docContentBuffer
411: .append("<" + MAINTENANCE_ACTION_TAG_NAME + ">");
412: docContentBuffer.append(newMaintainableObject
413: .getMaintenanceAction());
414: docContentBuffer.append("</" + MAINTENANCE_ACTION_TAG_NAME
415: + ">\n");
416:
417: docContentBuffer.append("</" + NEW_MAINTAINABLE_TAG_NAME + ">");
418: docContentBuffer.append("</maintainableDocumentContents>");
419: xmlDocumentContents = docContentBuffer.toString();
420: }
421:
422: /**
423: * @see org.kuali.core.document.DocumentBase#handleRouteStatusChange()
424: */
425: @Override
426: public void handleRouteStatusChange() {
427: super .handleRouteStatusChange();
428:
429: KualiWorkflowDocument workflowDocument = getDocumentHeader()
430: .getWorkflowDocument();
431: getNewMaintainableObject().handleRouteStatusChange(
432: getDocumentHeader());
433: // commit the changes to the Maintainable BusinessObject when it goes to Processed (ie, fully approved),
434: // and also unlock it
435: if (workflowDocument.stateIsProcessed()) {
436: String documentNumber = getDocumentHeader()
437: .getDocumentNumber();
438: newMaintainableObject.setDocumentNumber(documentNumber);
439: newMaintainableObject.saveBusinessObject();
440: KNSServiceLocator.getMaintenanceDocumentService()
441: .deleteLocks(documentNumber);
442: }
443:
444: // unlock the document when its canceled or disapproved
445: if (workflowDocument.stateIsCanceled()
446: || workflowDocument.stateIsDisapproved()) {
447: String documentNumber = getDocumentHeader()
448: .getDocumentNumber();
449: KNSServiceLocator.getMaintenanceDocumentService()
450: .deleteLocks(documentNumber);
451: }
452: }
453:
454: /**
455: * Pre-Save hook.
456: *
457: * @see org.kuali.core.document.Document#prepareForSave()
458: */
459: @Override
460: public void prepareForSave() {
461: if (newMaintainableObject != null) {
462: newMaintainableObject.prepareForSave();
463: }
464: }
465:
466: /**
467: * @see org.kuali.core.document.DocumentBase#processAfterRetrieve()
468: */
469: @Override
470: public void processAfterRetrieve() {
471: super .processAfterRetrieve();
472: populateMaintainablesFromXmlDocumentContents();
473: if (newMaintainableObject != null) {
474: newMaintainableObject.processAfterRetrieve();
475: }
476: }
477:
478: /**
479: * @return Returns the newMaintainableObject.
480: */
481: public Maintainable getNewMaintainableObject() {
482: return newMaintainableObject;
483: }
484:
485: /**
486: * @param newMaintainableObject The newMaintainableObject to set.
487: */
488: public void setNewMaintainableObject(
489: Maintainable newMaintainableObject) {
490: this .newMaintainableObject = newMaintainableObject;
491: }
492:
493: /**
494: * @return Returns the oldMaintainableObject.
495: */
496: public Maintainable getOldMaintainableObject() {
497: return oldMaintainableObject;
498: }
499:
500: /**
501: * @param oldMaintainableObject The oldMaintainableObject to set.
502: */
503: public void setOldMaintainableObject(
504: Maintainable oldMaintainableObject) {
505: this .oldMaintainableObject = oldMaintainableObject;
506: }
507:
508: @Override
509: public void setDocumentNumber(String documentNumber) {
510: super .setDocumentNumber(documentNumber);
511:
512: // set the finDocNumber on the Maintainable
513: oldMaintainableObject.setDocumentNumber(documentNumber);
514: newMaintainableObject.setDocumentNumber(documentNumber);
515:
516: }
517:
518: /**
519: * Gets the fieldsClearedOnCopy attribute.
520: *
521: * @return Returns the fieldsClearedOnCopy.
522: */
523: public final boolean isFieldsClearedOnCopy() {
524: return fieldsClearedOnCopy;
525: }
526:
527: /**
528: * Sets the fieldsClearedOnCopy attribute value.
529: *
530: * @param fieldsClearedOnCopy The fieldsClearedOnCopy to set.
531: */
532: public final void setFieldsClearedOnCopy(boolean fieldsClearedOnCopy) {
533: this .fieldsClearedOnCopy = fieldsClearedOnCopy;
534: }
535:
536: /**
537: * @see org.kuali.core.bo.BusinessObjectBase#toStringMapper()
538: */
539: @Override
540: protected LinkedHashMap toStringMapper() {
541: LinkedHashMap m = new LinkedHashMap();
542:
543: m.put("versionNumber", getVersionNumber());
544: m.put("comp", Boolean.valueOf(getDocumentHeader()
545: .getWorkflowDocument().isCompletionRequested()));
546: m.put("app", Boolean.valueOf(getDocumentHeader()
547: .getWorkflowDocument().isApprovalRequested()));
548: m.put("ack", Boolean.valueOf(getDocumentHeader()
549: .getWorkflowDocument().isAcknowledgeRequested()));
550: m.put("fyi", Boolean.valueOf(getDocumentHeader()
551: .getWorkflowDocument().isFYIRequested()));
552:
553: return m;
554: }
555:
556: /**
557: * Gets the xmlDocumentContents attribute.
558: *
559: * @return Returns the xmlDocumentContents.
560: */
561: public String getXmlDocumentContents() {
562: return xmlDocumentContents;
563: }
564:
565: /**
566: * Sets the xmlDocumentContents attribute value.
567: *
568: * @param xmlDocumentContents The xmlDocumentContents to set.
569: */
570: public void setXmlDocumentContents(String xmlDocumentContents) {
571: this .xmlDocumentContents = xmlDocumentContents;
572: }
573:
574: /**
575: * @see org.kuali.core.document.Document#getAllowsCopy()
576: */
577: public boolean getAllowsCopy() {
578: Boolean allowsCopy = KNSServiceLocator
579: .getMaintenanceDocumentDictionaryService()
580: .getAllowsCopy(this );
581: if (allowsCopy != null) {
582: return allowsCopy.booleanValue();
583: } else {
584: return false;
585: }
586: }
587:
588: /**
589: * @see org.kuali.core.document.MaintenanceDocument#getDisplayTopicFieldInNotes()
590: */
591: public boolean getDisplayTopicFieldInNotes() {
592: return displayTopicFieldInNotes;
593: }
594:
595: /**
596: * @see org.kuali.core.document.MaintenanceDocument#setDisplayTopicFieldInNotes(boolean)
597: */
598: public void setDisplayTopicFieldInNotes(
599: boolean displayTopicFieldInNotes) {
600: this .displayTopicFieldInNotes = displayTopicFieldInNotes;
601: }
602:
603: @Override
604: /**
605: * Overridden to avoid serializing the xml twice, because of the xmlDocumentContents property of this object
606: */
607: public String serializeDocumentToXml() {
608: String tempXmlDocumentContents = xmlDocumentContents;
609: xmlDocumentContents = null;
610: String xmlForWorkflow = super .serializeDocumentToXml();
611: xmlDocumentContents = tempXmlDocumentContents;
612: return xmlForWorkflow;
613: }
614:
615: @Override
616: public void prepareForSave(KualiDocumentEvent event) {
617: super .prepareForSave(event);
618: populateXmlDocumentContentsFromMaintainables();
619: }
620:
621: /**
622: * Explicitly NOT calling super here. This is a complete override of the validation
623: * rules behavior.
624: *
625: * @see org.kuali.core.document.DocumentBase#validateBusinessRules(org.kuali.core.rule.event.KualiDocumentEvent)
626: */
627: public void validateBusinessRules(KualiDocumentEvent event) {
628: if (!GlobalVariables.getErrorMap().isEmpty()) {
629: logErrors();
630: throw new ValidationException(
631: "errors occured before business rule");
632: }
633:
634: // check for locking documents for MaintenanceDocuments
635: if (this instanceof MaintenanceDocument) {
636: checkForLockingDocument();
637: }
638:
639: // perform validation against rules engine
640: LOG.info("invoking rules engine on document "
641: + getDocumentNumber());
642: boolean isValid = true;
643: isValid = KNSServiceLocator.getKualiRuleService().applyRules(
644: event);
645:
646: // check to see if the br eval passed or failed
647: if (!isValid) {
648: logErrors();
649: // TODO: better error handling at the lower level and a better error message are
650: // needed here
651: throw new ValidationException(
652: "business rule evaluation failed");
653: } else if (!GlobalVariables.getErrorMap().isEmpty()) {
654: logErrors();
655: if (event instanceof SaveDocumentEvent) {
656: // for maintenance documents, we want to always actually do a save if the
657: // user requests a save, even if there are validation or business rules
658: // failures. this empty if does this, and allows the document to be saved,
659: // even if there are failures.
660: // BR or validation failures on a ROUTE even should always stop the route,
661: // that has not changed
662: } else {
663: throw new ValidationException(
664: "Unreported errors occured during business rule evaluation (rule developer needs to put meaningful error messages into global ErrorMap)");
665: }
666: }
667: LOG.debug("validation completed");
668:
669: }
670:
671: private void checkForLockingDocument() {
672:
673: LOG.info("starting checkForLockingDocument");
674:
675: // get the docHeaderId of the blocking docs, if any are locked and blocking
676: String blockingDocId = KNSServiceLocator
677: .getMaintenanceDocumentService().getLockingDocumentId(
678: this );
679:
680: // if we got nothing, then no docs are blocking, and we're done
681: if (StringUtils.isBlank(blockingDocId)) {
682: return;
683: }
684:
685: LOG.info("Locking document found: docId = " + blockingDocId
686: + ".");
687:
688: // load the blocking locked document
689: org.kuali.core.document.Document lockedDocument;
690: try {
691: lockedDocument = KNSServiceLocator.getDocumentService()
692: .getByDocumentHeaderId(blockingDocId);
693: } catch (WorkflowException e) {
694: throw new ValidationException(
695: "Could not load the locking document.", e);
696: }
697:
698: // if we can ignore the lock (see method notes), then exit cause we're done
699: if (lockCanBeIgnored(lockedDocument)) {
700: return;
701: }
702:
703: // build the link URL for the blocking document
704: Properties parameters = new Properties();
705: parameters.put(RiceConstants.DISPATCH_REQUEST_PARAMETER,
706: RiceConstants.DOC_HANDLER_METHOD);
707: parameters.put(RiceConstants.PARAMETER_DOC_ID, blockingDocId);
708: parameters.put(RiceConstants.PARAMETER_COMMAND,
709: RiceConstants.METHOD_DISPLAY_DOC_SEARCH_VIEW);
710: String blockingUrl = UrlFactory.parameterizeUrl(
711: RiceConstants.MAINTENANCE_ACTION, parameters);
712: LOG.debug("blockingUrl = '" + blockingUrl + "'");
713:
714: // post an error about the locked document
715: LOG.debug("Maintenance record: "
716: + lockedDocument.getDocumentHeader()
717: .getDocumentNumber() + "is locked.");
718: String[] errorParameters = { blockingUrl, blockingDocId };
719: GlobalVariables.getErrorMap().putError(
720: RiceConstants.GLOBAL_ERRORS,
721: RiceKeyConstants.ERROR_MAINTENANCE_LOCKED,
722: errorParameters);
723:
724: throw new ValidationException(
725: "Maintenance Record is locked by another document.");
726: }
727:
728: /**
729: * This method guesses whether the current user should be allowed to change a document even though it is locked. It probably
730: * should use Authorization instead? See KULNRVSYS-948
731: *
732: * @param lockedDocument
733: * @return
734: * @throws WorkflowException
735: */
736: private boolean lockCanBeIgnored(
737: org.kuali.core.document.Document lockedDocument) {
738: // TODO: implement real authorization for Maintenance Document Save/Route - KULNRVSYS-948
739:
740: DocumentHeader documentHeader = lockedDocument
741: .getDocumentHeader();
742:
743: // get the user-id. if no user-id, then we can do this test, so exit
744: String userId = GlobalVariables.getUserSession().getNetworkId()
745: .trim();
746: if (StringUtils.isBlank(userId)) {
747: return false; // dont bypass locking
748: }
749:
750: // if the current user is not the initiator of the blocking document
751: if (!userId.equalsIgnoreCase(documentHeader
752: .getWorkflowDocument().getInitiatorNetworkId().trim())) {
753: return false;
754: }
755:
756: // if the blocking document hasn't been routed, we can ignore it
757: return RiceConstants.DocumentStatusCodes.INITIATED
758: .equals(documentHeader.getFinancialDocumentStatusCode());
759: }
760:
761: /**
762: * this needs to happen after the document itself is saved, to preserve consistency of the ver_nbr and in the case of initial
763: * save, because this can't be saved until the document is saved initially
764: *
765: * @see org.kuali.core.document.DocumentBase#postProcessSave(org.kuali.core.rule.event.KualiDocumentEvent)
766: */
767: @Override
768: public void postProcessSave(KualiDocumentEvent event) {
769: PersistableBusinessObject bo = getNewMaintainableObject()
770: .getBusinessObject();
771: if (bo instanceof GlobalBusinessObject) {
772: KNSServiceLocator.getBusinessObjectService().save(bo);
773: }
774: //currently only global documents could change the list of what they're affecting during routing,
775: //so could restrict this to only happening with them, but who knows if that will change, so safest
776: //to always do the delete and re-add...seems a bit inefficient though if nothing has changed, which is
777: //most of the time...could also try to only add/update/delete what's changed, but this is easier
778: if (!(event instanceof SaveDocumentEvent)) { //don't lock until they route
779: KNSServiceLocator.getMaintenanceDocumentService()
780: .deleteLocks(this .getDocumentNumber());
781: KNSServiceLocator.getMaintenanceDocumentService()
782: .storeLocks(
783: this .getNewMaintainableObject()
784: .generateMaintenanceLocks());
785: }
786: }
787:
788: /**
789: * @see org.kuali.core.document.DocumentBase#getDocumentBusinessObject()
790: */
791: @Override
792: public PersistableBusinessObject getDocumentBusinessObject() {
793: if (documentBusinessObject == null) {
794: documentBusinessObject = this.newMaintainableObject
795: .getBusinessObject();
796: }
797: return documentBusinessObject;
798: }
799: }
|