001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.krad.rules;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.core.api.config.property.ConfigurationService;
020    import org.kuali.rice.core.api.util.RiceKeyConstants;
021    import org.kuali.rice.coreservice.framework.parameter.ParameterConstants;
022    import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
023    import org.kuali.rice.kew.api.KewApiConstants;
024    import org.kuali.rice.kew.api.KewApiServiceLocator;
025    import org.kuali.rice.kew.api.doctype.DocumentType;
026    import org.kuali.rice.kew.api.doctype.DocumentTypeService;
027    import org.kuali.rice.kim.api.KimConstants;
028    import org.kuali.rice.kim.api.group.Group;
029    import org.kuali.rice.kim.api.group.GroupService;
030    import org.kuali.rice.kim.api.identity.Person;
031    import org.kuali.rice.kim.api.identity.PersonService;
032    import org.kuali.rice.kim.api.permission.PermissionService;
033    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
034    import org.kuali.rice.krad.bo.AdHocRoutePerson;
035    import org.kuali.rice.krad.bo.AdHocRouteRecipient;
036    import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
037    import org.kuali.rice.krad.bo.DocumentHeader;
038    import org.kuali.rice.krad.bo.Note;
039    import org.kuali.rice.krad.document.Document;
040    import org.kuali.rice.krad.maintenance.MaintenanceDocument;
041    import org.kuali.rice.krad.document.TransactionalDocument;
042    import org.kuali.rice.krad.rules.rule.AddAdHocRoutePersonRule;
043    import org.kuali.rice.krad.rules.rule.AddAdHocRouteWorkgroupRule;
044    import org.kuali.rice.krad.rules.rule.AddNoteRule;
045    import org.kuali.rice.krad.rules.rule.ApproveDocumentRule;
046    import org.kuali.rice.krad.rules.rule.CompleteDocumentRule;
047    import org.kuali.rice.krad.rules.rule.RouteDocumentRule;
048    import org.kuali.rice.krad.rules.rule.SaveDocumentRule;
049    import org.kuali.rice.krad.rules.rule.SendAdHocRequestsRule;
050    import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent;
051    import org.kuali.rice.krad.service.DataDictionaryService;
052    import org.kuali.rice.krad.service.DictionaryValidationService;
053    import org.kuali.rice.krad.service.DocumentDictionaryService;
054    import org.kuali.rice.krad.service.KRADServiceLocator;
055    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
056    import org.kuali.rice.krad.util.GlobalVariables;
057    import org.kuali.rice.krad.util.KRADConstants;
058    import org.kuali.rice.krad.util.KRADPropertyConstants;
059    import org.kuali.rice.krad.util.KRADUtils;
060    import org.kuali.rice.krad.util.MessageMap;
061    import org.kuali.rice.krad.util.ObjectUtils;
062    import org.kuali.rice.krad.util.RouteToCompletionUtil;
063    import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
064    
065    import java.util.HashMap;
066    import java.util.List;
067    import java.util.Map;
068    
069    /**
070     * Contains all of the business rules that are common to all documents
071     *
072     * @author Kuali Rice Team (rice.collab@kuali.org)
073     */
074    public abstract class DocumentRuleBase implements SaveDocumentRule, RouteDocumentRule, ApproveDocumentRule, AddNoteRule,
075            AddAdHocRoutePersonRule, AddAdHocRouteWorkgroupRule, SendAdHocRequestsRule, CompleteDocumentRule {
076        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentRuleBase.class);
077    
078        private static PersonService personService;
079        private static DictionaryValidationService dictionaryValidationService;
080        private static DocumentDictionaryService documentDictionaryService;
081        private static ConfigurationService kualiConfigurationService;
082        private static GroupService groupService;
083        private static PermissionService permissionService;
084        private static DocumentTypeService documentTypeService;
085        private static DataDictionaryService dataDictionaryService;
086    
087        // just some arbitrarily high max depth that's unlikely to occur in real life to prevent recursion problems
088        private int maxDictionaryValidationDepth = 100;
089    
090        /**
091         * Verifies that the document's overview fields are valid - it does required and format checks.
092         *
093         * @param document
094         * @return boolean True if the document description is valid, false otherwise.
095         */
096        public boolean isDocumentOverviewValid(Document document) {
097            // add in the documentHeader path
098            GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
099            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 = isDocumentAttributesValid(document, true);
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 &= processCustomRouteDocumentBusinessRules(document);
197            }
198    
199            return isValid;
200        }
201    
202        /**
203         * This method should be overridden by children rule classes as a hook to implement document specific business rule
204         * checks for
205         * the "route document" event.
206         *
207         * @param document
208         * @return boolean True if the rules checks passed, false otherwise.
209         */
210        protected boolean processCustomRouteDocumentBusinessRules(Document document) {
211            return true;
212        }
213    
214        /**
215         * Runs all business rules needed prior to approving. This includes both common rules for all documents, plus
216         * class-specific
217         * business rules. This method will return false if any business rule fails, or if the document is in an invalid
218         * state, and not
219         * approveble.
220         *
221         * @see org.kuali.rice.krad.rules.rule.ApproveDocumentRule#processApproveDocument(org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent)
222         */
223        public boolean processApproveDocument(ApproveDocumentEvent approveEvent) {
224            boolean isValid = true;
225    
226            isValid = processCustomApproveDocumentBusinessRules(approveEvent);
227    
228            return isValid;
229        }
230    
231        /**
232         * This method should be overridden by children rule classes as a hook to implement document specific business rule
233         * checks for
234         * the "approve document" event.
235         *
236         * @param approveEvent
237         * @return boolean True if the rules checks passed, false otherwise.
238         */
239        protected boolean processCustomApproveDocumentBusinessRules(ApproveDocumentEvent approveEvent) {
240            return true;
241        }
242    
243        /**
244         * Runs all business rules needed prior to adding a document note. This method will return false if any business
245         * rule fails
246         */
247        public boolean processAddNote(Document document, Note note) {
248            boolean isValid = true;
249    
250            isValid &= isNoteValid(note);
251            isValid &= processCustomAddNoteBusinessRules(document, note);
252    
253            return isValid;
254        }
255    
256        /**
257         * Verifies that the note's fields are valid - it does required and format checks.
258         *
259         * @param note
260         * @return boolean True if the document description is valid, false otherwise.
261         */
262        public boolean isNoteValid(Note note) {
263            // add the error path keys on the stack
264            GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME);
265    
266            // check the document header for fields like the description
267            getDictionaryValidationService().validateBusinessObject(note);
268    
269            validateSensitiveDataValue(KRADConstants.NOTE_TEXT_PROPERTY_NAME, note.getNoteText(),
270                    getDataDictionaryService().getAttributeLabel(Note.class, KRADConstants.NOTE_TEXT_PROPERTY_NAME));
271    
272            // drop the error path keys off now
273            GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.NEW_DOCUMENT_NOTE_PROPERTY_NAME);
274    
275            return GlobalVariables.getMessageMap().hasNoErrors();
276        }
277    
278        /**
279         * This method should be overridden by children rule classes as a hook to implement document specific business rule
280         * checks for
281         * the "add document note" event.
282         *
283         * @param document
284         * @param note
285         * @return boolean True if the rules checks passed, false otherwise.
286         */
287        protected boolean processCustomAddNoteBusinessRules(Document document, Note note) {
288            return true;
289        }
290    
291        /**
292         * @see org.kuali.rice.krad.rules.rule.AddAdHocRoutePersonRule#processAddAdHocRoutePerson(org.kuali.rice.krad.document.Document,
293         *      org.kuali.rice.krad.bo.AdHocRoutePerson)
294         */
295        public boolean processAddAdHocRoutePerson(Document document, AdHocRoutePerson adHocRoutePerson) {
296            boolean isValid = true;
297    
298            isValid &= isAddHocRoutePersonValid(document, adHocRoutePerson);
299    
300            isValid &= processCustomAddAdHocRoutePersonBusinessRules(document, adHocRoutePerson);
301            return isValid;
302        }
303    
304        /**
305         * @see org.kuali.rice.krad.rules.rule.SendAdHocRequestsRule#processSendAdHocRequests(org.kuali.rice.krad.document.Document)
306         */
307        public boolean processSendAdHocRequests(Document document) {
308            boolean isValid = true;
309    
310            isValid &= isAdHocRouteRecipientsValid(document);
311            isValid &= processCustomSendAdHocRequests(document);
312    
313            return isValid;
314        }
315    
316        protected boolean processCustomSendAdHocRequests(Document document) {
317            return true;
318        }
319    
320        /**
321         * Checks the adhoc route recipient list to ensure there are recipients or
322         * else throws an error that at least one recipient is required.
323         *
324         * @param document
325         * @return
326         */
327        protected boolean isAdHocRouteRecipientsValid(Document document) {
328            boolean isValid = true;
329            MessageMap errorMap = GlobalVariables.getMessageMap();
330    
331            if (errorMap.getErrorPath().size() == 0) {
332                // add the error path keys on the stack
333                errorMap.addToErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
334            }
335    
336            if ((document.getAdHocRoutePersons() == null || document.getAdHocRoutePersons().isEmpty()) && (document
337                    .getAdHocRouteWorkgroups() == null || document.getAdHocRouteWorkgroups().isEmpty())) {
338    
339                GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID, "error.adhoc.missing.recipients");
340                isValid = false;
341            }
342    
343            // drop the error path keys off now
344            errorMap.removeFromErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
345    
346            return isValid;
347        }
348    
349        /**
350         * Verifies that the adHocRoutePerson's fields are valid - it does required and format checks.
351         *
352         * @param person
353         * @return boolean True if valid, false otherwise.
354         */
355        public boolean isAddHocRoutePersonValid(Document document, AdHocRoutePerson person) {
356            MessageMap errorMap = GlobalVariables.getMessageMap();
357    
358            // new recipients are not embedded in the error path; existing lines should be
359            if (errorMap.getErrorPath().size() == 0) {
360                // add the error path keys on the stack
361                errorMap.addToErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
362            }
363    
364            String actionRequestedCode = person.getActionRequested();
365            if (StringUtils.isNotBlank(person.getId())) {
366                Person user = getPersonService().getPersonByPrincipalName(person.getId());
367    
368                if (user == null) {
369                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
370                            RiceKeyConstants.ERROR_INVALID_ADHOC_PERSON_ID);
371                } 
372                else if (!getPermissionService().hasPermission(user.getPrincipalId(),
373                        KimConstants.KIM_TYPE_DEFAULT_NAMESPACE, KimConstants.PermissionNames.LOG_IN)) {
374                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
375                            RiceKeyConstants.ERROR_INACTIVE_ADHOC_PERSON_ID);
376                }
377                else if(this.isAdHocRouteCompletionToInitiator(document, user, actionRequestedCode)){
378                    // KULRICE-7419: Adhoc route completion validation rule (should not route to initiator for completion)
379                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
380                            RiceKeyConstants.ERROR_ADHOC_COMPLETE_PERSON_IS_INITIATOR);
381                } 
382                else if(StringUtils.equals(actionRequestedCode, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ) && this.hasAdHocRouteCompletion(document, person)){
383                    // KULRICE-8760: Multiple complete adhoc requests should not be allowed on the same document
384                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
385                            RiceKeyConstants.ERROR_ADHOC_COMPLETE_MORE_THAN_ONE);
386                }
387                else {
388                    Class docOrBoClass = null;
389                    if (document instanceof MaintenanceDocument) {
390                        docOrBoClass = ((MaintenanceDocument) document).getNewMaintainableObject().getDataObjectClass();
391                    } else {
392                        docOrBoClass = document.getClass();
393                    }
394    
395                    if (!getDocumentDictionaryService().getDocumentAuthorizer(document).canReceiveAdHoc(document, user, actionRequestedCode)) {
396                        GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
397                                RiceKeyConstants.ERROR_UNAUTHORIZED_ADHOC_PERSON_ID);
398                    }
399                }
400            } else {
401                GlobalVariables.getMessageMap().putError(KRADPropertyConstants.ID,
402                        RiceKeyConstants.ERROR_MISSING_ADHOC_PERSON_ID);
403            }
404    
405            // drop the error path keys off now
406            errorMap.removeFromErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_PERSON_PROPERTY_NAME);
407    
408            return GlobalVariables.getMessageMap().hasNoErrors();
409        }
410        
411        /**
412         * KULRICE-7419: Adhoc route completion validation rule (should not route to initiator for completion)
413         * 
414         * determine whether the document initiator is the same as the adhoc recipient for completion
415         */
416        protected boolean isAdHocRouteCompletionToInitiator(Document document, Person person, String actionRequestCode){
417            if(!StringUtils.equals(actionRequestCode, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ)){
418                return false;
419            }
420            
421            String documentInitiator = document.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();       
422            String adhocRecipient = person.getPrincipalId();
423            
424            return StringUtils.equals(documentInitiator, adhocRecipient);
425        }
426        
427        /**
428         * KULRICE-8760: check whether there is any other complete adhoc request on the given document 
429         */
430        protected boolean hasAdHocRouteCompletion(Document document, AdHocRouteRecipient adHocRouteRecipient){         
431            List<AdHocRoutePerson> adHocRoutePersons = document.getAdHocRoutePersons();
432            if(ObjectUtils.isNotNull(adHocRoutePersons)){
433                for(AdHocRoutePerson adhocRecipient : adHocRoutePersons){
434                    // the given adhoc route recipient doesn't count
435                    if(adHocRouteRecipient==adhocRecipient){
436                        continue;
437                    }
438                    
439                    String actionRequestCode = adhocRecipient.getActionRequested();
440                    if(StringUtils.equals(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, actionRequestCode)){
441                        return true;
442                    }
443                }
444            }
445            
446            List<AdHocRouteWorkgroup> adHocRouteWorkgroups = document.getAdHocRouteWorkgroups();
447            if(ObjectUtils.isNotNull(adHocRouteWorkgroups)){
448                for(AdHocRouteWorkgroup adhocRecipient : adHocRouteWorkgroups){
449                    // the given adhoc route recipient doesn't count
450                    if(adHocRouteRecipient==adhocRecipient){
451                        continue;
452                    }
453                    
454                    String actionRequestCode = adhocRecipient.getActionRequested();
455                    if(StringUtils.equals(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, actionRequestCode)){
456                        return true;
457                    }
458                }
459            }        
460            
461            return false;
462        }    
463        
464        /**
465         * This method should be overridden by children rule classes as a hook to implement document specific business rule
466         * checks for
467         * the "add ad hoc route person" event.
468         *
469         * @param document
470         * @param person
471         * @return boolean True if the rules checks passed, false otherwise.
472         */
473        protected boolean processCustomAddAdHocRoutePersonBusinessRules(Document document, AdHocRoutePerson person) {
474            return true;
475        }
476    
477        /**
478         * @see org.kuali.rice.krad.rules.rule.AddAdHocRouteWorkgroupRule#processAddAdHocRouteWorkgroup(org.kuali.rice.krad.document.Document,
479         *      org.kuali.rice.krad.bo.AdHocRouteWorkgroup)
480         */
481        public boolean processAddAdHocRouteWorkgroup(Document document, AdHocRouteWorkgroup adHocRouteWorkgroup) {
482            boolean isValid = true;
483    
484            isValid &= isAddHocRouteWorkgroupValid(document, adHocRouteWorkgroup);
485    
486            isValid &= processCustomAddAdHocRouteWorkgroupBusinessRules(document, adHocRouteWorkgroup);
487            return isValid;
488        }
489    
490        /**
491         * Verifies that the adHocRouteWorkgroup's fields are valid - it does required and format checks.
492         *
493         * @param workgroup
494         * @return boolean True if valid, false otherwise.
495         */
496        public boolean isAddHocRouteWorkgroupValid(Document document, AdHocRouteWorkgroup workgroup) {
497            MessageMap errorMap = GlobalVariables.getMessageMap();
498    
499            // new recipients are not embedded in the error path; existing lines should be
500            if (errorMap.getErrorPath().size() == 0) {
501                // add the error path keys on the stack
502                GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME);
503            }
504    
505            if (workgroup.getRecipientName() != null && workgroup.getRecipientNamespaceCode() != null) {
506                // validate that they are a workgroup from the workgroup service by looking them up
507                try {
508                    Group group = getGroupService().getGroupByNamespaceCodeAndName(workgroup.getRecipientNamespaceCode(),
509                            workgroup.getRecipientName());
510                    
511                    String actionRequestedCode = workgroup.getActionRequested();
512                    if (group == null || !group.isActive()) {
513                        //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
514                        GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
515                                RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_ID);
516                        GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE);
517                    } 
518                    else if(StringUtils.equals(actionRequestedCode, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ) && this.hasAdHocRouteCompletion(document, workgroup)){
519                        // KULRICE-8760: Multiple complete adhoc requests should not be allowed on the same document
520                        GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE,
521                                RiceKeyConstants.ERROR_ADHOC_COMPLETE_MORE_THAN_ONE);
522                    }
523                    else {
524                        org.kuali.rice.kew.api.document.WorkflowDocumentService
525                                wds = KewApiServiceLocator.getWorkflowDocumentService();
526                        DocumentType documentType = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(
527                                wds.getDocument(document.getDocumentNumber()).getDocumentTypeName());
528                        Map<String, String> permissionDetails = buildDocumentTypeActionRequestPermissionDetails(
529                                documentType, workgroup.getActionRequested());
530                        if (useKimPermission(KewApiConstants.KEW_NAMESPACE, KewApiConstants.AD_HOC_REVIEW_PERMISSION, permissionDetails) ){
531                            List<String> principalIds = getGroupService().getMemberPrincipalIds(group.getId());
532                            // if any member of the group is not allowed to receive the request, then the group may not receive it
533                            for (String principalId : principalIds) {
534                                if (!getPermissionService().isAuthorizedByTemplate(principalId,
535                                        KewApiConstants.KEW_NAMESPACE, KewApiConstants.AD_HOC_REVIEW_PERMISSION,
536                                        permissionDetails, new HashMap<String, String>())) {
537                                    
538                                    //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
539                                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
540                                            RiceKeyConstants.ERROR_UNAUTHORIZED_ADHOC_WORKGROUP_ID);
541                                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE);
542                                    
543                                    break;
544                                }
545                            }
546                        }
547                    }
548                } catch (Exception e) {
549                    LOG.error("isAddHocRouteWorkgroupValid(AdHocRouteWorkgroup)", e);
550                    
551                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
552                            RiceKeyConstants.ERROR_INVALID_ADHOC_WORKGROUP_ID);
553                    
554                    //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
555                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE);
556                }
557            } else {
558                //  KULRICE-8091: Adhoc routing tab utilizing Groups on all documents missing asterisks 
559                if(workgroup.getRecipientNamespaceCode()==null) {
560                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAMESPACE_CODE, RiceKeyConstants.ERROR_ADHOC_INVALID_WORKGROUP_NAMESPACE_MISSING);
561                }
562                
563                if(workgroup.getRecipientName()==null) {
564                    GlobalVariables.getMessageMap().putError(KRADPropertyConstants.RECIPIENT_NAME,
565                        RiceKeyConstants.ERROR_MISSING_ADHOC_WORKGROUP_ID);
566                }
567            }
568    
569            // drop the error path keys off now
570            GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.NEW_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME);
571    
572            return GlobalVariables.getMessageMap().hasNoErrors();
573        }
574        /**
575         * This method should be overridden by children rule classes as a hook to implement document specific business rule
576         * checks for
577         * the "add ad hoc route workgroup" event.
578         *
579         * @param document
580         * @param workgroup
581         * @return boolean True if the rules checks passed, false otherwise.
582         */
583        protected boolean processCustomAddAdHocRouteWorkgroupBusinessRules(Document document,
584                AdHocRouteWorkgroup workgroup) {
585            return true;
586        }
587    
588        /**
589         * Gets the maximum number of levels the data-dictionary based validation will recurse for the document
590         */
591        public int getMaxDictionaryValidationDepth() {
592            return this.maxDictionaryValidationDepth;
593        }
594    
595        /**
596         * Gets the maximum number of levels the data-dictionary based validation will recurse for the document
597         */
598        public void setMaxDictionaryValidationDepth(int maxDictionaryValidationDepth) {
599            if (maxDictionaryValidationDepth < 0) {
600                LOG.error("Dictionary validation depth should be greater than or equal to 0.  Value received was: "
601                        + maxDictionaryValidationDepth);
602                throw new RuntimeException(
603                        "Dictionary validation depth should be greater than or equal to 0.  Value received was: "
604                                + maxDictionaryValidationDepth);
605            }
606            this.maxDictionaryValidationDepth = maxDictionaryValidationDepth;
607        }
608    
609        protected boolean validateSensitiveDataValue(String fieldName, String fieldValue, String fieldLabel) {
610            boolean dataValid = true;
611    
612            if (fieldValue == null) {
613                return dataValid;
614            }
615    
616            boolean patternFound = KRADUtils.containsSensitiveDataPatternMatch(fieldValue);
617            boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
618                    KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
619                    KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
620            if (patternFound && !warnForSensitiveData) {
621                dataValid = false;
622                GlobalVariables.getMessageMap().putError(fieldName,
623                        RiceKeyConstants.ERROR_DOCUMENT_FIELD_CONTAINS_POSSIBLE_SENSITIVE_DATA, fieldLabel);
624            }
625    
626            return dataValid;
627        }
628    
629        /**
630         * Business rules check will include all save action rules and any custom rules required by the document specific rule implementation
631         *
632         * @param document Document
633         * @return true if all validations are passed
634         */
635        public boolean processCompleteDocument(Document document) {
636            boolean isValid = true;
637            isValid &= processSaveDocument(document);
638            isValid &= processCustomCompleteDocumentBusinessRules(document);
639            return isValid;
640        }
641    
642        /**
643         * Hook method for deriving business rule classes to provide custom validations required during completion action
644         *
645         * @param document
646         * @return default is true
647         */
648        protected boolean processCustomCompleteDocumentBusinessRules(Document document) {
649            return true;
650        }
651    
652        protected boolean useKimPermission(String namespace, String permissionTemplateName, Map<String, String> permissionDetails) {
653                    Boolean b =  CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.KIM_PRIORITY_ON_DOC_TYP_PERMS_IND);
654                    if (b == null || b) {
655                            return getPermissionService().isPermissionDefinedByTemplate(namespace, permissionTemplateName,
656                        permissionDetails);
657                    }
658                    return false;
659            }
660        protected Map<String, String> buildDocumentTypeActionRequestPermissionDetails(DocumentType documentType, String actionRequestCode) {
661                    Map<String, String> details = buildDocumentTypePermissionDetails(documentType);
662                    if (!StringUtils.isBlank(actionRequestCode)) {
663                            details.put(KewApiConstants.ACTION_REQUEST_CD_DETAIL, actionRequestCode);
664                    }
665                    return details;
666            }
667    
668        protected Map<String, String> buildDocumentTypePermissionDetails(DocumentType documentType) {
669                    Map<String, String> details = new HashMap<String, String>();
670                    details.put(KewApiConstants.DOCUMENT_TYPE_NAME_DETAIL, documentType.getName());
671                    return details;
672            }
673    
674        protected DataDictionaryService getDataDictionaryService() {
675            if (dataDictionaryService == null) {
676                dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
677            }
678            return dataDictionaryService;
679        }
680    
681        protected PersonService getPersonService() {
682            if (personService == null) {
683                personService = KimApiServiceLocator.getPersonService();
684            }
685            return personService;
686        }
687    
688        public static GroupService getGroupService() {
689            if (groupService == null) {
690                groupService = KimApiServiceLocator.getGroupService();
691            }
692            return groupService;
693        }
694    
695        public static PermissionService getPermissionService() {
696            if (permissionService == null) {
697                permissionService = KimApiServiceLocator.getPermissionService();
698            }
699            return permissionService;
700        }
701    
702        protected DictionaryValidationService getDictionaryValidationService() {
703            if (dictionaryValidationService == null) {
704                dictionaryValidationService = KRADServiceLocatorWeb.getDictionaryValidationService();
705            }
706            return dictionaryValidationService;
707        }
708    
709        protected ConfigurationService getKualiConfigurationService() {
710            if (kualiConfigurationService == null) {
711                kualiConfigurationService = KRADServiceLocator.getKualiConfigurationService();
712            }
713            return kualiConfigurationService;
714        }
715    
716        protected static DocumentDictionaryService getDocumentDictionaryService() {
717            if (documentDictionaryService == null) {
718                documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
719            }
720            return documentDictionaryService;
721        }
722    
723        public static void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
724            DocumentRuleBase.documentDictionaryService = documentDictionaryService;
725        }
726    }