View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.rule.service.impl;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.ObjectUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.jdom.Element;
22  import org.kuali.rice.core.api.impex.ExportDataSet;
23  import org.kuali.rice.core.api.impex.xml.XmlConstants;
24  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
25  import org.kuali.rice.core.api.util.RiceConstants;
26  import org.kuali.rice.core.api.util.collect.CollectionUtils;
27  import org.kuali.rice.core.impl.cache.DistributedCacheManagerDecorator;
28  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
29  import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
30  import org.kuali.rice.kew.api.KewApiServiceLocator;
31  import org.kuali.rice.kew.api.WorkflowDocument;
32  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
33  import org.kuali.rice.kew.api.WorkflowRuntimeException;
34  import org.kuali.rice.kew.api.action.ActionRequestPolicy;
35  import org.kuali.rice.kew.api.rule.Rule;
36  import org.kuali.rice.kew.api.rule.RuleDelegation;
37  import org.kuali.rice.kew.api.rule.RuleExtension;
38  import org.kuali.rice.kew.api.rule.RuleResponsibility;
39  import org.kuali.rice.kew.api.validation.RuleValidationContext;
40  import org.kuali.rice.kew.api.validation.ValidationResults;
41  import org.kuali.rice.kew.doctype.bo.DocumentType;
42  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
43  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
44  import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
45  import org.kuali.rice.kew.impl.KewImplConstants;
46  import org.kuali.rice.kew.responsibility.service.ResponsibilityIdService;
47  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
48  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
49  import org.kuali.rice.kew.rule.RuleBaseValues;
50  import org.kuali.rice.kew.rule.RuleDelegationBo;
51  import org.kuali.rice.kew.rule.RuleExtensionBo;
52  import org.kuali.rice.kew.rule.RuleExtensionValue;
53  import org.kuali.rice.kew.rule.RuleResponsibilityBo;
54  import org.kuali.rice.kew.rule.RuleRoutingDefinition;
55  import org.kuali.rice.kew.rule.RuleValidationAttribute;
56  import org.kuali.rice.kew.rule.bo.RuleTemplateAttributeBo;
57  import org.kuali.rice.kew.rule.bo.RuleTemplateBo;
58  import org.kuali.rice.kew.rule.dao.RuleDAO;
59  import org.kuali.rice.kew.rule.dao.RuleResponsibilityDAO;
60  import org.kuali.rice.kew.rule.service.RuleDelegationService;
61  import org.kuali.rice.kew.rule.service.RuleServiceInternal;
62  import org.kuali.rice.kew.rule.service.RuleTemplateService;
63  import org.kuali.rice.kew.service.KEWServiceLocator;
64  import org.kuali.rice.kew.api.KewApiConstants;
65  import org.kuali.rice.kew.util.PerformanceLogger;
66  import org.kuali.rice.kew.xml.RuleXmlParser;
67  import org.kuali.rice.kew.xml.export.RuleXmlExporter;
68  import org.kuali.rice.kim.api.group.Group;
69  import org.kuali.rice.kim.api.group.GroupService;
70  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
71  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
72  import org.kuali.rice.krad.UserSession;
73  import org.kuali.rice.krad.util.GlobalVariables;
74  import org.kuali.rice.krad.util.KRADConstants;
75  import org.springframework.cache.Cache;
76  
77  import java.io.InputStream;
78  import java.sql.Timestamp;
79  import java.text.ParseException;
80  import java.util.ArrayList;
81  import java.util.Collection;
82  import java.util.Collections;
83  import java.util.Comparator;
84  import java.util.HashMap;
85  import java.util.HashSet;
86  import java.util.Iterator;
87  import java.util.List;
88  import java.util.Map;
89  import java.util.Set;
90  import java.util.UUID;
91  
92  
93  public class RuleServiceInternalImpl implements RuleServiceInternal {
94  
95      private static final String XML_PARSE_ERROR = "general.error.parsexml";
96  
97      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RuleServiceInternalImpl.class);
98  
99      private RuleDAO ruleDAO;
100     private RuleResponsibilityDAO ruleResponsibilityDAO;
101 
102     public RuleResponsibilityDAO getRuleResponsibilityDAO() {
103         return ruleResponsibilityDAO;
104     }
105 
106     public RuleBaseValues getRuleByName(String name) {
107         return ruleDAO.findRuleBaseValuesByName(name);
108     }
109 
110     public RuleBaseValues findDefaultRuleByRuleTemplateId(String ruleTemplateId){
111         return this.ruleDAO.findDefaultRuleByRuleTemplateId(ruleTemplateId);
112     }
113     public void setRuleResponsibilityDAO(RuleResponsibilityDAO ruleResponsibilityDAO) {
114         this.ruleResponsibilityDAO = ruleResponsibilityDAO;
115     }
116 
117     public void save2(RuleBaseValues ruleBaseValues) throws Exception {
118         save2(ruleBaseValues, null, true);
119     }
120 
121     public void save2(RuleBaseValues ruleBaseValues, RuleDelegationBo ruleDelegation, boolean saveDelegations) throws Exception {
122         if (ruleBaseValues.getPreviousRuleId() != null) {
123             RuleBaseValues oldRule = findRuleBaseValuesById(ruleBaseValues.getPreviousRuleId());
124             ruleBaseValues.setPreviousVersion(oldRule);
125             ruleBaseValues.setCurrentInd(Boolean.FALSE);
126             ruleBaseValues.setVersionNbr(getNextVersionNumber(oldRule));
127         }
128         if (ruleBaseValues.getVersionNbr() == null) {
129             ruleBaseValues.setVersionNbr(Integer.valueOf(0));
130         }
131         if (ruleBaseValues.getCurrentInd() == null) {
132             ruleBaseValues.setCurrentInd(Boolean.FALSE);
133         }
134         // iterate through all associated responsibilities, and if they are unsaved (responsibilityId is null)
135         // set a new id on them, and recursively save any associated delegation rules
136         for (Object element : ruleBaseValues.getRuleResponsibilities()) {
137             RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
138             if (responsibility.getResponsibilityId() == null) {
139                 responsibility.setResponsibilityId(getResponsibilityIdService().getNewResponsibilityId());
140             }
141             if (saveDelegations) {
142                 for (Object element2 : responsibility.getDelegationRules()) {
143                     RuleDelegationBo localRuleDelegation = (RuleDelegationBo) element2;
144                     save2(localRuleDelegation.getDelegationRule(), localRuleDelegation, true);
145                 }
146             }
147         }
148         validate2(ruleBaseValues, ruleDelegation, null);
149         getRuleDAO().save(ruleBaseValues);
150     }
151 
152     public void makeCurrent(String documentId) {
153         makeCurrent(findByDocumentId(documentId));
154     }
155 
156     public void makeCurrent(List<RuleBaseValues> rules) {
157         PerformanceLogger performanceLogger = new PerformanceLogger();
158 
159         boolean isGenerateRuleArs = true;
160         String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_GENERATE_ACTION_REQESTS_IND);
161         if (!StringUtils.isBlank(generateRuleArs)) {
162             isGenerateRuleArs = KewApiConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
163         }
164         Set<String> responsibilityIds = new HashSet<String>();
165         Map<String, RuleBaseValues> rulesToSave = new HashMap<String, RuleBaseValues>();
166 
167         Collections.sort(rules, new RuleDelegationSorter());
168         boolean delegateFirst = false;
169         for (RuleBaseValues rule : rules) {
170             performanceLogger.log("Preparing rule: " + rule.getDescription());
171 
172             rule.setCurrentInd(Boolean.TRUE);
173             Timestamp date = new Timestamp(System.currentTimeMillis());
174             rule.setActivationDate(date);
175             try {
176                 rule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
177             } catch (Exception e) {
178                 LOG.error("Parse Exception", e);
179             }
180             rulesToSave.put(rule.getId(), rule);
181             RuleBaseValues oldRule = rule.getPreviousVersion();
182             if (oldRule != null) {
183                 performanceLogger.log("Setting previous rule: " + oldRule.getId() + " to non current.");
184 
185                 oldRule.setCurrentInd(Boolean.FALSE);
186                 oldRule.setDeactivationDate(date);
187                 rulesToSave.put(oldRule.getId(), oldRule);
188                 if (!delegateFirst) {
189                     responsibilityIds.addAll(getResponsibilityIdsFromGraph(oldRule, isGenerateRuleArs));
190                 }
191                 //TODO if more than one delegate is edited from the create delegation screen (which currently can not happen), then this logic will not work.
192                 if (rule.getDelegateRule().booleanValue() && rule.getPreviousRuleId() != null) {
193                     delegateFirst = true;
194                 }
195 
196                 List<RuleBaseValues> oldDelegationRules = findOldDelegationRules(oldRule, rule, performanceLogger);
197                 for (RuleBaseValues delegationRule : oldDelegationRules) {
198 
199                     performanceLogger.log("Setting previous delegation rule: " + delegationRule.getId() + "to non current.");
200 
201                     delegationRule.setCurrentInd(Boolean.FALSE);
202                     rulesToSave.put(delegationRule.getId(), delegationRule);
203                     responsibilityIds.addAll(getResponsibilityIdsFromGraph(delegationRule, isGenerateRuleArs));
204                 }
205             }
206             for (Object element : rule.getRuleResponsibilities()) {
207                 RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
208                 for (Object element2 : responsibility.getDelegationRules()) {
209                     RuleDelegationBo delegation = (RuleDelegationBo) element2;
210 
211                     delegation.getDelegationRule().setCurrentInd(Boolean.TRUE);
212                     RuleBaseValues delegatorRule = delegation.getDelegationRule();
213 
214                     performanceLogger.log("Setting delegate rule: " + delegatorRule.getDescription() + " to current.");
215                     if (delegatorRule.getActivationDate() == null) {
216                         delegatorRule.setActivationDate(date);
217                     }
218                     try {
219                         delegatorRule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
220                     } catch (Exception e) {
221                         LOG.error("Parse Exception", e);
222                     }
223                     rulesToSave.put(delegatorRule.getId(), delegatorRule);
224                 }
225             }
226         }
227         for (RuleBaseValues rule : rulesToSave.values()) {
228             getRuleDAO().save(rule);
229             performanceLogger.log("Saved rule: " + rule.getId());
230         }
231         getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
232         performanceLogger.log("Time to make current");
233     }
234 
235     /**
236      * TODO consolidate this method with makeCurrent.  The reason there's a seperate implementation is because the
237      * original makeCurrent(...) could not properly handle versioning a List of multiple rules (including multiple
238      * delegates rules for a single parent.  ALso, this work is being done for a patch so we want to mitigate the
239      * impact on the existing rule code.
240      *
241      * <p>This version will only work for remove/replace operations where rules
242      * aren't being added or removed.  This is why it doesn't perform some of the functions like checking
243      * for delegation rules that were removed from a parent rule.
244      */
245     public void makeCurrent2(List<RuleBaseValues> rules) {
246         PerformanceLogger performanceLogger = new PerformanceLogger();
247 
248         boolean isGenerateRuleArs = true;
249         String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_GENERATE_ACTION_REQESTS_IND);
250         if (!StringUtils.isBlank(generateRuleArs)) {
251             isGenerateRuleArs = KewApiConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
252         }
253         Set<String> responsibilityIds = new HashSet<String>();
254         Map<String, RuleBaseValues> rulesToSave = new HashMap<String, RuleBaseValues>();
255 
256         Collections.sort(rules, new RuleDelegationSorter());
257         for (RuleBaseValues rule : rules) {
258             performanceLogger.log("Preparing rule: " + rule.getDescription());
259 
260             rule.setCurrentInd(Boolean.TRUE);
261             Timestamp date = new Timestamp(System.currentTimeMillis());
262             rule.setActivationDate(date);
263             try {
264                 rule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
265             } catch (Exception e) {
266                 LOG.error("Parse Exception", e);
267             }
268             rulesToSave.put(rule.getId(), rule);
269             RuleBaseValues oldRule = rule.getPreviousVersion();
270             if (oldRule != null) {
271                 performanceLogger.log("Setting previous rule: " + oldRule.getId() + " to non current.");
272                 oldRule.setCurrentInd(Boolean.FALSE);
273                 oldRule.setDeactivationDate(date);
274                 rulesToSave.put(oldRule.getId(), oldRule);
275                 responsibilityIds.addAll(getModifiedResponsibilityIds(oldRule, rule));
276             }
277             for (Object element : rule.getRuleResponsibilities()) {
278                 RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
279                 for (Object element2 : responsibility.getDelegationRules()) {
280                     RuleDelegationBo delegation = (RuleDelegationBo) element2;
281                     RuleBaseValues delegateRule = delegation.getDelegationRule();
282                     delegateRule.setCurrentInd(Boolean.TRUE);
283                     performanceLogger.log("Setting delegate rule: " + delegateRule.getDescription() + " to current.");
284                     if (delegateRule.getActivationDate() == null) {
285                         delegateRule.setActivationDate(date);
286                     }
287                     try {
288                         delegateRule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
289                     } catch (Exception e) {
290                         LOG.error("Parse Exception", e);
291                     }
292                     rulesToSave.put(delegateRule.getId(), delegateRule);
293                 }
294             }
295         }
296         for (RuleBaseValues rule : rulesToSave.values()) {
297             getRuleDAO().save(rule);
298             performanceLogger.log("Saved rule: " + rule.getId());
299 
300         }
301 
302         if (isGenerateRuleArs) {
303             getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
304         }
305         performanceLogger.log("Time to make current");
306     }
307 
308     /**
309      * makeCurrent(RuleBaseValues) is the version of makeCurrent which is initiated from the new Routing Rule
310      * Maintenance document.  Because of the changes in the data model and the front end here,
311      * this method can be much less complicated than the previous 2!
312      */
313     public void makeCurrent(RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
314     	makeCurrent(null, rule, isRetroactiveUpdatePermitted);
315     }
316 
317     public void makeCurrent(RuleDelegationBo ruleDelegation, boolean isRetroactiveUpdatePermitted) {
318         clearCache(RuleDelegation.Cache.NAME);
319     	makeCurrent(ruleDelegation, ruleDelegation.getDelegationRule(), isRetroactiveUpdatePermitted);
320     }
321 
322     protected void makeCurrent(RuleDelegationBo ruleDelegation, RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
323         PerformanceLogger performanceLogger = new PerformanceLogger();
324 
325         boolean isGenerateRuleArs = false;
326         if (isRetroactiveUpdatePermitted) {
327         	isGenerateRuleArs = true;
328         	String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_GENERATE_ACTION_REQESTS_IND);
329         	if (!StringUtils.isBlank(generateRuleArs)) {
330         		isGenerateRuleArs = KewApiConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
331         	}
332         }
333         Set<String> responsibilityIds = new HashSet<String>();
334 
335 
336         performanceLogger.log("Preparing rule: " + rule.getDescription());
337 
338         Map<String, RuleBaseValues> rulesToSave = new HashMap<String, RuleBaseValues>();
339         generateRuleNameIfNeeded(rule);
340         assignResponsibilityIds(rule);
341         rule.setCurrentInd(Boolean.TRUE);
342         Timestamp date = new Timestamp(System.currentTimeMillis());
343         rule.setActivationDate(date);
344         rule.setDeactivationDate(null);
345 
346         rulesToSave.put(rule.getId(), rule);
347         if (rule.getPreviousRuleId() != null) {
348         	RuleBaseValues oldRule = findRuleBaseValuesById(rule.getPreviousRuleId());
349         	rule.setPreviousVersion(oldRule);
350         }
351         rule.setVersionNbr(0);
352         rule.setObjectId(null);
353         RuleBaseValues oldRule = rule.getPreviousVersion();
354         if (oldRule != null) {
355         	performanceLogger.log("Setting previous rule: " + oldRule.getId() + " to non current.");
356         	oldRule.setCurrentInd(Boolean.FALSE);
357         	oldRule.setDeactivationDate(date);
358         	rulesToSave.put(oldRule.getId(), oldRule);
359         	responsibilityIds.addAll(getModifiedResponsibilityIds(oldRule, rule));
360         	rule.setVersionNbr(getNextVersionNumber(oldRule));
361         }
362                
363 
364         boolean isRuleDelegation = ruleDelegation != null;
365         
366         for (RuleBaseValues ruleToSave : rulesToSave.values()) {
367         	getRuleDAO().save(ruleToSave);
368         	performanceLogger.log("Saved rule: " + ruleToSave.getId());
369         }
370         if (isRuleDelegation) {
371         	responsibilityIds.add(ruleDelegation.getResponsibilityId());
372         	ruleDelegation.setDelegateRuleId(rule.getId());
373         	getRuleDelegationService().save(ruleDelegation);
374         }
375         
376         if (isGenerateRuleArs
377                 && org.apache.commons.collections.CollectionUtils.isNotEmpty(responsibilityIds)) {
378             getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
379         }
380         performanceLogger.log("Time to make current");
381     }
382     
383     private void clearCache(String cacheName) {
384         DistributedCacheManagerDecorator distributedCacheManagerDecorator =
385                 GlobalResourceLoader.getService(KewImplConstants.KEW_DISTRIBUTED_CACHE_MANAGER);
386 
387         Cache cache = distributedCacheManagerDecorator.getCache(cacheName);
388         if (cache != null) {
389             cache.clear();
390         }
391     }
392 
393     public RuleBaseValues getParentRule(String ruleBaseValuesId) {
394         return getRuleDAO().getParentRule(ruleBaseValuesId);
395     }
396 
397     private Set getResponsibilityIdsFromGraph(RuleBaseValues rule, boolean isRuleCollecting) {
398         Set responsibilityIds = new HashSet();
399         for (Object element : rule.getRuleResponsibilities()) {
400             RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
401             if (isRuleCollecting) {
402                 responsibilityIds.add(responsibility.getResponsibilityId());
403             }
404         }
405         return responsibilityIds;
406     }
407 
408     /**
409      * Returns the responsibility IDs that were modified between the 2 given versions of the rule.  Any added
410      * or removed responsibilities are also included in the returned Set.
411      */
412     private Set<String> getModifiedResponsibilityIds(RuleBaseValues oldRule, RuleBaseValues newRule) {
413         Map<String, RuleResponsibilityBo> modifiedResponsibilityMap = new HashMap<String, RuleResponsibilityBo>();
414         for (Object element : oldRule.getRuleResponsibilities()) {
415             RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
416             modifiedResponsibilityMap.put(responsibility.getResponsibilityId(), responsibility);
417         }
418         for (Object element : newRule.getRuleResponsibilities()) {
419             RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
420             RuleResponsibilityBo oldResponsibility = modifiedResponsibilityMap.get(responsibility.getResponsibilityId());
421             if (oldResponsibility == null) {
422                 // if there's no old responsibility then it's a new responsibility, add it
423                 modifiedResponsibilityMap.put(responsibility.getResponsibilityId(), responsibility);
424             } else if (!hasResponsibilityChanged(oldResponsibility, responsibility)) {
425                 // if it hasn't been modified, remove it from the collection of modified ids
426                 modifiedResponsibilityMap.remove(responsibility.getResponsibilityId());
427             }
428         }
429         return modifiedResponsibilityMap.keySet();
430     }
431 
432     /**
433      * Determines if the given responsibilities are different or not.
434      */
435     private boolean hasResponsibilityChanged(RuleResponsibilityBo oldResponsibility, RuleResponsibilityBo newResponsibility) {
436         return !ObjectUtils.equals(oldResponsibility.getActionRequestedCd(), newResponsibility.getActionRequestedCd()) ||
437         !ObjectUtils.equals(oldResponsibility.getApprovePolicy(), newResponsibility.getActionRequestedCd()) ||
438         !ObjectUtils.equals(oldResponsibility.getPriority(), newResponsibility.getPriority()) ||
439         !ObjectUtils.equals(oldResponsibility.getRole(), newResponsibility.getRole()) ||
440         !ObjectUtils.equals(oldResponsibility.getRuleResponsibilityName(), newResponsibility.getRuleResponsibilityName()) ||
441         !ObjectUtils.equals(oldResponsibility.getRuleResponsibilityType(), newResponsibility.getRuleResponsibilityType());
442     }
443 
444     /**
445      * This method will find any old delegation rules on the previous version of the parent rule which are not on the
446      * new version of the rule so that they can be marked non-current.
447      */
448     private List<RuleBaseValues> findOldDelegationRules(RuleBaseValues oldRule, RuleBaseValues newRule, PerformanceLogger performanceLogger) {
449         performanceLogger.log("Begin to get delegation rules.");
450         List<RuleBaseValues> oldDelegations = getRuleDAO().findOldDelegations(oldRule, newRule);
451         performanceLogger.log("Located "+oldDelegations.size()+" old delegation rules.");
452         return oldDelegations;
453     }
454 
455     public String routeRuleWithDelegate(String documentId, RuleBaseValues parentRule, RuleBaseValues delegateRule, PrincipalContract principal, String annotation, boolean blanketApprove) throws Exception {
456         if (parentRule == null) {
457             throw new IllegalArgumentException("Cannot route a delegate without a parent rule.");
458         }
459         if (parentRule.getDelegateRule().booleanValue()) {
460             throw new IllegalArgumentException("Parent rule cannot be a delegate.");
461         }
462         if (parentRule.getPreviousRuleId() == null && delegateRule.getPreviousRuleId() == null) {
463             throw new IllegalArgumentException("Previous rule version required.");
464         }
465 
466         // if the parent rule is new, unsaved, then save it
467 //      boolean isRoutingParent = parentRule.getId() == null;
468 //      if (isRoutingParent) {
469 //      // it's very important that we do not save delegations here (that's what the false parameter is for)
470 //      // this is because, if we save the delegations, the existing delegations on our parent rule will become
471 //      // saved as "non current" before the rule is approved!!!
472 //      save2(parentRule, null, false);
473 //      //save2(parentRule, null, true);
474 //      }
475 
476         // XXX: added when the RuleValidation stuff was added, basically we just need to get the RuleDelegation
477         // that points to our delegate rule, this rule code is scary...
478         RuleDelegationBo ruleDelegation = getRuleDelegation(parentRule, delegateRule);
479 
480         save2(delegateRule, ruleDelegation, true);
481 
482 //      if the parent rule is new, unsaved, then save it
483         // It's important to save the parent rule after the delegate rule is saved, that way we can ensure that any new rule
484         // delegations have a valid, saved, delegation rule to point to (otherwise we end up with a null constraint violation)
485         boolean isRoutingParent = parentRule.getId() == null;
486         if (isRoutingParent) {
487             // it's very important that we do not save delegations here (that's what the false parameter is for)
488             // this is because, if we save the delegations, the existing delegations on our parent rule will become
489             // saved as "non current" before the rule is approved!!!
490             save2(parentRule, null, false);
491             //save2(parentRule, null, true);
492         }
493 
494         WorkflowDocument workflowDocument = null;
495         if (documentId != null) {
496             workflowDocument = WorkflowDocumentFactory.loadDocument(principal.getPrincipalId(), documentId);
497         } else {
498             List rules = new ArrayList();
499             rules.add(delegateRule);
500             rules.add(parentRule);
501             workflowDocument = WorkflowDocumentFactory.createDocument(principal.getPrincipalId(), getRuleDocumentTypeName(
502                     rules));
503         }
504         workflowDocument.setTitle(generateTitle(parentRule, delegateRule));
505         delegateRule.setDocumentId(workflowDocument.getDocumentId());
506         workflowDocument.addAttributeDefinition(RuleRoutingDefinition.createAttributeDefinition(parentRule.getDocTypeName()));
507         getRuleDAO().save(delegateRule);
508         if (isRoutingParent) {
509             parentRule.setDocumentId(workflowDocument.getDocumentId());
510             getRuleDAO().save(parentRule);
511         }
512         if (blanketApprove) {
513             workflowDocument.blanketApprove(annotation);
514         } else {
515             workflowDocument.route(annotation);
516         }
517         return workflowDocument.getDocumentId();
518     }
519 
520     /**
521      * Gets the RuleDelegation object from the parentRule that points to the delegateRule.
522      */
523     private RuleDelegationBo getRuleDelegation(RuleBaseValues parentRule, RuleBaseValues delegateRule) throws Exception {
524         for (Object element : parentRule.getRuleResponsibilities()) {
525             RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
526             for (Object element2 : responsibility.getDelegationRules()) {
527                 RuleDelegationBo ruleDelegation = (RuleDelegationBo) element2;
528                 // they should be the same object in memory
529                 if (ruleDelegation.getDelegationRule().equals(delegateRule)) {
530                     return ruleDelegation;
531                 }
532             }
533         }
534         return null;
535     }
536 
537     private String generateTitle(RuleBaseValues parentRule, RuleBaseValues delegateRule) {
538         StringBuffer title = new StringBuffer();
539         if (delegateRule.getPreviousRuleId() != null) {
540             title.append("Editing Delegation Rule '").append(delegateRule.getDescription()).append("' on '");
541         } else {
542             title.append("Adding Delegation Rule '").append(delegateRule.getDescription()).append("' to '");
543         }
544         title.append(parentRule.getDescription()).append("'");
545         return title.toString();
546     }
547 
548     public void validate(RuleBaseValues ruleBaseValues, List errors) {
549         if (errors == null) {
550             errors = new ArrayList();
551         }
552         if (getDocumentTypeService().findByName(ruleBaseValues.getDocTypeName()) == null) {
553             errors.add(new WorkflowServiceErrorImpl("Document Type Invalid", "doctype.documenttypeservice.doctypename.required"));
554         }
555         if (ruleBaseValues.getToDateValue().before(ruleBaseValues.getFromDateValue())) {
556             errors.add(new WorkflowServiceErrorImpl("From Date is later than to date", "routetemplate.ruleservice.daterange.fromafterto"));
557         }
558         if (ruleBaseValues.getDescription() == null || ruleBaseValues.getDescription().equals("")) {
559             errors.add(new WorkflowServiceErrorImpl("Description is required", "routetemplate.ruleservice.description.required"));
560         }
561         if (ruleBaseValues.getRuleResponsibilities().isEmpty()) {
562             errors.add(new WorkflowServiceErrorImpl("A responsibility is required", "routetemplate.ruleservice.responsibility.required"));
563         } else {
564             for (Object element : ruleBaseValues.getRuleResponsibilities()) {
565                 RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
566                 if (responsibility.getRuleResponsibilityName() != null && KewApiConstants.RULE_RESPONSIBILITY_GROUP_ID.equals(responsibility.getRuleResponsibilityType())) {
567                     if (getGroupService().getGroup(responsibility.getRuleResponsibilityName()) == null) {
568                         errors.add(new WorkflowServiceErrorImpl("Workgroup is invalid", "routetemplate.ruleservice.workgroup.invalid"));
569                     }
570                 } else if (responsibility.getPrincipal() == null && responsibility.getRole() == null) {
571                     errors.add(new WorkflowServiceErrorImpl("User is invalid", "routetemplate.ruleservice.user.invalid"));
572                 }
573             }
574         }
575         if (!errors.isEmpty()) {
576             throw new WorkflowServiceErrorException("RuleBaseValues validation errors", errors);
577         }
578     }
579 
580     public void validate2(RuleBaseValues ruleBaseValues, RuleDelegationBo ruleDelegation, List errors) {
581         if (errors == null) {
582             errors = new ArrayList();
583         }
584         if (getDocumentTypeService().findByName(ruleBaseValues.getDocTypeName()) == null) {
585             errors.add(new WorkflowServiceErrorImpl("Document Type Invalid", "doctype.documenttypeservice.doctypename.required"));
586             LOG.error("Document Type Invalid");
587         }
588         if (ruleBaseValues.getToDateValue() == null) {
589             try {
590                 ruleBaseValues.setToDateValue(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100")
591                         .getTime()));
592             } catch (ParseException e) {
593                 LOG.error("Error date-parsing default date");
594                 throw new WorkflowServiceErrorException("Error parsing default date.", e);
595             }
596         }
597         if (ruleBaseValues.getFromDateValue() == null) {
598             ruleBaseValues.setFromDateValue(new Timestamp(System.currentTimeMillis()));
599         }
600         if (ruleBaseValues.getToDateValue().before(ruleBaseValues.getFromDateValue())) {
601             errors.add(new WorkflowServiceErrorImpl("From Date is later than to date", "routetemplate.ruleservice.daterange.fromafterto"));
602             LOG.error("From Date is later than to date");
603         }
604         if (ruleBaseValues.getDescription() == null || ruleBaseValues.getDescription().equals("")) {
605             errors.add(new WorkflowServiceErrorImpl("Description is required", "routetemplate.ruleservice.description.required"));
606             LOG.error("Description is missing");
607         }
608 
609         for (Object element : ruleBaseValues.getRuleResponsibilities()) {
610             RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
611             if (responsibility.getRuleResponsibilityName() != null && KewApiConstants.RULE_RESPONSIBILITY_GROUP_ID.equals(responsibility.getRuleResponsibilityType())) {
612                 if (getGroupService().getGroup(responsibility.getRuleResponsibilityName()) == null) {
613                     errors.add(new WorkflowServiceErrorImpl("Workgroup is invalid", "routetemplate.ruleservice.workgroup.invalid"));
614                     LOG.error("Workgroup is invalid");
615                 }
616             } else if (responsibility.getPrincipal() == null && responsibility.getRole() == null) {
617                 errors.add(new WorkflowServiceErrorImpl("User is invalid", "routetemplate.ruleservice.user.invalid"));
618                 LOG.error("User is invalid");
619             } else if (responsibility.isUsingRole()) {
620                 if (responsibility.getApprovePolicy() == null || !(responsibility.getApprovePolicy().equals(ActionRequestPolicy.ALL.getCode()) || responsibility.getApprovePolicy().equals(ActionRequestPolicy.FIRST.getCode()))) {
621                     errors.add(new WorkflowServiceErrorImpl("Approve Policy is Invalid", "routetemplate.ruleservice.approve.policy.invalid"));
622                     LOG.error("Approve Policy is Invalid");
623                 }
624             }
625         }
626 
627         if (ruleBaseValues.getRuleTemplate() != null) {
628             for (Object element : ruleBaseValues.getRuleTemplate().getActiveRuleTemplateAttributes()) {
629                 RuleTemplateAttributeBo templateAttribute = (RuleTemplateAttributeBo) element;
630                 if (!templateAttribute.isRuleValidationAttribute()) {
631                     continue;
632                 }
633                 RuleValidationAttribute attribute = templateAttribute.getRuleValidationAttribute();
634                 UserSession userSession = GlobalVariables.getUserSession();
635                 try {
636                     RuleValidationContext validationContext = RuleValidationContext.Builder.create(RuleBaseValues.to(ruleBaseValues), RuleDelegationBo
637                             .to(ruleDelegation), userSession.getPrincipalId()).build();
638                     ValidationResults results = attribute.validate(validationContext);
639                     if (results != null && !results.getErrors().isEmpty()) {
640                         errors.add(results);
641                     }
642                 } catch (Exception e) {
643                     if (e instanceof RuntimeException) {
644                         throw (RuntimeException)e;
645                     }
646                     throw new RuntimeException("Problem validation rule.", e);
647                 }
648 
649             }
650         }
651         if (ruleBaseValues.getRuleExpressionDef() != null) {
652             // rule expressions do not require parse-/save-time validation
653         }
654 
655         if (!errors.isEmpty()) {
656             throw new WorkflowServiceErrorException("RuleBaseValues validation errors", errors);
657         }
658     }
659 
660     public List<RuleBaseValues> findByDocumentId(String documentId) {
661         return getRuleDAO().findByDocumentId(documentId);
662     }
663 
664     public List<RuleBaseValues> search(String docTypeName, String ruleId, String ruleTemplateId, String ruleDescription, String groupId, String principalId,
665             Boolean delegateRule, Boolean activeInd, Map extensionValues, String workflowIdDirective) {
666         return getRuleDAO().search(docTypeName, ruleId, ruleTemplateId, ruleDescription, groupId, principalId, delegateRule,
667                 activeInd, extensionValues, workflowIdDirective);
668     }
669 
670     public List<RuleBaseValues> searchByTemplate(String docTypeName, String ruleTemplateName, String ruleDescription, String groupId, String principalId,
671             Boolean workgroupMember, Boolean delegateRule, Boolean activeInd, Map extensionValues, Collection<String> actionRequestCodes) {
672 
673         if ( (StringUtils.isEmpty(docTypeName)) &&
674                 (StringUtils.isEmpty(ruleTemplateName)) &&
675                 (StringUtils.isEmpty(ruleDescription)) &&
676                 (StringUtils.isEmpty(groupId)) &&
677                 (StringUtils.isEmpty(principalId)) &&
678                 (extensionValues.isEmpty()) &&
679                 (actionRequestCodes.isEmpty()) ) {
680             // all fields are empty
681             throw new IllegalArgumentException("At least one criterion must be sent");
682         }
683 
684         RuleTemplateBo ruleTemplate = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName);
685         String ruleTemplateId = null;
686         if (ruleTemplate != null) {
687             ruleTemplateId = ruleTemplate.getId();
688         }
689 
690         if ( ( (extensionValues != null) && (!extensionValues.isEmpty()) ) &&
691                 (ruleTemplateId == null) ) {
692             // cannot have extensions without a correct template
693             throw new IllegalArgumentException("A Rule Template Name must be given if using Rule Extension values");
694         }
695 
696         Collection<String> workgroupIds = new ArrayList<String>();
697         if (principalId != null) {
698             KEWServiceLocator.getIdentityHelperService().validatePrincipalId(principalId);
699             if ( (workgroupMember == null) || (workgroupMember.booleanValue()) ) {
700         		workgroupIds = getGroupService().getGroupIdsByPrincipalId(principalId);
701         	} else {
702         		// user was passed but workgroups should not be parsed... do nothing
703         	}
704         } else if (groupId != null) {
705         	Group group = KEWServiceLocator.getIdentityHelperService().getGroup(groupId);
706         	if (group == null) {
707         		throw new IllegalArgumentException("Group does not exist in for given group id: " + groupId);
708         	} else  {
709         		workgroupIds.add(group.getId());
710         	}
711         }
712 
713         return getRuleDAO().search(docTypeName, ruleTemplateId, ruleDescription, workgroupIds, principalId,
714                 delegateRule,activeInd, extensionValues, actionRequestCodes);
715     }
716 
717     public void delete(String ruleBaseValuesId) {
718         getRuleDAO().delete(ruleBaseValuesId);
719     }
720 
721     public RuleBaseValues findRuleBaseValuesById(String ruleBaseValuesId) {
722         return getRuleDAO().findRuleBaseValuesById(ruleBaseValuesId);
723     }
724 
725     public RuleResponsibilityBo findRuleResponsibility(String responsibilityId) {
726         return getRuleDAO().findRuleResponsibility(responsibilityId);
727     }
728 
729     public List fetchAllCurrentRulesForTemplateDocCombination(String ruleTemplateName, String documentType) {
730         	String ruleTemplateId = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName).getId();
731             return getRuleDAO().fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateId, getDocGroupAndTypeList(documentType));
732     }
733 
734     public List fetchAllCurrentRulesForTemplateDocCombination(String ruleTemplateName, String documentType, Timestamp effectiveDate){
735         String ruleTemplateId = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName).getId();
736         PerformanceLogger performanceLogger = new PerformanceLogger();
737         performanceLogger.log("Time to fetchRules by template " + ruleTemplateName + " not caching.");
738         return getRuleDAO().fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateId, getDocGroupAndTypeList(documentType), effectiveDate);
739     }
740     public List fetchAllRules(boolean currentRules) {
741         return getRuleDAO().fetchAllRules(currentRules);
742     }
743 
744     private List getDocGroupAndTypeList(String documentType) {
745         List docTypeList = new ArrayList();
746         DocumentTypeService docTypeService = (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
747         DocumentType docType = docTypeService.findByName(documentType);
748         while (docType != null) {
749             docTypeList.add(docType.getName());
750             docType = docType.getParentDocType();
751         }
752         return docTypeList;
753     }
754 
755     private Integer getNextVersionNumber(RuleBaseValues currentRule) {
756         List candidates = new ArrayList();
757         candidates.add(currentRule.getVersionNbr());
758         List pendingRules = ruleDAO.findByPreviousRuleId(currentRule.getId());
759         for (Iterator iterator = pendingRules.iterator(); iterator.hasNext();) {
760             RuleBaseValues pendingRule = (RuleBaseValues) iterator.next();
761             candidates.add(pendingRule.getVersionNbr());
762         }
763         Collections.sort(candidates);
764         Integer maxVersionNumber = (Integer) candidates.get(candidates.size() - 1);
765         if (maxVersionNumber == null) {
766             return Integer.valueOf(0);
767         }
768         return Integer.valueOf(maxVersionNumber.intValue() + 1);
769     }
770 
771     /**
772      * Determines if the given rule is locked for routing.
773      *
774      * In the case of a root rule edit, this method will take the rule id of the rule being edited.
775      *
776      * In the case of a new delegate rule or a delegate rule edit, this method will take the id of it's parent.
777      */
778     public String isLockedForRouting(String currentRuleBaseValuesId) {
779         // checks for any other versions of the given rule, essentially, if this is a rule edit we want to see how many other
780         // pending edits are out there
781         List pendingRules = ruleDAO.findByPreviousRuleId(currentRuleBaseValuesId);
782         boolean isDead = true;
783         for (Iterator iterator = pendingRules.iterator(); iterator.hasNext();) {
784             RuleBaseValues pendingRule = (RuleBaseValues) iterator.next();
785 
786             if (pendingRule.getDocumentId() != null && StringUtils.isNotBlank(pendingRule.getDocumentId())) {
787                 DocumentRouteHeaderValue routeHeader = getRouteHeaderService().getRouteHeader(pendingRule.getDocumentId());
788                 // the pending edit is considered dead if it's been disapproved or cancelled and we are allowed to proceed with our own edit
789                 isDead = routeHeader.isDisaproved() || routeHeader.isCanceled();
790                 if (!isDead) {
791                     return pendingRule.getDocumentId();
792                 }
793             }
794 
795             for (Object element : pendingRule.getRuleResponsibilities()) {
796                 RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
797                 for (Object element2 : responsibility.getDelegationRules()) {
798                     RuleDelegationBo delegation = (RuleDelegationBo) element2;
799                     List pendingDelegateRules = ruleDAO.findByPreviousRuleId(delegation.getDelegationRule().getId());
800                     for (Iterator iterator3 = pendingDelegateRules.iterator(); iterator3.hasNext();) {
801                         RuleBaseValues pendingDelegateRule = (RuleBaseValues) iterator3.next();
802                         if (pendingDelegateRule.getDocumentId() != null && StringUtils.isNotBlank(pendingDelegateRule.getDocumentId())) {
803                             DocumentRouteHeaderValue routeHeader = getRouteHeaderService().getRouteHeader(pendingDelegateRule.getDocumentId());
804                             isDead = routeHeader.isDisaproved() || routeHeader.isCanceled();
805                             if (!isDead) {
806                                 return pendingDelegateRule.getDocumentId();
807                             }
808                         }
809                     }
810                 }
811             }
812         }
813         return null;
814     }
815 
816     public RuleBaseValues getParentRule(RuleBaseValues rule) {
817         if (rule == null || rule.getId() == null) {
818             throw new IllegalArgumentException("Rule must be non-null with non-null id: " + rule);
819         }
820         if (!Boolean.TRUE.equals(rule.getDelegateRule())) {
821             return null;
822         }
823         return getRuleDAO().getParentRule(rule.getId());
824     }
825 
826     /**
827      * This configuration is currently stored in a system parameter named "CUSTOM_DOCUMENT_TYPES ",
828      * long term we should come up with a better solution.  The format of this constant is a comma-separated
829      * list of entries of the following form:
830      *
831      * <<name of doc type on rule>>:<<rule template name on rule>>:<<type of rule>>:<<name of document type to use for rule routing>>
832      *
833      * Rule type indicates either main or delegation rules.  A main rule is indicated by the character 'M' and a
834      * delegate rule is indicated by the character 'D'.
835      *
836      * So, if you wanted to route "main" rules made for the "MyDocType" document with the rule template name
837      * "MyRuleTemplate" using the "MyMainRuleDocType" doc type, it would be specified as follows:
838      *
839      * MyDocType:MyRuleTemplate:M:MyMainRuleDocType
840      *
841      * If you also wanted to route "delegate" rules made for the "MyDocType" document with rule template name
842      * "MyDelegateTemplate" using the "MyDelegateRuleDocType", you would then set the constant as follows:
843      *
844      * MyDocType:MyRuleTemplate:M:MyMainRuleDocType,MyDocType:MyDelegateTemplate:D:MyDelegateRuleDocType
845      *
846      * TODO this method ended up being a mess, we should get rid of this as soon as we can
847      */
848     public String getRuleDocumentTypeName(List rules) {
849         if (rules.size() == 0) {
850             throw new IllegalArgumentException("Cannot determine rule DocumentType for an empty list of rules.");
851         }
852         String ruleDocTypeName = null;
853         RuleRoutingConfig config = RuleRoutingConfig.parse();
854         // There are 2 cases here
855         RuleBaseValues firstRule = (RuleBaseValues)rules.get(0);
856         if (Boolean.TRUE.equals(firstRule.getDelegateRule())) {
857             // if it's a delegate rule then the list will contain only 2 elements, the first is the delegate rule,
858             // the second is the parent rule.  In this case just look at the custom routing process for the delegate rule.
859             ruleDocTypeName = config.getDocumentTypeName(firstRule);
860         } else {
861             // if this is a list of parent rules being routed, look at all configued routing types and verify that they are
862             // all the same, if not throw an exception
863             String parentRulesDocTypeName = null;
864             for (Iterator iterator = rules.iterator(); iterator.hasNext();) {
865                 RuleBaseValues rule = (RuleBaseValues) iterator.next();
866                 // if it's a delegate rule just skip it
867                 if  (Boolean.TRUE.equals(rule.getDelegateRule())) {
868                     continue;
869                 }
870                 String currentDocTypeName = config.getDocumentTypeName(rule);
871                 if (parentRulesDocTypeName == null) {
872                     parentRulesDocTypeName = currentDocTypeName;
873                 } else {
874                     if (!ObjectUtils.equals(currentDocTypeName, parentRulesDocTypeName)) {
875                         throw new RuntimeException("There are multiple rules being routed and they have different document type definitions!  " + parentRulesDocTypeName + " and " + currentDocTypeName);
876                     }
877                 }
878             }
879             ruleDocTypeName = parentRulesDocTypeName;
880         }
881         if (ruleDocTypeName == null) {
882             ruleDocTypeName = KewApiConstants.DEFAULT_RULE_DOCUMENT_NAME;
883         }
884         return ruleDocTypeName;
885     }
886 
887     public void setRuleDAO(RuleDAO ruleDAO) {
888         this.ruleDAO = ruleDAO;
889     }
890 
891     public RuleDAO getRuleDAO() {
892         return ruleDAO;
893     }
894 
895     public void deleteRuleResponsibilityById(String ruleResponsibilityId) {
896         getRuleResponsibilityDAO().delete(ruleResponsibilityId);
897     }
898 
899     public RuleResponsibilityBo findByRuleResponsibilityId(String ruleResponsibilityId) {
900         return getRuleResponsibilityDAO().findByRuleResponsibilityId(ruleResponsibilityId);
901     }
902 
903     public List findRuleBaseValuesByResponsibilityReviewer(String reviewerName, String type) {
904         return getRuleDAO().findRuleBaseValuesByResponsibilityReviewer(reviewerName, type);
905     }
906 
907     public List findRuleBaseValuesByResponsibilityReviewerTemplateDoc(String ruleTemplateName, String documentType, String reviewerName, String type) {
908         return getRuleDAO().findRuleBaseValuesByResponsibilityReviewerTemplateDoc(ruleTemplateName, documentType, reviewerName, type);
909     }
910 
911     public RuleTemplateService getRuleTemplateService() {
912         return (RuleTemplateService) KEWServiceLocator.getService(KEWServiceLocator.RULE_TEMPLATE_SERVICE);
913     }
914 
915     public DocumentTypeService getDocumentTypeService() {
916         return (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
917     }
918 
919     public GroupService getGroupService() {
920         return KimApiServiceLocator.getGroupService();
921     }
922 
923     public ActionRequestService getActionRequestService() {
924         return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV);
925     }
926 
927     private ResponsibilityIdService getResponsibilityIdService() {
928         return (ResponsibilityIdService) KEWServiceLocator.getService(KEWServiceLocator.RESPONSIBILITY_ID_SERVICE);
929     }
930 
931     private RuleDelegationService getRuleDelegationService() {
932         return (RuleDelegationService) KEWServiceLocator.getService(KEWServiceLocator.RULE_DELEGATION_SERVICE);
933     }
934 
935     private RouteHeaderService getRouteHeaderService() {
936         return (RouteHeaderService) KEWServiceLocator.getService(KEWServiceLocator.DOC_ROUTE_HEADER_SRV);
937     }
938 
939     /**
940      * A comparator implementation which compares RuleBaseValues and puts all delegate rules first.
941      */
942     public class RuleDelegationSorter implements Comparator {
943         public int compare(Object arg0, Object arg1) {
944             RuleBaseValues rule1 = (RuleBaseValues) arg0;
945             RuleBaseValues rule2 = (RuleBaseValues) arg1;
946 
947             Integer rule1Value = new Integer((rule1.getDelegateRule().booleanValue() ? 0 : 1));
948             Integer rule2Value = new Integer((rule2.getDelegateRule().booleanValue() ? 0 : 1));
949             int value = rule1Value.compareTo(rule2Value);
950             return value;
951         }
952     }
953 
954 
955     public void loadXml(InputStream inputStream, String principalId) {
956         RuleXmlParser parser = new RuleXmlParser();
957         try {
958             parser.parseRules(inputStream);
959         } catch (Exception e) { //any other exception
960             LOG.error("Error loading xml file", e);
961             WorkflowServiceErrorException wsee = new WorkflowServiceErrorException("Error loading xml file", new WorkflowServiceErrorImpl("Error loading xml file", XML_PARSE_ERROR));
962             wsee.initCause(e);
963             throw wsee;
964         }
965     }
966 
967     public Element export(ExportDataSet dataSet) {
968         RuleXmlExporter exporter = new RuleXmlExporter(XmlConstants.RULE_NAMESPACE);
969         return exporter.export(dataSet);
970     }
971     
972     @Override
973 	public boolean supportPrettyPrint() {
974 		return true;
975 	}
976 
977     protected List<RuleBaseValues> loadRules(List<String> ruleIds) {
978         List<RuleBaseValues> rules = new ArrayList<RuleBaseValues>();
979         for (String ruleId : ruleIds) {
980             RuleBaseValues rule = KEWServiceLocator.getRuleService().findRuleBaseValuesById(ruleId);
981             rules.add(rule);
982         }
983         return rules;
984     }
985 
986     /**
987      * If a rule has been modified and is no longer current since the original request was made, we need to
988      * be sure to NOT update the rule.
989      */
990     protected boolean shouldChangeRuleInvolvement(String documentId, RuleBaseValues rule) {
991         if (!rule.getCurrentInd()) {
992             LOG.warn("Rule requested for rule involvement change by document " + documentId + " is no longer current.  Change will not be executed!  Rule id is: " + rule.getId());
993             return false;
994         }
995         String lockingDocumentId = KEWServiceLocator.getRuleService().isLockedForRouting(rule.getId());
996         if (lockingDocumentId != null) {
997             LOG.warn("Rule requested for rule involvement change by document " + documentId + " is locked by document " + lockingDocumentId + " and cannot be modified.  " +
998                     "Change will not be executed!  Rule id is: " + rule.getId());
999             return false;
1000         }
1001         return true;
1002     }
1003 
1004     protected RuleDelegationBo getRuleDelegationForDelegateRule(RuleBaseValues rule) {
1005         if (Boolean.TRUE.equals(rule.getDelegateRule())) {
1006             List delegations = getRuleDelegationService().findByDelegateRuleId(rule.getId());
1007             for (Iterator iterator = delegations.iterator(); iterator.hasNext();) {
1008                 RuleDelegationBo ruleDelegation = (RuleDelegationBo) iterator.next();
1009                 RuleBaseValues parentRule = ruleDelegation.getRuleResponsibility().getRuleBaseValues();
1010                 if (Boolean.TRUE.equals(parentRule.getCurrentInd())) {
1011                     return ruleDelegation;
1012                 }
1013             }
1014         }
1015         return null;
1016     }
1017 
1018     protected void hookUpDelegateRuleToParentRule(RuleBaseValues newParentRule, RuleBaseValues newDelegationRule, RuleDelegationBo existingRuleDelegation) {
1019         // hook up parent rule to new rule delegation
1020         boolean foundDelegation = false;
1021         outer:for (RuleResponsibilityBo responsibility : (List<RuleResponsibilityBo>)newParentRule.getRuleResponsibilities()) {
1022             for (RuleDelegationBo ruleDelegation : (List<RuleDelegationBo>)responsibility.getDelegationRules()) {
1023                 if (ruleDelegation.getDelegationRule().getId().equals(existingRuleDelegation.getDelegationRule().getId())) {
1024                     ruleDelegation.setDelegationRule(newDelegationRule);
1025                     foundDelegation = true;
1026                     break outer;
1027                 }
1028             }
1029         }
1030         if (!foundDelegation) {
1031             throw new WorkflowRuntimeException("Failed to locate the existing rule delegation with id: " + existingRuleDelegation.getDelegationRule().getId());
1032         }
1033 
1034     }
1035 
1036     protected RuleBaseValues createNewRuleVersion(RuleBaseValues existingRule, String documentId) throws Exception {
1037         RuleBaseValues rule = new RuleBaseValues();
1038         PropertyUtils.copyProperties(rule, existingRule);
1039         rule.setPreviousVersion(existingRule);
1040         rule.setPreviousRuleId(existingRule.getId());
1041         rule.setId(null);
1042         rule.setActivationDate(null);
1043         rule.setDeactivationDate(null);
1044         rule.setVersionNumber(0L);
1045         rule.setDocumentId(documentId);
1046 
1047         // TODO: FIXME: need to copy the rule expression here too?
1048 
1049         rule.setRuleResponsibilities(new ArrayList());
1050         for (RuleResponsibilityBo existingResponsibility : (List<RuleResponsibilityBo>)existingRule.getRuleResponsibilities()) {
1051             RuleResponsibilityBo responsibility = new RuleResponsibilityBo();
1052             PropertyUtils.copyProperties(responsibility, existingResponsibility);
1053             responsibility.setRuleBaseValues(rule);
1054             responsibility.setRuleBaseValuesId(null);
1055             responsibility.setId(null);
1056             responsibility.setVersionNumber(0L);
1057             rule.getRuleResponsibilities().add(responsibility);
1058 //            responsibility.setDelegationRules(new ArrayList());
1059 //            for (RuleDelegation existingDelegation : (List<RuleDelegation>)existingResponsibility.getDelegationRules()) {
1060 //                RuleDelegation delegation = new RuleDelegation();
1061 //                PropertyUtils.copyProperties(delegation, existingDelegation);
1062 //                delegation.setRuleDelegationId(null);
1063 //                delegation.setRuleResponsibility(responsibility);
1064 //                delegation.setRuleResponsibilityId(null);
1065 //                delegation.setVersionNumber(0L);
1066 //                // it's very important that we do NOT recurse down into the delegation rules and reversion those,
1067 //                // this is important to how rule versioning works
1068 //                responsibility.getDelegationRules().add(delegation);
1069 //            }
1070         }
1071         rule.setRuleExtensions(new ArrayList());
1072         for (RuleExtensionBo existingExtension : (List<RuleExtensionBo>)existingRule.getRuleExtensions()) {
1073             RuleExtensionBo extension = new RuleExtensionBo();
1074             PropertyUtils.copyProperties(extension, existingExtension);
1075             extension.setVersionNumber(new Long(0));
1076             extension.setRuleBaseValues(rule);
1077             extension.setRuleBaseValuesId(null);
1078             extension.setRuleExtensionId(null);
1079             rule.getRuleExtensions().add(extension);
1080             extension.setExtensionValues(new ArrayList<RuleExtensionValue>());
1081             for (RuleExtensionValue existingExtensionValue : extension.getExtensionValues()) {
1082                 RuleExtensionValue extensionValue = new RuleExtensionValue();
1083                 PropertyUtils.copyProperties(extensionValue, existingExtensionValue);
1084                 extensionValue.setExtension(extension);
1085                 extensionValue.setRuleExtensionId(null);
1086                 extensionValue.setLockVerNbr(0);
1087                 extensionValue.setRuleExtensionValueId(null);
1088                 extension.getExtensionValues().add(extensionValue);
1089             }
1090         }
1091         return rule;
1092     }
1093 
1094     private static class RuleVersion {
1095         public RuleBaseValues rule;
1096         public RuleBaseValues parent;
1097         public RuleDelegationBo delegation;
1098     }
1099 
1100     private static class RuleRoutingConfig {
1101         private List configs = new ArrayList();
1102         public static RuleRoutingConfig parse() {
1103             RuleRoutingConfig config = new RuleRoutingConfig();
1104             String constant = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_CUSTOM_DOC_TYPES);
1105             if (!StringUtils.isEmpty(constant)) {
1106                 String[] ruleConfigs = constant.split(",");
1107                 for (String ruleConfig : ruleConfigs) {
1108                     String[] configElements = ruleConfig.split(":");
1109                     if (configElements.length != 4) {
1110                         throw new RuntimeException("Found incorrect number of config elements within a section of the custom rule document types config.  There should have been four ':' delimited sections!  " + ruleConfig);
1111                     }
1112                     config.configs.add(configElements);
1113                 }
1114             }
1115             return config;
1116         }
1117         public String getDocumentTypeName(RuleBaseValues rule) {
1118             for (Iterator iterator = configs.iterator(); iterator.hasNext();) {
1119                 String[] configElements = (String[]) iterator.next();
1120                 String docTypeName = configElements[0];
1121                 String ruleTemplateName = configElements[1];
1122                 String type = configElements[2];
1123                 String ruleDocTypeName = configElements[3];
1124                 if (rule.getDocTypeName().equals(docTypeName) && rule.getRuleTemplateName().equals(ruleTemplateName)) {
1125                     if (type.equals("M")) {
1126                         if (Boolean.FALSE.equals(rule.getDelegateRule())) {
1127                             return ruleDocTypeName;
1128                         }
1129                     } else if (type.equals("D")) {
1130                         if (Boolean.TRUE.equals(rule.getDelegateRule())) {
1131                             return ruleDocTypeName;
1132                         }
1133                     } else {
1134                         throw new RuntimeException("Bad rule type '" + type + "' in rule doc type routing config.");
1135                     }
1136                 }
1137             }
1138             return null;
1139         }
1140     }
1141 
1142     public String getDuplicateRuleId(RuleBaseValues rule) {
1143 
1144     	// TODO: this method is extremely slow, if we could implement a more optimized query here, that would help tremendously
1145         Rule baseRule = RuleBaseValues.to(rule);
1146     	List<RuleResponsibility> responsibilities = baseRule.getRuleResponsibilities();
1147     	List<RuleExtension> extensions = baseRule.getRuleExtensions();
1148     	String docTypeName = baseRule.getDocTypeName();
1149     	String ruleTemplateName = baseRule.getRuleTemplateName();
1150         //use api service to take advantage of caching
1151         List<Rule> rules = KewApiServiceLocator.getRuleService().getRulesByTemplateNameAndDocumentTypeName(
1152                 ruleTemplateName, docTypeName);
1153         for (Rule r : rules) {
1154             if (ObjectUtils.equals(rule.isActive(), r.isActive()) &&
1155         	        ObjectUtils.equals(docTypeName, r.getDocTypeName()) &&
1156                     ObjectUtils.equals(ruleTemplateName, r.getRuleTemplateName()) &&
1157                     CollectionUtils.collectionsEquivalent(responsibilities, r.getRuleResponsibilities()) &&
1158                     CollectionUtils.collectionsEquivalent(extensions, r.getRuleExtensions())) {
1159 
1160                 if (ObjectUtils.equals(baseRule.getRuleExpressionDef(), r.getRuleExpressionDef()) ||
1161                     (baseRule.getRuleExpressionDef() != null && r.getRuleExpressionDef() != null) &&
1162                       ObjectUtils.equals(baseRule.getRuleExpressionDef().getType(), r.getRuleExpressionDef().getType()) &&
1163                       ObjectUtils.equals(baseRule.getRuleExpressionDef().getExpression(), r.getRuleExpressionDef().getExpression())) {
1164                 // we have a duplicate
1165                     return r.getId();
1166                 }
1167             }
1168         }
1169         return null;
1170     }
1171 
1172     private void generateRuleNameIfNeeded(RuleBaseValues rule) {
1173         if (StringUtils.isBlank(rule.getName())) {
1174         	rule.setName(UUID.randomUUID().toString());
1175         }
1176     }
1177 
1178     private void assignResponsibilityIds(RuleBaseValues rule) {
1179     	for (RuleResponsibilityBo responsibility : rule.getRuleResponsibilities()) {
1180     		if (responsibility.getResponsibilityId() == null) {
1181     			responsibility.setResponsibilityId(KEWServiceLocator.getResponsibilityIdService().getNewResponsibilityId());
1182     		}
1183     	}
1184     }
1185 
1186     public RuleBaseValues saveRule(RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
1187     	rule.setPreviousRuleId(rule.getId());
1188 		rule.setPreviousVersion(null);
1189 		rule.setId(null);
1190 		makeCurrent(rule, isRetroactiveUpdatePermitted);
1191 		return rule;
1192     }
1193 
1194     public List<RuleBaseValues> saveRules(List<RuleBaseValues> rulesToSave, boolean isRetroactiveUpdatePermitted) {
1195     	List<RuleBaseValues> savedRules = new ArrayList<RuleBaseValues>();
1196     	for (RuleBaseValues rule : rulesToSave) {
1197     		rule = saveRule(rule, isRetroactiveUpdatePermitted);
1198     		savedRules.add(rule);
1199     	}
1200     	return savedRules;
1201     }
1202 
1203     public RuleDelegationBo saveRuleDelegation(RuleDelegationBo ruleDelegation, boolean isRetroactiveUpdatePermitted) {
1204     	RuleBaseValues rule = ruleDelegation.getDelegationRule();
1205 		rule.setPreviousRuleId(rule.getId());
1206 		rule.setPreviousVersion(null);
1207 		rule.setId(null);
1208 		ruleDelegation.setRuleDelegationId(null);
1209 		makeCurrent(ruleDelegation, isRetroactiveUpdatePermitted);
1210 		return ruleDelegation;
1211     }
1212 
1213     public List<RuleDelegationBo> saveRuleDelegations(List<RuleDelegationBo> ruleDelegationsToSave, boolean isRetroactiveUpdatePermitted) {
1214     	List<RuleDelegationBo> savedRuleDelegations = new ArrayList<RuleDelegationBo>();
1215     	for (RuleDelegationBo ruleDelegation : ruleDelegationsToSave) {
1216     		ruleDelegation = saveRuleDelegation(ruleDelegation, isRetroactiveUpdatePermitted);
1217     		savedRuleDelegations.add(ruleDelegation);
1218     	}
1219     	return savedRuleDelegations;
1220     }
1221 
1222     public String findResponsibilityIdForRule(String ruleName, String ruleResponsibilityName, String ruleResponsibilityType) {
1223     	return getRuleDAO().findResponsibilityIdForRule(ruleName, ruleResponsibilityName, ruleResponsibilityType);
1224     }
1225 
1226     protected String getRuleByTemplateAndDocTypeCacheKey(String ruleTemplateName, String docTypeName) {
1227         return "'templateName=' + " + ruleTemplateName +" '|' + 'documentTypeName=' + " + docTypeName;
1228     }
1229 
1230 }