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