001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License"); you may not use this file except in
005: * compliance with the License. You may obtain a copy of the License at
006: *
007: * http://www.opensource.org/licenses/ecl1.php
008: *
009: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS
010: * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
011: * language governing permissions and limitations under the License.
012: */
013: package org.kuali.core.rules;
014:
015: import org.apache.commons.lang.StringUtils;
016: import org.kuali.RiceConstants;
017: import org.kuali.RiceKeyConstants;
018: import org.kuali.RicePropertyConstants;
019: import org.kuali.core.KualiModule;
020: import org.kuali.core.authorization.AuthorizationType;
021: import org.kuali.core.bo.AdHocRoutePerson;
022: import org.kuali.core.bo.AdHocRouteWorkgroup;
023: import org.kuali.core.bo.Note;
024: import org.kuali.core.bo.Parameter;
025: import org.kuali.core.bo.user.AuthenticationUserId;
026: import org.kuali.core.bo.user.UniversalUser;
027: import org.kuali.core.document.Document;
028: import org.kuali.core.document.MaintenanceDocument;
029: import org.kuali.core.exceptions.UserNotFoundException;
030: import org.kuali.core.rule.AddAdHocRoutePersonRule;
031: import org.kuali.core.rule.AddAdHocRouteWorkgroupRule;
032: import org.kuali.core.rule.AddNoteRule;
033: import org.kuali.core.rule.ApproveDocumentRule;
034: import org.kuali.core.rule.RouteDocumentRule;
035: import org.kuali.core.rule.SaveDocumentRule;
036: import org.kuali.core.rule.event.ApproveDocumentEvent;
037: import org.kuali.core.service.DictionaryValidationService;
038: import org.kuali.core.service.KualiConfigurationService;
039: import org.kuali.core.service.KualiModuleService;
040: import org.kuali.core.service.UniversalUserService;
041: import org.kuali.core.util.ErrorMap;
042: import org.kuali.core.util.GlobalVariables;
043: import org.kuali.core.workflow.service.KualiWorkflowInfo;
044: import org.kuali.rice.KNSServiceLocator;
045:
046: import edu.iu.uis.eden.clientapp.vo.WorkgroupNameIdVO;
047: import edu.iu.uis.eden.clientapp.vo.WorkgroupVO;
048: import edu.iu.uis.eden.exception.WorkflowException;
049:
050: /**
051: * This class contains all of the business rules that are common to all documents.
052: */
053: public abstract class DocumentRuleBase implements SaveDocumentRule,
054: RouteDocumentRule, ApproveDocumentRule, AddNoteRule,
055: AddAdHocRoutePersonRule, AddAdHocRouteWorkgroupRule {
056: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
057: .getLogger(DocumentRuleBase.class);
058:
059: private static UniversalUserService universalUserService;
060: private static KualiModuleService kualiModuleService;
061: private static DictionaryValidationService dictionaryValidationService;
062: private static KualiWorkflowInfo workflowInfoService;
063: private static KualiConfigurationService kualiConfigurationService;
064:
065: /**
066: * Just some arbitrarily high max depth that's unlikely to occur in real life to prevent recursion problems
067: */
068: private int maxDictionaryValidationDepth = 100;
069:
070: private void initStatics() {
071: if (universalUserService == null) {
072: workflowInfoService = KNSServiceLocator
073: .getWorkflowInfoService();
074: kualiConfigurationService = KNSServiceLocator
075: .getKualiConfigurationService();
076: kualiModuleService = KNSServiceLocator
077: .getKualiModuleService();
078: dictionaryValidationService = KNSServiceLocator
079: .getDictionaryValidationService();
080: universalUserService = KNSServiceLocator
081: .getUniversalUserService();
082: }
083: }
084:
085: protected UniversalUserService getUniversalUserService() {
086: initStatics();
087: return universalUserService;
088: }
089:
090: protected KualiModuleService getKualiModuleService() {
091: initStatics();
092: return kualiModuleService;
093: }
094:
095: protected DictionaryValidationService getDictionaryValidationService() {
096: initStatics();
097: return dictionaryValidationService;
098: }
099:
100: protected KualiWorkflowInfo getWorkflowInfoService() {
101: initStatics();
102: return workflowInfoService;
103: }
104:
105: protected KualiConfigurationService getKualiConfigurationService() {
106: initStatics();
107: return kualiConfigurationService;
108: }
109:
110: /**
111: * Verifies that the document's overview fields are valid - it does required and format checks.
112: *
113: * @param document
114: * @return boolean True if the document description is valid, false otherwise.
115: */
116: public boolean isDocumentOverviewValid(Document document) {
117: LOG.debug("isDocumentOverviewValid(Document) - start");
118:
119: // add in the documentHeader path
120: GlobalVariables.getErrorMap().addToErrorPath(
121: RiceConstants.DOCUMENT_PROPERTY_NAME);
122: GlobalVariables.getErrorMap().addToErrorPath(
123: RiceConstants.DOCUMENT_HEADER_PROPERTY_NAME);
124:
125: // check the document header for fields like the description
126: getDictionaryValidationService().validateBusinessObject(
127: document.getDocumentHeader());
128:
129: // drop the error path keys off now
130: GlobalVariables.getErrorMap().removeFromErrorPath(
131: RiceConstants.DOCUMENT_HEADER_PROPERTY_NAME);
132: GlobalVariables.getErrorMap().removeFromErrorPath(
133: RiceConstants.DOCUMENT_PROPERTY_NAME);
134:
135: boolean returnboolean = GlobalVariables.getErrorMap().isEmpty();
136: LOG.debug("isDocumentOverviewValid(Document) - end");
137: return returnboolean;
138: }
139:
140: /**
141: * Validates the document attributes against the data dictionary.
142: *
143: * @param document
144: * @param validateRequired if true, then an error will be retruned if a DD required field is empty. if false, no required checking is done
145: * @return True if the document attributes are valid, false otherwise.
146: */
147: public boolean isDocumentAttributesValid(Document document,
148: boolean validateRequired) {
149: LOG.debug("isDocumentAttributesValid(Document) - start");
150:
151: // start updating the error path name
152: GlobalVariables.getErrorMap().addToErrorPath(
153: RiceConstants.DOCUMENT_PROPERTY_NAME);
154:
155: // check the document for fields like explanation and org doc #
156: getDictionaryValidationService()
157: .validateDocumentAndUpdatableReferencesRecursively(
158: document, getMaxDictionaryValidationDepth(),
159: validateRequired);
160:
161: // drop the error path keys off now
162: GlobalVariables.getErrorMap().removeFromErrorPath(
163: RiceConstants.DOCUMENT_PROPERTY_NAME);
164:
165: boolean returnboolean = GlobalVariables.getErrorMap().isEmpty();
166: LOG.debug("isDocumentAttributesValid(Document) - end");
167: return returnboolean;
168: }
169:
170: /**
171: * Runs all business rules needed prior to saving. This includes both common rules for all documents, plus
172: * class-specific business rules.
173: *
174: * This method will only return false if it fails the isValidForSave() test. Otherwise, it will always return
175: * positive regardless of the outcome of the business rules. However, any error messages resulting from the business
176: * rules will still be populated, for display to the consumer of this service.
177: *
178: * @see org.kuali.core.rule.SaveDocumentRule#processSaveDocument(org.kuali.core.document.Document)
179: *
180: */
181: public boolean processSaveDocument(Document document) {
182: LOG.debug("processSaveDocument(Document) - start");
183:
184: boolean isValid = true;
185:
186: isValid &= isDocumentOverviewValid(document);
187: if (isValid) {
188: GlobalVariables.getErrorMap().addToErrorPath(
189: RiceConstants.DOCUMENT_PROPERTY_NAME);
190: getDictionaryValidationService()
191: .validateDocumentAndUpdatableReferencesRecursively(
192: document,
193: getMaxDictionaryValidationDepth(), false);
194: GlobalVariables.getErrorMap().removeFromErrorPath(
195: RiceConstants.DOCUMENT_PROPERTY_NAME);
196: isValid &= GlobalVariables.getErrorMap().isEmpty();
197: }
198: if (isValid) {
199: isValid &= processCustomSaveDocumentBusinessRules(document);
200: }
201:
202: LOG.debug("processSaveDocument(Document) - end");
203: return isValid;
204: }
205:
206: /**
207: * This method should be overridden by children rule classes as a hook to implement document specific business rule
208: * checks for the "save document" event.
209: *
210: * @param document
211: * @return boolean True if the rules checks passed, false otherwise.
212: */
213: protected boolean processCustomSaveDocumentBusinessRules(
214: Document document) {
215: LOG
216: .debug("processCustomSaveDocumentBusinessRules(Document) - start");
217:
218: LOG
219: .debug("processCustomSaveDocumentBusinessRules(Document) - end");
220: return true;
221: }
222:
223: /**
224: * Runs all business rules needed prior to routing. This includes both common rules for all maintenance documents,
225: * plus class-specific business rules.
226: *
227: * This method will return false if any business rule fails, or if the document is in an invalid state, and not
228: * routable (see isDocumentValidForRouting()).
229: *
230: * @see org.kuali.core.rule.RouteDocumentRule#processRouteDocument(org.kuali.core.document.Document)
231: */
232: public boolean processRouteDocument(Document document) {
233: LOG.debug("processRouteDocument(Document) - start");
234:
235: boolean isValid = true;
236:
237: isValid &= isDocumentAttributesValid(document, true);
238:
239: if (isValid) {
240: isValid &= processCustomRouteDocumentBusinessRules(document);
241: }
242:
243: LOG.debug("processRouteDocument(Document) - end");
244: return isValid;
245: }
246:
247: /**
248: * This method should be overridden by children rule classes as a hook to implement document specific business rule
249: * checks for the "route document" event.
250: *
251: * @param document
252: * @return boolean True if the rules checks passed, false otherwise.
253: */
254: protected boolean processCustomRouteDocumentBusinessRules(
255: Document document) {
256: LOG
257: .debug("processCustomRouteDocumentBusinessRules(Document) - start");
258:
259: LOG
260: .debug("processCustomRouteDocumentBusinessRules(Document) - end");
261: return true;
262: }
263:
264: /**
265: * Runs all business rules needed prior to approving. This includes both common rules for all documents, plus
266: * class-specific business rules.
267: *
268: * This method will return false if any business rule fails, or if the document is in an invalid state, and not
269: * approveble.
270: *
271: * @see org.kuali.core.rule.ApproveDocumentRule#processApproveDocument(org.kuali.core.rule.event.ApproveDocumentEvent)
272: */
273: public boolean processApproveDocument(
274: ApproveDocumentEvent approveEvent) {
275: LOG
276: .debug("processApproveDocument(ApproveDocumentEvent) - start");
277:
278: boolean isValid = true;
279:
280: isValid &= processCustomApproveDocumentBusinessRules(approveEvent);
281:
282: LOG.debug("processApproveDocument(ApproveDocumentEvent) - end");
283: return isValid;
284: }
285:
286: /**
287: * This method should be overridden by children rule classes as a hook to implement document specific business rule
288: * checks for the "approve document" event.
289: *
290: * @param document
291: * @return boolean True if the rules checks passed, false otherwise.
292: */
293: protected boolean processCustomApproveDocumentBusinessRules(
294: ApproveDocumentEvent approveEvent) {
295: LOG
296: .debug("processCustomApproveDocumentBusinessRules(ApproveDocumentEvent) - start");
297:
298: LOG
299: .debug("processCustomApproveDocumentBusinessRules(ApproveDocumentEvent) - end");
300: return true;
301: }
302:
303: /**
304: * Runs all business rules needed prior to adding a document note.
305: *
306: * This method will return false if any business rule fails.
307: *
308: * @see org.kuali.core.rule.AddDocumentNoteRule#processAddDocumentNote(org.kuali.core.document.Document,
309: * org.kuali.core.document.DocumentNote)
310: */
311: public boolean processAddNote(Document document, Note note) {
312: LOG.debug("processAddNote(Document, Note) - start");
313:
314: boolean isValid = true;
315:
316: isValid &= isNoteValid(note);
317:
318: isValid &= processCustomAddNoteBusinessRules(document, note);
319:
320: LOG.debug("processAdfNote(Document, Note) - end");
321: return isValid;
322: }
323:
324: /**
325: * Verifies that the note's fields are valid - it does required and format checks.
326: *
327: * @param note
328: * @return boolean True if the document description is valid, false otherwise.
329: */
330: public boolean isNoteValid(Note note) {
331: // TODO: Chris change these constants!
332:
333: // add the error path keys on the stack
334: GlobalVariables.getErrorMap().addToErrorPath(
335: RiceConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME);
336:
337: // check the document header for fields like the description
338: KNSServiceLocator.getDictionaryValidationService()
339: .validateBusinessObject(note);
340:
341: // drop the error path keys off now
342: GlobalVariables.getErrorMap().removeFromErrorPath(
343: RiceConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME);
344:
345: return GlobalVariables.getErrorMap().isEmpty();
346: }
347:
348: /**
349: * This method should be overridden by children rule classes as a hook to implement document specific business rule
350: * checks for the "add document note" event.
351: *
352: * @param document
353: * @param note
354: * @return boolean True if the rules checks passed, false otherwise.
355: */
356: protected boolean processCustomAddNoteBusinessRules(
357: Document document, Note note) {
358: return true;
359: }
360:
361: /**
362: * @see org.kuali.core.rule.AddAdHocRoutePersonRule#processAddAdHocRoutePerson(org.kuali.core.document.Document,
363: * org.kuali.core.bo.AdHocRoutePerson)
364: */
365: public boolean processAddAdHocRoutePerson(Document document,
366: AdHocRoutePerson adHocRoutePerson) {
367: LOG
368: .debug("processAddAdHocRoutePerson(Document, AdHocRoutePerson) - start");
369:
370: boolean isValid = true;
371:
372: isValid &= isAddHocRoutePersonValid(document, adHocRoutePerson);
373:
374: isValid &= processCustomAddAdHocRoutePersonBusinessRules(
375: document, adHocRoutePerson);
376:
377: LOG
378: .debug("processAddAdHocRoutePerson(Document, AdHocRoutePerson) - end");
379: return isValid;
380: }
381:
382: /**
383: * Verifies that the adHocRoutePerson's fields are valid - it does required and format checks.
384: *
385: * @param person
386: * @return boolean True if valid, false otherwise.
387: */
388: public boolean isAddHocRoutePersonValid(Document document,
389: AdHocRoutePerson person) {
390: LOG.debug("isAddHocRoutePersonValid(AdHocRoutePerson) - start");
391:
392: ErrorMap errorMap = GlobalVariables.getErrorMap();
393:
394: // new recipients are not embedded in the error path; existing lines should be
395: if (errorMap.getErrorPath().size() == 0) {
396: // add the error path keys on the stack
397: errorMap
398: .addToErrorPath(RiceConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
399: }
400:
401: if (StringUtils.isNotBlank(person.getId())) {
402: UniversalUser user = null;
403: // validate that they are a user from the user service by looking them up
404: try {
405: user = getUniversalUserService().getUniversalUser(
406: new AuthenticationUserId(person.getId()));
407: } catch (UserNotFoundException userNotFoundException) {
408: LOG
409: .warn(
410: "isAddHocRoutePersonValid(AdHocRoutePerson) - exception ignored",
411: userNotFoundException);
412: }
413: if (user == null) {
414: GlobalVariables.getErrorMap().putError(
415: RicePropertyConstants.ID,
416: RiceKeyConstants.ERROR_INVALID_ADHOC_PERSON_ID);
417: } else if (!user.isActiveForAnyModule()) {
418: GlobalVariables
419: .getErrorMap()
420: .putError(
421: RicePropertyConstants.ID,
422: RiceKeyConstants.ERROR_INACTIVE_ADHOC_PERSON_ID);
423: } else {
424: // determine the module for the document
425: KualiModule module = null;
426: Class docOrBoClass = null;
427: if (document instanceof MaintenanceDocument) {
428: docOrBoClass = ((MaintenanceDocument) document)
429: .getNewMaintainableObject().getBoClass();
430: } else {
431: docOrBoClass = document.getClass();
432: }
433: if (!getKualiModuleService().isAuthorized(
434: user,
435: new AuthorizationType.AdHocRequest(
436: docOrBoClass, person
437: .getActionRequested()))) {
438: GlobalVariables
439: .getErrorMap()
440: .putError(
441: RicePropertyConstants.ID,
442: RiceKeyConstants.ERROR_UNAUTHORIZED_ADHOC_PERSON_ID);
443: }
444: }
445: } else {
446: GlobalVariables.getErrorMap().putError(
447: RicePropertyConstants.ID,
448: RiceKeyConstants.ERROR_MISSING_ADHOC_PERSON_ID);
449: }
450:
451: // drop the error path keys off now
452: errorMap
453: .removeFromErrorPath(RiceConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
454:
455: boolean returnboolean = GlobalVariables.getErrorMap().isEmpty();
456: LOG.debug("isAddHocRoutePersonValid(AdHocRoutePerson) - end");
457: return returnboolean;
458: }
459:
460: /**
461: * This method should be overridden by children rule classes as a hook to implement document specific business rule
462: * checks for the "add ad hoc route person" event.
463: *
464: * @param document
465: * @param person
466: * @return boolean True if the rules checks passed, false otherwise.
467: */
468: protected boolean processCustomAddAdHocRoutePersonBusinessRules(
469: Document document, AdHocRoutePerson person) {
470: LOG
471: .debug("processCustomAddAdHocRoutePersonBusinessRules(Document, AdHocRoutePerson) - start");
472:
473: LOG
474: .debug("processCustomAddAdHocRoutePersonBusinessRules(Document, AdHocRoutePerson) - end");
475: return true;
476: }
477:
478: /**
479: * @see org.kuali.core.rule.AddAdHocRouteWorkgroupRule#processAddAdHocRouteWorkgroup(org.kuali.core.document.Document,
480: * org.kuali.core.bo.AdHocRouteWorkgroup)
481: */
482: public boolean processAddAdHocRouteWorkgroup(Document document,
483: AdHocRouteWorkgroup adHocRouteWorkgroup) {
484: LOG
485: .debug("processAddAdHocRouteWorkgroup(Document, AdHocRouteWorkgroup) - start");
486:
487: boolean isValid = true;
488:
489: isValid &= isAddHocRouteWorkgroupValid(adHocRouteWorkgroup);
490:
491: isValid &= processCustomAddAdHocRouteWorkgroupBusinessRules(
492: document, adHocRouteWorkgroup);
493:
494: LOG
495: .debug("processAddAdHocRouteWorkgroup(Document, AdHocRouteWorkgroup) - end");
496: return isValid;
497: }
498:
499: /**
500: * Verifies that the adHocRouteWorkgroup's fields are valid - it does required and format checks.
501: *
502: * @param workgroup
503: * @return boolean True if valid, false otherwise.
504: */
505: public boolean isAddHocRouteWorkgroupValid(
506: AdHocRouteWorkgroup workgroup) {
507: LOG
508: .debug("isAddHocRouteWorkgroupValid(AdHocRouteWorkgroup) - start");
509:
510: ErrorMap errorMap = GlobalVariables.getErrorMap();
511:
512: // new recipients are not embedded in the error path; existing lines should be
513: if (errorMap.getErrorPath().size() == 0) {
514: // add the error path keys on the stack
515: GlobalVariables
516: .getErrorMap()
517: .addToErrorPath(
518: RiceConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME);
519: }
520:
521: if (StringUtils.isNotBlank(workgroup.getId())) {
522: // validate that they are a workgroup from the workgroup service by looking them up
523: try {
524: WorkgroupVO workgroupVo = getWorkflowInfoService()
525: .getWorkgroup(
526: new WorkgroupNameIdVO(workgroup.getId()));
527: if (workgroupVo == null || !workgroupVo.isActiveInd()) {
528: GlobalVariables
529: .getErrorMap()
530: .putError(
531: RicePropertyConstants.ID,
532: RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_ID);
533: }
534: } catch (WorkflowException e) {
535: LOG
536: .error(
537: "isAddHocRouteWorkgroupValid(AdHocRouteWorkgroup)",
538: e);
539:
540: GlobalVariables
541: .getErrorMap()
542: .putError(
543: RicePropertyConstants.ID,
544: RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_ID);
545: }
546: } else {
547: GlobalVariables.getErrorMap().putError(
548: RicePropertyConstants.ID,
549: RiceKeyConstants.ERROR_MISSING_ADHOC_WORKGROUP_ID);
550: }
551:
552: // drop the error path keys off now
553: GlobalVariables.getErrorMap().removeFromErrorPath(
554: RiceConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME);
555:
556: boolean returnboolean = GlobalVariables.getErrorMap().isEmpty();
557: LOG
558: .debug("isAddHocRouteWorkgroupValid(AdHocRouteWorkgroup) - end");
559: return returnboolean;
560: }
561:
562: /**
563: * This method should be overridden by children rule classes as a hook to implement document specific business rule
564: * checks for the "add ad hoc route workgroup" event.
565: *
566: * @param document
567: * @param workgroup
568: * @return boolean True if the rules checks passed, false otherwise.
569: */
570: protected boolean processCustomAddAdHocRouteWorkgroupBusinessRules(
571: Document document, AdHocRouteWorkgroup workgroup) {
572: LOG
573: .debug("processCustomAddAdHocRouteWorkgroupBusinessRules(Document, AdHocRouteWorkgroup) - start");
574:
575: LOG
576: .debug("processCustomAddAdHocRouteWorkgroupBusinessRules(Document, AdHocRouteWorkgroup) - end");
577: return true;
578: }
579:
580: /**
581: * Gets the maximum number of levels the data-dictionary based validation will recurse for the document
582: *
583: * @return
584: */
585: public int getMaxDictionaryValidationDepth() {
586: return this .maxDictionaryValidationDepth;
587: }
588:
589: /**
590: * Gets the maximum number of levels the data-dictionary based validation will recurse for the document
591: *
592: * @param maxDictionaryValidationDepth
593: */
594: public void setMaxDictionaryValidationDepth(
595: int maxDictionaryValidationDepth) {
596: if (maxDictionaryValidationDepth < 0) {
597: LOG
598: .error("Dictionary validation depth should be greater than or equal to 0. Value received was: "
599: + maxDictionaryValidationDepth);
600: throw new RuntimeException(
601: "Dictionary validation depth should be greater than or equal to 0. Value received was: "
602: + maxDictionaryValidationDepth);
603: }
604: this.maxDictionaryValidationDepth = maxDictionaryValidationDepth;
605: }
606:
607: }
|