View Javadoc

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