View Javadoc

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