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