View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.rules;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.CoreApiServiceLocator;
20  import org.kuali.rice.core.api.config.property.ConfigurationService;
21  import org.kuali.rice.core.api.util.RiceKeyConstants;
22  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
23  import org.kuali.rice.coreservice.framework.parameter.ParameterConstants;
24  import org.kuali.rice.kew.api.KewApiConstants;
25  import org.kuali.rice.kew.api.KewApiServiceLocator;
26  import org.kuali.rice.kew.api.doctype.DocumentType;
27  import org.kuali.rice.kew.api.doctype.DocumentTypeService;
28  import org.kuali.rice.kim.api.KimConstants;
29  import org.kuali.rice.kim.api.group.Group;
30  import org.kuali.rice.kim.api.group.GroupService;
31  import org.kuali.rice.kim.api.identity.Person;
32  import org.kuali.rice.kim.api.identity.PersonService;
33  import org.kuali.rice.kim.api.permission.PermissionService;
34  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
35  import org.kuali.rice.krad.bo.AdHocRoutePerson;
36  import org.kuali.rice.krad.bo.AdHocRouteRecipient;
37  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
38  import org.kuali.rice.krad.bo.DocumentHeader;
39  import org.kuali.rice.krad.bo.Note;
40  import org.kuali.rice.krad.document.Document;
41  import org.kuali.rice.krad.document.TransactionalDocument;
42  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
43  import org.kuali.rice.krad.rules.rule.AddAdHocRoutePersonRule;
44  import org.kuali.rice.krad.rules.rule.AddAdHocRouteWorkgroupRule;
45  import org.kuali.rice.krad.rules.rule.AddCollectionLineRule;
46  import org.kuali.rice.krad.rules.rule.AddNoteRule;
47  import org.kuali.rice.krad.rules.rule.ApproveDocumentRule;
48  import org.kuali.rice.krad.rules.rule.CompleteDocumentRule;
49  import org.kuali.rice.krad.rules.rule.RouteDocumentRule;
50  import org.kuali.rice.krad.rules.rule.SaveDocumentRule;
51  import org.kuali.rice.krad.rules.rule.SendAdHocRequestsRule;
52  import org.kuali.rice.krad.rules.rule.event.AddCollectionLineEvent;
53  import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent;
54  import org.kuali.rice.krad.service.DataDictionaryService;
55  import org.kuali.rice.krad.service.DictionaryValidationService;
56  import org.kuali.rice.krad.service.DocumentDictionaryService;
57  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
58  import org.kuali.rice.krad.uif.UifPropertyPaths;
59  import org.kuali.rice.krad.util.GlobalVariables;
60  import org.kuali.rice.krad.util.KRADConstants;
61  import org.kuali.rice.krad.util.KRADPropertyConstants;
62  import org.kuali.rice.krad.util.KRADUtils;
63  import org.kuali.rice.krad.util.MessageMap;
64  import org.kuali.rice.krad.util.RouteToCompletionUtil;
65  
66  import java.util.HashMap;
67  import java.util.List;
68  import java.util.Map;
69  
70  /**
71   * Contains all of the business rules that are common to all documents.
72   *
73   * @author Kuali Rice Team (rice.collab@kuali.org)
74   */
75  public abstract class DocumentRuleBase implements SaveDocumentRule, RouteDocumentRule, ApproveDocumentRule, AddNoteRule,
76          AddAdHocRoutePersonRule, AddAdHocRouteWorkgroupRule, SendAdHocRequestsRule, CompleteDocumentRule,
77          AddCollectionLineRule {
78      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentRuleBase.class);
79  
80      private static PersonService personService;
81      private static DictionaryValidationService dictionaryValidationService;
82      private static DocumentDictionaryService documentDictionaryService;
83      private static ConfigurationService kualiConfigurationService;
84      private static GroupService groupService;
85      private static PermissionService permissionService;
86      private static DocumentTypeService documentTypeService;
87      private static DataDictionaryService dataDictionaryService;
88  
89      // just some arbitrarily high max depth that's unlikely to occur in real life to prevent recursion problems
90      private int maxDictionaryValidationDepth = 100;
91  
92      /**
93       * Verifies that the document's overview fields are valid - it does required and format checks.
94       *
95       * @param document
96       * @return boolean True if the document description is valid, false otherwise.
97       */
98      public boolean isDocumentOverviewValid(Document document) {
99          // add in the documentHeader path
100         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
101         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_HEADER_PROPERTY_NAME);
102 
103         // check the document header for fields like the description
104         getDictionaryValidationService().validateBusinessObject(document.getDocumentHeader());
105         validateSensitiveDataValue(KRADPropertyConstants.EXPLANATION, document.getDocumentHeader().getExplanation(),
106                 getDataDictionaryService().getAttributeLabel(DocumentHeader.class, KRADPropertyConstants.EXPLANATION));
107         validateSensitiveDataValue(KRADPropertyConstants.DOCUMENT_DESCRIPTION,
108                 document.getDocumentHeader().getDocumentDescription(), getDataDictionaryService().getAttributeLabel(
109                 DocumentHeader.class, KRADPropertyConstants.DOCUMENT_DESCRIPTION));
110 
111         // drop the error path keys off now
112         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_HEADER_PROPERTY_NAME);
113         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
114 
115         return GlobalVariables.getMessageMap().hasNoErrors();
116     }
117 
118     /**
119      * Validates the document attributes against the data dictionary.
120      *
121      * @param document
122      * @param validateRequired if true, then an error will be retruned if a DD required field is empty. if false, no
123      * required
124      * checking is done
125      * @return True if the document attributes are valid, false otherwise.
126      */
127     public boolean isDocumentAttributesValid(Document document, boolean validateRequired) {
128         // start updating the error path name
129         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
130 
131         // check the document for fields like explanation and org doc #
132         getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document,
133                 getMaxDictionaryValidationDepth(), validateRequired);
134 
135         // drop the error path keys off now
136         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
137 
138         return GlobalVariables.getMessageMap().hasNoErrors();
139     }
140 
141     /**
142      * Runs all business rules needed prior to saving. This includes both common rules for all documents, plus
143      * class-specific
144      * business rules. This method will only return false if it fails the isValidForSave() test. Otherwise, it will
145      * always return
146      * positive regardless of the outcome of the business rules. However, any error messages resulting from the business
147      * rules will
148      * still be populated, for display to the consumer of this service.
149      *
150      * @see org.kuali.rice.krad.rules.rule.SaveDocumentRule#processSaveDocument(org.kuali.rice.krad.document.Document)
151      */
152     public boolean processSaveDocument(Document document) {
153         boolean isValid = true;
154 
155         isValid = isDocumentOverviewValid(document);
156 
157         GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
158 
159         getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document,
160                 getMaxDictionaryValidationDepth(), false);
161         getDictionaryValidationService().validateDefaultExistenceChecksForTransDoc((TransactionalDocument) document);
162 
163         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
164 
165         isValid &= GlobalVariables.getMessageMap().hasNoErrors();
166         isValid &= processCustomSaveDocumentBusinessRules(document);
167 
168         return isValid;
169     }
170 
171     /**
172      * This method should be overridden by children rule classes as a hook to implement document specific business rule
173      * checks for
174      * the "save document" event.
175      *
176      * @param document
177      * @return boolean True if the rules checks passed, false otherwise.
178      */
179     protected boolean processCustomSaveDocumentBusinessRules(Document document) {
180         return true;
181     }
182 
183     /**
184      * Runs all business rules needed prior to routing. This includes both common rules for all maintenance documents,
185      * plus
186      * class-specific business rules. This method will return false if any business rule fails, or if the document is in
187      * an invalid
188      * state, and not routable (see isDocumentValidForRouting()).
189      *
190      * @see org.kuali.rice.krad.rules.rule.RouteDocumentRule#processRouteDocument(org.kuali.rice.krad.document.Document)
191      */
192     public boolean processRouteDocument(Document document) {
193         boolean isValid = true;
194 
195         isValid = isDocumentOverviewValid(document);
196 
197         boolean completeRequestPending = RouteToCompletionUtil.checkIfAtleastOneAdHocCompleteRequestExist(document);
198 
199         // Validate the document if the header is valid and no pending completion requests
200         if (isValid && !completeRequestPending) {
201             isValid &= isDocumentAttributesValid(document, true);
202             isValid &= processCustomRouteDocumentBusinessRules(document);
203         }
204 
205         return isValid;
206     }
207 
208     /**
209      * This method should be overridden by children rule classes as a hook to implement document specific business rule
210      * checks for
211      * the "route document" event.
212      *
213      * @param document
214      * @return boolean True if the rules checks passed, false otherwise.
215      */
216     protected boolean processCustomRouteDocumentBusinessRules(Document document) {
217         return true;
218     }
219 
220     /**
221      * Runs all business rules needed prior to approving. This includes both common rules for all documents, plus
222      * class-specific
223      * business rules. This method will return false if any business rule fails, or if the document is in an invalid
224      * state, and not
225      * approveble.
226      *
227      * @see org.kuali.rice.krad.rules.rule.ApproveDocumentRule#processApproveDocument(org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent)
228      */
229     public boolean processApproveDocument(ApproveDocumentEvent approveEvent) {
230         boolean isValid = true;
231 
232         isValid = processCustomApproveDocumentBusinessRules(approveEvent);
233 
234         return isValid;
235     }
236 
237     /**
238      * This method should be overridden by children rule classes as a hook to implement document specific business rule
239      * checks for
240      * the "approve document" event.
241      *
242      * @param approveEvent
243      * @return boolean True if the rules checks passed, false otherwise.
244      */
245     protected boolean processCustomApproveDocumentBusinessRules(ApproveDocumentEvent approveEvent) {
246         return true;
247     }
248 
249     /**
250      * {@inheritDoc}
251      *
252      * This base implementation just runs the custom rules.
253      */
254     @Override
255     public boolean processAddCollectionLine(AddCollectionLineEvent addEvent) {
256         boolean isValid = true;
257 
258         isValid = processCustomAddCollectionLineBusinessRules(addEvent);
259 
260         return isValid;
261     }
262 
263 
264     /**
265      * This method should be overridden to provide custom rules for processing adding to collections.
266      *
267      * @param addEvent the event containing all of the object necessary to run the rules
268      *
269      * @return true if validation succeeds, false otherwise
270      */
271     protected boolean processCustomAddCollectionLineBusinessRules(AddCollectionLineEvent addEvent) {
272         return true;
273     }
274 
275     /**
276      * Runs all business rules needed prior to adding a document note. This method will return false if any business
277      * rule fails
278      */
279     public boolean processAddNote(Document document, Note note) {
280         boolean isValid = true;
281 
282         isValid &= isNoteValid(note);
283         isValid &= processCustomAddNoteBusinessRules(document, note);
284 
285         return isValid;
286     }
287 
288     /**
289      * Verifies that the note's fields are valid - it does required and format checks.
290      *
291      * @param note
292      * @return boolean True if the document description is valid, false otherwise.
293      */
294     public boolean isNoteValid(Note note) {
295         // add the error path keys on the stack
296         GlobalVariables.getMessageMap().addToErrorPath(UifPropertyPaths.NEW_COLLECTION_LINES
297                 + "['"
298                 + KRADConstants.DOCUMENT_PROPERTY_NAME
299                 + "."
300                 + KRADConstants.NOTES_PROPERTY_NAME
301                 + "']");
302 
303         // check the document header for fields like the description
304         getDictionaryValidationService().validateBusinessObject(note);
305 
306         validateSensitiveDataValue(KRADConstants.NOTE_TEXT_PROPERTY_NAME, note.getNoteText(),
307                 getDataDictionaryService().getAttributeLabel(Note.class, KRADConstants.NOTE_TEXT_PROPERTY_NAME));
308 
309         // drop the error path keys off now
310         GlobalVariables.getMessageMap().removeFromErrorPath(UifPropertyPaths.NEW_COLLECTION_LINES
311                 + "['"
312                 + KRADConstants.DOCUMENT_PROPERTY_NAME
313                 + "."
314                 + KRADConstants.NOTES_PROPERTY_NAME
315                 + "']");
316 
317         return GlobalVariables.getMessageMap().hasNoErrors();
318     }
319 
320     /**
321      * This method should be overridden by children rule classes as a hook to implement document specific business rule
322      * checks for
323      * the "add document note" event.
324      *
325      * @param document
326      * @param note
327      * @return boolean True if the rules checks passed, false otherwise.
328      */
329     protected boolean processCustomAddNoteBusinessRules(Document document, Note note) {
330         return true;
331     }
332 
333     /**
334      * @see org.kuali.rice.krad.rules.rule.AddAdHocRoutePersonRule#processAddAdHocRoutePerson(org.kuali.rice.krad.document.Document,
335      *      org.kuali.rice.krad.bo.AdHocRoutePerson)
336      */
337     public boolean processAddAdHocRoutePerson(Document document, AdHocRoutePerson adHocRoutePerson) {
338         boolean isValid = true;
339 
340         isValid &= isAddHocRoutePersonValid(document, adHocRoutePerson);
341 
342         isValid &= processCustomAddAdHocRoutePersonBusinessRules(document, adHocRoutePerson);
343         return isValid;
344     }
345 
346     /**
347      * @see org.kuali.rice.krad.rules.rule.SendAdHocRequestsRule#processSendAdHocRequests(org.kuali.rice.krad.document.Document)
348      */
349     public boolean processSendAdHocRequests(Document document) {
350         boolean isValid = true;
351 
352         isValid &= isAdHocRouteRecipientsValid(document);
353         isValid &= processCustomSendAdHocRequests(document);
354 
355         return isValid;
356     }
357 
358     protected boolean processCustomSendAdHocRequests(Document document) {
359         return true;
360     }
361 
362     /**
363      * Checks the adhoc route recipient list to ensure there are recipients or
364      * else throws an error that at least one recipient is required.
365      *
366      * @param document
367      * @return true if all adhoc route recipients are valid
368      */
369     protected boolean isAdHocRouteRecipientsValid(Document document) {
370         boolean isValid = true;
371         MessageMap errorMap = GlobalVariables.getMessageMap();
372 
373         if (errorMap.getErrorPath().size() == 0) {
374             // add the error path keys on the stack
375             errorMap.addToErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
376         }
377 
378         if ((document.getAdHocRoutePersons() == null || document.getAdHocRoutePersons().isEmpty()) && (document
379                 .getAdHocRouteWorkgroups() == null || document.getAdHocRouteWorkgroups().isEmpty())) {
380 
381             GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID, "error.adhoc.missing.recipients");
382             isValid = false;
383         }
384 
385         // drop the error path keys off now
386         errorMap.removeFromErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
387 
388         return isValid;
389     }
390 
391     /**
392      * Verifies that the adHocRoutePerson's fields are valid - it does required and format checks.
393      *
394      * @param person
395      * @return boolean True if valid, false otherwise.
396      */
397     public boolean isAddHocRoutePersonValid(Document document, AdHocRoutePerson person) {
398         MessageMap errorMap = GlobalVariables.getMessageMap();
399 
400         // new recipients are not embedded in the error path; existing lines should be
401         if (errorMap.getErrorPath().size() == 0) {
402             // add the error path keys on the stack
403             errorMap.addToErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
404         }
405 
406         String actionRequestedCode = person.getActionRequested();
407         if (StringUtils.isNotBlank(person.getId())) {
408             Person user = getPersonService().getPersonByPrincipalName(person.getId());
409 
410             if (user == null) {
411                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
412                         RiceKeyConstants.ERROR_INVALID_ADHOC_PERSON_ID);
413             } 
414             else if (!getPermissionService().hasPermission(user.getPrincipalId(),
415                     KimConstants.KIM_TYPE_DEFAULT_NAMESPACE, KimConstants.PermissionNames.LOG_IN)) {
416                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
417                         RiceKeyConstants.ERROR_INACTIVE_ADHOC_PERSON_ID);
418             }
419             else if(this.isAdHocRouteCompletionToInitiator(document, user, actionRequestedCode)){
420                 // KULRICE-7419: Adhoc route completion validation rule (should not route to initiator for completion)
421                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
422                         RiceKeyConstants.ERROR_ADHOC_COMPLETE_PERSON_IS_INITIATOR);
423             } 
424             else if(StringUtils.equals(actionRequestedCode, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ) && this.hasAdHocRouteCompletion(document, person)){
425                 // KULRICE-8760: Multiple complete adhoc requests should not be allowed on the same document
426                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
427                         RiceKeyConstants.ERROR_ADHOC_COMPLETE_MORE_THAN_ONE);
428             }
429             else {
430                 Class docOrBoClass = null;
431                 if (document instanceof MaintenanceDocument) {
432                     docOrBoClass = ((MaintenanceDocument) document).getNewMaintainableObject().getDataObjectClass();
433                 } else {
434                     docOrBoClass = document.getClass();
435                 }
436 
437                 if (!getDocumentDictionaryService().getDocumentAuthorizer(document).canReceiveAdHoc(document, user, actionRequestedCode)) {
438                     GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
439                             RiceKeyConstants.ERROR_UNAUTHORIZED_ADHOC_PERSON_ID);
440                 }
441             }
442         } else {
443             GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
444                     RiceKeyConstants.ERROR_MISSING_ADHOC_PERSON_ID);
445         }
446 
447         // drop the error path keys off now
448         errorMap.removeFromErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
449 
450         return GlobalVariables.getMessageMap().hasNoErrors();
451     }
452     
453     /**
454      * KULRICE-7419: Adhoc route completion validation rule (should not route to initiator for completion)
455      * 
456      * determine whether the document initiator is the same as the adhoc recipient for completion
457      */
458     protected boolean isAdHocRouteCompletionToInitiator(Document document, Person person, String actionRequestCode){
459         if(!StringUtils.equals(actionRequestCode, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ)){
460             return false;
461         }
462         
463         String documentInitiator = document.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();       
464         String adhocRecipient = person.getPrincipalId();
465         
466         return StringUtils.equals(documentInitiator, adhocRecipient);
467     }
468     
469     /**
470      * KULRICE-8760: check whether there is any other complete adhoc request on the given document 
471      */
472     protected boolean hasAdHocRouteCompletion(Document document, AdHocRouteRecipient adHocRouteRecipient){         
473         List<AdHocRoutePerson> adHocRoutePersons = document.getAdHocRoutePersons();
474         if(KRADUtils.isNotNull(adHocRoutePersons)){
475             for(AdHocRoutePerson adhocRecipient : adHocRoutePersons){
476                 // the given adhoc route recipient doesn't count
477                 if(adHocRouteRecipient==adhocRecipient){
478                     continue;
479                 }
480                 
481                 String actionRequestCode = adhocRecipient.getActionRequested();
482                 if(StringUtils.equals(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, actionRequestCode)){
483                     return true;
484                 }
485             }
486         }
487         
488         List<AdHocRouteWorkgroup> adHocRouteWorkgroups = document.getAdHocRouteWorkgroups();
489         if(KRADUtils.isNotNull(adHocRouteWorkgroups)){
490             for(AdHocRouteWorkgroup adhocRecipient : adHocRouteWorkgroups){
491                 // the given adhoc route recipient doesn't count
492                 if(adHocRouteRecipient==adhocRecipient){
493                     continue;
494                 }
495                 
496                 String actionRequestCode = adhocRecipient.getActionRequested();
497                 if(StringUtils.equals(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, actionRequestCode)){
498                     return true;
499                 }
500             }
501         }        
502         
503         return false;
504     }    
505     
506     /**
507      * This method should be overridden by children rule classes as a hook to implement document specific business rule
508      * checks for
509      * the "add ad hoc route person" event.
510      *
511      * @param document
512      * @param person
513      * @return boolean True if the rules checks passed, false otherwise.
514      */
515     protected boolean processCustomAddAdHocRoutePersonBusinessRules(Document document, AdHocRoutePerson person) {
516         return true;
517     }
518 
519     /**
520      * @see org.kuali.rice.krad.rules.rule.AddAdHocRouteWorkgroupRule#processAddAdHocRouteWorkgroup(org.kuali.rice.krad.document.Document,
521      *      org.kuali.rice.krad.bo.AdHocRouteWorkgroup)
522      */
523     public boolean processAddAdHocRouteWorkgroup(Document document, AdHocRouteWorkgroup adHocRouteWorkgroup) {
524         boolean isValid = true;
525 
526         isValid &= isAddHocRouteWorkgroupValid(document, adHocRouteWorkgroup);
527 
528         isValid &= processCustomAddAdHocRouteWorkgroupBusinessRules(document, adHocRouteWorkgroup);
529         return isValid;
530     }
531 
532     /**
533      * Verifies that the adHocRouteWorkgroup's fields are valid - it does required and format checks.
534      *
535      * @param workgroup
536      * @return boolean True if valid, false otherwise.
537      */
538     public boolean isAddHocRouteWorkgroupValid(Document document, AdHocRouteWorkgroup workgroup) {
539         MessageMap errorMap = GlobalVariables.getMessageMap();
540 
541         // new recipients are not embedded in the error path; existing lines should be
542         if (errorMap.getErrorPath().size() == 0) {
543             // add the error path keys on the stack
544             GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME);
545         }
546 
547         if (workgroup.getRecipientName() != null && workgroup.getRecipientNamespaceCode() != null) {
548             // validate that they are a workgroup from the workgroup service by looking them up
549             try {
550                 Group group = getGroupService().getGroupByNamespaceCodeAndName(workgroup.getRecipientNamespaceCode(),
551                         workgroup.getRecipientName());
552                 
553                 String actionRequestedCode = workgroup.getActionRequested();
554                 if (group == null || !group.isActive()) {
555                     //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
556                     GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
557                             RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_ID);
558                     GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE);
559                 } 
560                 else if(StringUtils.equals(actionRequestedCode, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ) && this.hasAdHocRouteCompletion(document, workgroup)){
561                     // KULRICE-8760: Multiple complete adhoc requests should not be allowed on the same document
562                     GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE,
563                             RiceKeyConstants.ERROR_ADHOC_COMPLETE_MORE_THAN_ONE);
564                 }
565                 else {
566                     org.kuali.rice.kew.api.document.WorkflowDocumentService
567                             wds = KewApiServiceLocator.getWorkflowDocumentService();
568                     DocumentType documentType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(
569                             wds.getDocument(document.getDocumentNumber()).getDocumentTypeName());
570                     Map<String, String> permissionDetails = buildDocumentTypeActionRequestPermissionDetails(
571                             documentType, workgroup.getActionRequested());
572                     if (useKimPermission(KewApiConstants.KEW_NAMESPACE, KewApiConstants.AD_HOC_REVIEW_PERMISSION, permissionDetails) ){
573                         List<String> principalIds = getGroupService().getMemberPrincipalIds(group.getId());
574                         // if any member of the group is not allowed to receive the request, then the group may not receive it
575                         for (String principalId : principalIds) {
576                             if (!getPermissionService().isAuthorizedByTemplate(principalId,
577                                     KewApiConstants.KEW_NAMESPACE, KewApiConstants.AD_HOC_REVIEW_PERMISSION,
578                                     permissionDetails, new HashMap<String, String>())) {
579                                 
580                                 //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
581                                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
582                                         RiceKeyConstants.ERROR_UNAUTHORIZED_ADHOC_WORKGROUP_ID);
583                                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE);
584                                 
585                                 break;
586                             }
587                         }
588                     }
589                 }
590             } catch (Exception e) {
591                 LOG.error("isAddHocRouteWorkgroupValid(AdHocRouteWorkgroup)", e);
592                 
593                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
594                         RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_ID);
595                 
596                 //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
597                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE);
598             }
599         } else {
600             //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
601             if(workgroup.getRecipientNamespaceCode()==null) {
602                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE_MISSING);
603             }
604             
605             if(workgroup.getRecipientName()==null) {
606                 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
607                     RiceKeyConstants.ERROR_MISSING_ADHOC_WORKGROUP_ID);
608             }
609         }
610 
611         // drop the error path keys off now
612         GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME);
613 
614         return GlobalVariables.getMessageMap().hasNoErrors();
615     }
616     /**
617      * This method should be overridden by children rule classes as a hook to implement document specific business rule
618      * checks for
619      * the "add ad hoc route workgroup" event.
620      *
621      * @param document
622      * @param workgroup
623      * @return boolean True if the rules checks passed, false otherwise.
624      */
625     protected boolean processCustomAddAdHocRouteWorkgroupBusinessRules(Document document,
626             AdHocRouteWorkgroup workgroup) {
627         return true;
628     }
629 
630     /**
631      * Gets the maximum number of levels the data-dictionary based validation will recurse for the document
632      */
633     public int getMaxDictionaryValidationDepth() {
634         return this.maxDictionaryValidationDepth;
635     }
636 
637     /**
638      * Gets the maximum number of levels the data-dictionary based validation will recurse for the document
639      */
640     public void setMaxDictionaryValidationDepth(int maxDictionaryValidationDepth) {
641         if (maxDictionaryValidationDepth < 0) {
642             LOG.error("Dictionary validation depth should be greater than or equal to 0.  Value received was: "
643                     + maxDictionaryValidationDepth);
644             throw new RuntimeException(
645                     "Dictionary validation depth should be greater than or equal to 0.  Value received was: "
646                             + maxDictionaryValidationDepth);
647         }
648         this.maxDictionaryValidationDepth = maxDictionaryValidationDepth;
649     }
650 
651     protected boolean validateSensitiveDataValue(String fieldName, String fieldValue, String fieldLabel) {
652         boolean dataValid = true;
653 
654         if (fieldValue == null) {
655             return dataValid;
656         }
657 
658         boolean patternFound = KRADUtils.containsSensitiveDataPatternMatch(fieldValue);
659         boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
660                 KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
661                 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
662         if (patternFound && !warnForSensitiveData) {
663             dataValid = false;
664             GlobalVariables.getMessageMap().putError(fieldName,
665                     RiceKeyConstants.ERROR_DOCUMENT_FIELD_CONTAINS_POSSIBLE_SENSITIVE_DATA, fieldLabel);
666         }
667 
668         return dataValid;
669     }
670 
671     /**
672      * Business rules check will include all save action rules and any custom rules required by the document specific rule implementation
673      *
674      * @param document Document
675      * @return true if all validations are passed
676      */
677     public boolean processCompleteDocument(Document document) {
678         boolean isValid = true;
679         isValid &= processSaveDocument(document);
680         isValid &= processCustomCompleteDocumentBusinessRules(document);
681         return isValid;
682     }
683 
684     /**
685      * Hook method for deriving business rule classes to provide custom validations required during completion action
686      *
687      * @param document
688      * @return default is true
689      */
690     protected boolean processCustomCompleteDocumentBusinessRules(Document document) {
691         return true;
692     }
693 
694     protected boolean useKimPermission(String namespace, String permissionTemplateName, Map<String, String> permissionDetails) {
695 		Boolean b =  CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.KIM_PRIORITY_ON_DOC_TYP_PERMS_IND);
696 		if (b == null || b) {
697 			return getPermissionService().isPermissionDefinedByTemplate(namespace, permissionTemplateName,
698                     permissionDetails);
699 		}
700 		return false;
701 	}
702     protected Map<String, String> buildDocumentTypeActionRequestPermissionDetails(DocumentType documentType, String actionRequestCode) {
703 		Map<String, String> details = buildDocumentTypePermissionDetails(documentType);
704 		if (!StringUtils.isBlank(actionRequestCode)) {
705 			details.put(KewApiConstants.ACTION_REQUEST_CD_DETAIL, actionRequestCode);
706 		}
707 		return details;
708 	}
709 
710     protected Map<String, String> buildDocumentTypePermissionDetails(DocumentType documentType) {
711 		Map<String, String> details = new HashMap<String, String>();
712 		details.put(KewApiConstants.DOCUMENT_TYPE_NAME_DETAIL, documentType.getName());
713 		return details;
714 	}
715 
716     protected DataDictionaryService getDataDictionaryService() {
717         if (dataDictionaryService == null) {
718             dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
719         }
720         return dataDictionaryService;
721     }
722 
723     protected PersonService getPersonService() {
724         if (personService == null) {
725             personService = KimApiServiceLocator.getPersonService();
726         }
727         return personService;
728     }
729 
730     public static GroupService getGroupService() {
731         if (groupService == null) {
732             groupService = KimApiServiceLocator.getGroupService();
733         }
734         return groupService;
735     }
736 
737     public static PermissionService getPermissionService() {
738         if (permissionService == null) {
739             permissionService = KimApiServiceLocator.getPermissionService();
740         }
741         return permissionService;
742     }
743 
744     protected DictionaryValidationService getDictionaryValidationService() {
745         if (dictionaryValidationService == null) {
746             dictionaryValidationService = KRADServiceLocatorWeb.getDictionaryValidationService();
747         }
748         return dictionaryValidationService;
749     }
750 
751     protected ConfigurationService getKualiConfigurationService() {
752         if (kualiConfigurationService == null) {
753             kualiConfigurationService = CoreApiServiceLocator.getKualiConfigurationService();
754         }
755         return kualiConfigurationService;
756     }
757 
758     protected static DocumentDictionaryService getDocumentDictionaryService() {
759         if (documentDictionaryService == null) {
760             documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
761         }
762         return documentDictionaryService;
763     }
764 
765     public static void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
766         DocumentRuleBase.documentDictionaryService = documentDictionaryService;
767     }
768 }