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