001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.rules;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.CoreApiServiceLocator;
020import org.kuali.rice.core.api.config.property.ConfigurationService;
021import org.kuali.rice.core.api.util.RiceKeyConstants;
022import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
023import org.kuali.rice.coreservice.framework.parameter.ParameterConstants;
024import org.kuali.rice.kew.api.KewApiConstants;
025import org.kuali.rice.kew.api.KewApiServiceLocator;
026import org.kuali.rice.kew.api.doctype.DocumentType;
027import org.kuali.rice.kew.api.doctype.DocumentTypeService;
028import org.kuali.rice.kim.api.KimConstants;
029import org.kuali.rice.kim.api.group.Group;
030import org.kuali.rice.kim.api.group.GroupService;
031import org.kuali.rice.kim.api.identity.Person;
032import org.kuali.rice.kim.api.identity.PersonService;
033import org.kuali.rice.kim.api.permission.PermissionService;
034import org.kuali.rice.kim.api.services.KimApiServiceLocator;
035import org.kuali.rice.krad.bo.AdHocRoutePerson;
036import org.kuali.rice.krad.bo.AdHocRouteRecipient;
037import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
038import org.kuali.rice.krad.bo.DocumentHeader;
039import org.kuali.rice.krad.bo.Note;
040import org.kuali.rice.krad.document.Document;
041import org.kuali.rice.krad.document.TransactionalDocument;
042import org.kuali.rice.krad.maintenance.MaintenanceDocument;
043import org.kuali.rice.krad.rules.rule.AddAdHocRoutePersonRule;
044import org.kuali.rice.krad.rules.rule.AddAdHocRouteWorkgroupRule;
045import org.kuali.rice.krad.rules.rule.AddCollectionLineRule;
046import org.kuali.rice.krad.rules.rule.AddNoteRule;
047import org.kuali.rice.krad.rules.rule.ApproveDocumentRule;
048import org.kuali.rice.krad.rules.rule.CompleteDocumentRule;
049import org.kuali.rice.krad.rules.rule.RouteDocumentRule;
050import org.kuali.rice.krad.rules.rule.SaveDocumentRule;
051import org.kuali.rice.krad.rules.rule.SendAdHocRequestsRule;
052import org.kuali.rice.krad.rules.rule.event.AddCollectionLineEvent;
053import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent;
054import org.kuali.rice.krad.service.DataDictionaryService;
055import org.kuali.rice.krad.service.DictionaryValidationService;
056import org.kuali.rice.krad.service.DocumentDictionaryService;
057import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
058import org.kuali.rice.krad.uif.UifPropertyPaths;
059import org.kuali.rice.krad.util.GlobalVariables;
060import org.kuali.rice.krad.util.KRADConstants;
061import org.kuali.rice.krad.util.KRADPropertyConstants;
062import org.kuali.rice.krad.util.KRADUtils;
063import org.kuali.rice.krad.util.MessageMap;
064import org.kuali.rice.krad.util.RouteToCompletionUtil;
065
066import java.util.HashMap;
067import java.util.List;
068import java.util.Map;
069
070/**
071 * Contains all of the business rules that are common to all documents.
072 *
073 * @author Kuali Rice Team (rice.collab@kuali.org)
074 */
075public abstract class DocumentRuleBase implements SaveDocumentRule, RouteDocumentRule, ApproveDocumentRule, AddNoteRule,
076        AddAdHocRoutePersonRule, AddAdHocRouteWorkgroupRule, SendAdHocRequestsRule, CompleteDocumentRule,
077        AddCollectionLineRule {
078    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentRuleBase.class);
079
080    private static PersonService personService;
081    private static DictionaryValidationService dictionaryValidationService;
082    private static DocumentDictionaryService documentDictionaryService;
083    private static ConfigurationService kualiConfigurationService;
084    private static GroupService groupService;
085    private static PermissionService permissionService;
086    private static DocumentTypeService documentTypeService;
087    private static DataDictionaryService dataDictionaryService;
088
089    // just some arbitrarily high max depth that's unlikely to occur in real life to prevent recursion problems
090    private int maxDictionaryValidationDepth = 100;
091
092    /**
093     * Verifies that the document's overview fields are valid - it does required and format checks.
094     *
095     * @param document
096     * @return boolean True if the document description is valid, false otherwise.
097     */
098    public boolean isDocumentOverviewValid(Document document) {
099        // 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}