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