View Javadoc

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